III. Implémentation▲
III-A. Introduction▲
Nous allons présenter ici, les différentes API mises en œuvre dans le cadre de la création d'un Hook.
III-B. SetWindowsHookEx▲
III-B-1. Présentation▲
Cette API permet la mise en place d'une procédure de traitement au sein d'une des chaînes de traitement de messages :
<
DllImport
(
"user32"
)>
_
Public
Shared
Function
SetWindowsHookEx
(
_
ByVal
idHook As
Integer
, _
ByVal
lpfn As
HookProc, _
ByVal
hMod As
IntPtr, _
ByVal
dwThreadId As
Integer
) As
Integer
End
Function
La procédure de traitement installée par le Hook l'est toujours en début de chaîne.
(Cela signifie que c'est toujours la procédure de traitement installée par le dernier Hook qui est déclenchée la première).
III-B-2. Paramètres▲
III-B-2-a. idHook▲
Il s'agit du type de Hook à mettre en place :
- Hook sur les messages clavier (WH_KEYBOARD_LL et WH_KEYBOARD) ;
- Hook sur les messages souris (WH_MOUSE_LL et WH_MOUSE) ;
- Hook sur les messages de création/destruction de fenêtres de premier plan (WH_SHELL) ;
- etc.
III-B-2-b. lpfn▲
C'est un pointeur sur la procédure de traitement à inscrire dans la chaîne de traitement.
Comme pour toute utilisation d'un délégué passé à du code non managé, l'application devra s'assurer du maintien du pointeur sur ce délégué afin d'éviter sa collecte inopinée par le Garbage Collector.
Le passage d'un « AddressOf NomDeLaProcédure » est donc à proscrire, et il convient d'utiliser une fonction déléguée déclarée explicitement.
La signature de la procédure de traitement ne dépendant pas du type de Hook, on pourra utiliser le délégué suivant :
Public
Delegate
Function
HookProc _
(
ByVal
nCode As
Integer
, _
ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
Les valeurs des paramètres passés à la fonction seront par contre dépendantes du type de Hook.
Cette procédure pourra retourner une valeur différente de 0 pour indiquer au système que le message a été traité (celui-ci ne communiquera alors pas ce message à la fenêtre cible).
Toutefois, elle ne devra traiter le message que si le paramètre ncode est supérieur à 0. Dans le cas contraire, elle devra retourner le résultat de l'appel à l'API CallNextHookEx (décrite plus loin).
III-B-2-c. hMod▲
C'est le Handle de la DLL contenant la procédure pointée par lpfn.
Ce paramètre peut être omis (par cela, entendez, valorisé à IntPtr.Zero) dans le cas où cette procédure de traitement se trouve dans le même module que l'installation via SetWindowsHookEx.
Ceci est vrai qu'il s'agisse d'un Hook Global ou d'un Hook Local.
À titre personnel, je n'utilise donc pas ce paramètre, car il me semble opportun de gérer la mise en place et la suppression du Hook au travers d'un objet levant au besoin un événement. L'ensemble du code de Hooking s'en trouve donc intégralement dans le même module.
III-B-2-d. dwthreadId▲
C'est l'identifiant du thread pour lequel le Hook est positionné.
Ce paramètre valorisé à 0 équivaut à positionner un Hook pour l'ensemble des threads.
Donc :
- si dwthreadId est alimenté avec l'identifiant d'un thread, il s'agira d'un Hook Local, i.e. localisé sur ce thread ;
- si dwthreadId n'est pas défini (0), il s'agira d'un Hook Global, i.e. pour l'ensemble des threads.
III-B-3. Valeur retournée▲
Si le positionnement de la procédure de traitement au sein de la chaîne a réussi, SetWindowsHookEx retourne le Handle de la procédure de Hook.
En cas d'échec, la valeur retournée est 0 et le code erreur peut être obtenu via l'API GetLastError.
<
DllImport
(
"kernel32"
)>
_
Public
Shared
Function
GetLastError
(
) As
Integer
End
Function
III-B-4. Utilisation▲
Dans le cadre d'un Hook souris global, l'utilisation de SetWindowsHookEx peut se présenter ainsi :
Public
Delegate
Function
HookProc
(
ByVal
nCode As
Integer
, _
ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
Private
Shared
hMouseHook As
Integer
Private
Shared
MouseHookProcedure As
HookProc
<
DllImport
(
"user32"
)>
_
Public
Shared
Function
SetWindowsHookEx
(
_
ByVal
idHook As
Integer
, _
ByVal
lpfn As
HookProc, _
ByVal
hMod As
IntPtr, _
ByVal
dwThreadId As
Integer
) As
Integer
End
Function
<
DllImport
(
"kernel32"
)>
_
Public
Shared
Function
GetLastError
(
) As
Integer
End
Function
#Region
"Declaration constantes"
Protected
Const
WH_MOUSE_LL As
Integer
=
14
#End
Region
Protected
Sub
StartHookingInternal
(
)
' Evite le CallbackOnCollectedDelegate
MouseHookProcedure =
New
HookProc
(
AddressOf
MouseHookProc)
hMouseHook =
SetWindowsHookEx
(
WH_MOUSE_LL, MouseHookProcedure, _
IntPtr.Zero
, 0
)
If
hMouseHook =
0
Then
MessageBox.Show
(
"Échec de l'installation du hook : "
&
GetLastError
(
))
Else
MessageBox.Show
(
"Start effectué"
)
End
If
End
Sub
Private
Function
MouseHookProc
(
ByVal
nCode As
Integer
, _
ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
' Traitement
' --> ici
End
Function
III-C. CallNextHookEx▲
III-C-1. Présentation▲
Cette API permet de passer les informations du message en cours de traitement à la procédure suivante dans la chaîne de traitement.
<
DllImport
(
"user32"
)>
_
Public
Shared
Function
CallNextHookEx
(
_
ByVal
hhk As
Integer
, _
ByVal
nCode As
Integer
, _
ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
End
Function
L'appel à cette fonction doit être réalisé dans la procédure de traitement positionnée par SetWindowsHookEx (c'est-à-dire dans la procédure déléguée).
Sa valeur de retour correspond à la réponse fournie par la procédure de traitement du Hook suivant de la chaîne.
Une procédure de traitement retournera une valeur différente de 0 pour indiquer au système que le message a été traité et qu'il n'est pas nécessaire de le communiquer à la fenêtre cible.
En l'absence d'un blocage nécessaire du message dans la procédure de traitement de notre Hook, celle-ci doit rendre le résultat fourni par la procédure de traitement du Hook suivant de la chaîne.
Une chaîne de traitement non bloquante pour le message peut donc se représenter ainsi :
Ainsi l'utilisation de CallNextHookEx peut de manière générale se décliner à partir de :
Public
Delegate
Function
HookProc _
(
ByVal
nCode As
Integer
, _
ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
Private
InternalHookProcedure As
HookProc
Private
Shared
hHook As
Integer
Protected
Sub
StartHookingInternal
(
)
InternalHookProcedure =
New
HookProc
(
AddressOf
InternalHookProc)
hHook =
SetWindowsHookEx
(
xxxxxx , MouseHookProcedure, _
yyyy , zzzzz )
End
Sub
Private
Function
InternalHookProc
(
ByVal
nCode As
Integer
, _
ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
' Traitement du message ici
Return
CallNextHookEx
(
hHook, nCode, wParam, lParam)
End
Function
Se passer de l'appel à CallNextHookEx est fortement déconseillé.
En effet, on ne sait pas dans quelle position de la chaîne se trouve notre Hook et il est donc impossible de déterminer à priori l'effet du non-déclenchement des procédures de traitement positionnées en aval de celui-ci par les autres applications.
Ceci sera illustré dans la suite de l'article.
III-C-2. Paramètres▲
Les paramètres utilisés sont ceux obtenus par la procédure de traitement auxquels s'ajoute le paramètre hhk qui doit être alimenté avec le Handle de la procédure de Hook courant (retourné par SetWindowsHookEx).
III-C-3. Valeur retournée▲
La valeur de retour correspond à la réponse fournie par la procédure de traitement du Hook suivant de la chaîne.
III-C-4. Utilisation▲
Dans le cadre de notre exemple pour un Hook souris global, l'utilisation de CallNextHookEx peut se présenter ainsi :
<
DllImport
(
"user32"
)>
_
Public
Shared
Function
CallNextHookEx
(
_
ByVal
hhk As
Integer
, _
ByVal
nCode As
Integer
, _
ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
End
Function
Private
Function
MouseHookProc
(
ByVal
nCode As
Integer
,
ByVal
wParam As
Integer
,
ByVal
lParam As
IntPtr) As
Integer
' Traitement
' --> ici
Return
CallNextHookEx
(
hMouseHook, nCode, wParam, lParam)
End
Function
III-D. UnhookWindowsHookEx▲
III-D-1. Présentation▲
Cette API permet de retirer la procédure de traitement de la chaîne de procédures de traitement.
Toute installation d'un Hook via SetWindowsHookEx doit être suivie d'une désinstallation via UnhookWindowsHookEx, car le Hook génère un ralentissement du traitement des messages système.
Les règles de « bonne conduite » à retenir sont donc :
- installer le Hook le plus TARD possible via SetWindowsHookEx ;
- désinstaller le Hook le plus TÔT possible via UnhookWindowsHookEx.
<
DllImport
(
"user32"
)>
_
Public
Shared
Function
UnhookWindowsHookEx
(
ByVal
idHook As
Integer
) As
Boolean
End
Function
III-D-2. Paramètres▲
UnhookWindowsHookEx accepte un seul paramètre hhk définissant le Handle de la procédure de Hook à désinstaller (retourné par SetWindowsHookEx).
III-D-3. Valeur retournée▲
La fonction retourne True si la désinstallation a réussi, False sinon.
III-D-4. Utilisation▲
Dans le cadre de notre exemple pour un Hook souris global, l'utilisation de UnhookWindowsHookEx peut se présenter ainsi :
<
DllImport
(
"user32"
)>
_
Public
Shared
Function
UnhookWindowsHookEx
(
ByVal
idHook As
Integer
) As
Boolean
End
Function
Protected
Overrides
Sub
StopHookingInternal
(
)
Dim
blnUH As
Boolean
=
True
blnUH =
UnhookWindowsHookEx
(
hMouseHook)
MyBase
.OnMouseLog
(
"Stop effectué"
)
End
Sub
IV. Substitut au Hook Local▲
IV-A. Présentation▲
Un substitut intéressant à la mise en place d'un Hook Local est l'utilisation de la méthode :
Application.AddMessageFilter
(
value As
IMessageFilter)
Il s'agit ici de poser un filtre sur les messages destinés à l'application.
Chacun de ces messages est d'abord transmis à la méthode PreFilterMessage de l'objet implémentant IMessageFilter.
IV-B. Exemple▲
L'exemple suivant permet lors de la saisie d'un caractère compris entre A et Z de lever un événement avec comme paramètre le caractère en majuscule :
Option
Explicit
On
Option
Strict
On
Public
Class
FormDemarrage
Private
pf As
New
ExemplePreFilter
Public
Sub
New
(
)
InitializeComponent
(
)
Application.AddMessageFilter
(
pf)
AddHandler
pf.KeyDown
, AddressOf
pfKeyDown
End
Sub
Private
Sub
pfKeyDown
(
ByVal
s As
String
)
Me
.TextBox1.Text
=
Me
.TextBox1.Text
&
s
End
Sub
Private
Sub
Button1_Click
(
ByVal
sender As
System.Object
, _
ByVal
e As
System.EventArgs
) Handles
Button1.Click
Dim
f As
New
FormDeSaisie
f.Show
(
)
End
Sub
End
Class
Public
NotInheritable
Class
ExemplePreFilter
Implements
Windows.Forms.IMessageFilter
'
Public
Event
KeyDown
(
ByVal
s As
String
)
Public
Function
PreFilterMessage
(
ByRef
m As
System.Windows.Forms.Message
) _
As
Boolean
Implements
System.Windows.Forms.IMessageFilter.PreFilterMessage
If
m.Msg
=
&H100
Then
' WM_KEYDOWN
If
m.WParam.ToInt32
>
64
And
m.WParam.ToInt32
<
91
Then
RaiseEvent
KeyDown
(
UCase
(
Chr
(
m.WParam.ToInt32
)))
End
If
End
If
Return
False
End
Function
End
Class
L'exemple complet est disponible ici : UseApplicationPrefilter.zip.
V. Hook souris▲
V-A. Présentation▲
Nous allons utiliser les API présentées précédemment pour créer un Hook local et un Hook global sur les messages d'activité de la souris.
Pour ce faire, nous utiliserons les types de Hook suivants :
- WH_MOUSE ;
- WH_MOUSE_LL.
V-B. Types de Hook souris▲
V-B-1. WH_MOUSE▲
Ce type de Hook réagit aux messages fournis aux applications suite à un GetMessage (attente d'un message positionné par une application) ou un PeekMessage (vérification de la disponibilité d'un message et récupération) sur les messages d'activité de la souris.
Il peut être soit Local, soit Global.
V-B-2. WH_MOUSE_LL▲
Ce type de Hook réagit aux messages fournis par le driver de la souris et générés via l'API SendInput.
Il s'agit d'un Hook de « bas niveau » (d'où le LL pour Low Level).
De par sa nature, un Hook de « bas niveau » est toujours Global.
V-C. Procédure de traitement▲
Comme pour tout Hook, la procédure de traitement d'un Hook souris se présente ainsi :
Public
Delegate
Function
HookProc _
(
ByVal
nCode As
Integer
, _
ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
Dans le cadre d'un Hook souris, les paramètres passés à la procédure sont les suivants.
V-C-1. nCode▲
Code destiné à déterminer quel est le traitement à effectuer.
Pour un Hook souris de type WH_MOUSE_LL, il sera toujours positionné à HC_ACTION signifiant que des informations sur un message d'activité de la souris sont disponibles.
Pour un Hook de type WH_MOUSE, il pourra également prendre la valeur HC_NOREMOVE signifiant que des informations sur un message d'activité de la souris sont disponibles et que ce message n'a pas été supprimé de la file des messages suite à un PeekMessage avec flag PM_NOREMOVE.
Protected
Const
HC_ACTION As
Integer
=
0
Protected
Const
HC_NOREMOVE As
Integer
=
3
V-C-2. wParam▲
Il s'agit du type de message d'activité de la souris :
' Type de messages souris
' -- Déplacement
Protected
Const
WM_MOUSEMOVE As
Integer
=
&H200
' -- Bouton gauche
' ---- Zone cliente
Protected
Const
WM_LBUTTONDOWN As
Integer
=
&H201
Protected
Const
WM_LBUTTONUP As
Integer
=
&H202
Protected
Const
WM_LBUTTONDBLCLK As
Integer
=
&H203
' ---- Zone non cliente
Protected
Const
WM_NCLBUTTONDBLCLK As
Integer
=
&HA3
Protected
Const
WM_NCLBUTTONDOWN As
Integer
=
&HA1
Protected
Const
WM_NCLBUTTONUP As
Integer
=
&HA2
' -- Bouton droit
' ---- Zone cliente
Protected
Const
WM_RBUTTONDOWN As
Integer
=
&H204
Protected
Const
WM_RBUTTONUP As
Integer
=
&H205
Protected
Const
WM_RBUTTONDBLCLK As
Integer
=
&H206
' ---- Zone non cliente
Protected
Const
WM_NCRBUTTONDBLCLK As
Integer
=
&HA6
Protected
Const
WM_NCRBUTTONDOWN As
Integer
=
&HA4
Protected
Const
WM_NCRBUTTONUP As
Integer
=
&HA5
' -- Bouton central
' ---- Zone cliente
Protected
Const
WM_MBUTTONDOWN As
Integer
=
&H207
Protected
Const
WM_MBUTTONUP As
Integer
=
&H208
Protected
Const
WM_MBUTTONDBLCLK As
Integer
=
&H209
' ---- Zone non cliente
Protected
Const
WM_NCMBUTTONDBLCLK As
Integer
=
&HA9
Protected
Const
WM_NCMBUTTONDOWN As
Integer
=
&HA7
Protected
Const
WM_NCMBUTTONUP As
Integer
=
&HA8
' -- X Bouton
' ---- Zone cliente
Protected
Const
WM_XBUTTONDOWN As
Integer
=
&H20B
Protected
Const
WM_XBUTTONUP As
Integer
=
&H20C
Protected
Const
WM_XBUTTONDBLCLK As
Integer
=
&H20D
' ---- Zone non cliente
Protected
Const
WM_NCXBUTTONDOWN As
Integer
=
&HAB
Protected
Const
WM_NCXBUTTONUP As
Integer
=
&HAC
Protected
Const
WM_NCXBUTTONDBLCLK As
Integer
=
&HAD
' Molette
Protected
Const
WM_MOUSEWHEEL As
Integer
=
&H20A
Protected
Const
WM_MOUSEHWHEEL As
Integer
=
&H20E
Un hook de type WH_MOUSE_LL ne reçoit pas de messages de type « double click » (WM_xxBUTTONDBLCLK) ou « click en zone non cliente » (WM_NCxBUTTONxxxx).
V-C-3. lParam▲
V-C-3-a. Hook de type WH_MOUSE_LL▲
Dans ce cas, lParam contient un pointeur vers une structure MOUSEHOOKSTRUCT :
<
StructLayout
(
LayoutKind.Sequential
)>
_
Private
Class
MSLLHOOKSTRUCT
Public
pt As
PointMHS
Public
mouseData As
Integer
Public
flags As
Integer
Public
time
As
Integer
Public
dwExtraInfo As
Integer
End
Class
Ces paramètres sont :
- pt :
structure contenant les coordonnées X/Y de la position de la souris :
<
StructLayout
(
LayoutKind.Sequential
)>
_
Protected
Class
PointMHS
Public
x As
Integer
Public
y As
Integer
End
Class
- mouseData :
dans le cas d'un message WM_MOUSEWHEEL, Delta de positionnement (sur les 8 premiers bits) ;
- flags :
1 si l'événement est issu d'un SendInput (événement généré applicativement) , 0 sinon ;
- time :
Timestamp du message : durée entre le démarrage du système et la production du message en millisecondes ;
- dwExtraInfo :
Information supplémentaire (non utilisée).
V-C-3-b. Hook de type WH_MOUSE▲
Dans ce cas, lParam contient un pointeur vers une structure MOUSEHOOKSTRUCTEX :
<
StructLayout
(
LayoutKind.Sequential
)>
_
Private
Class
MOUSEHOOKSTRUCT
Public
pt As
PointMHS
Public
hwnd As
Integer
Public
wHitTestCode As
Integer
Public
dwExtraInfo As
Integer
Public
mouseData As
Integer
End
Class
Ses paramètres sont :
- pt :
structure contenant les coordonnées X/Y de la position de la souris :
<
StructLayout
(
LayoutKind.Sequential
)>
_
Protected
Class
PointMHS
Public
x As
Integer
Public
y As
Integer
End
Class
- hwnd :
handle de la fenêtre destinatrice du message ;
- wHitTestCode :
code identifiant la zone de la fenêtre concernée par le message :
HTERROR =
(
-2
)
HTTRANSPARENT =
(
-1
)
HTNOWHERE =
0
HTCLIENT =
1
HTCAPTION =
2
HTSYSMENU =
3
HTGROWBOX =
4
HTSIZE =
HTGROWBOX
HTMENU =
5
HTHSCROLL =
6
HTVSCROLL =
7
HTMINBUTTON =
8
HTMAXBUTTON =
9
HTLEFT =
10
HTRIGHT =
11
HTTOP =
12
HTTOPLEFT =
13
HTTOPRIGHT =
14
HTBOTTOM =
15
HTBOTTOMLEFT =
16
HTBOTTOMRIGHT =
17
HTBORDER =
18
HTREDUCE =
HTMINBUTTON
HTZOOM =
HTMAXBUTTON
HTSIZEFIRST =
HTLEFT
HTSIZELAST =
HTBOTTOMRIGHT
HTOBJECT =
19
HTCLOSE =
20
HTHELP =
21
- dwExtraInfo :
information supplémentaire (non utilisée) ;
- mouseData :
dans le cas d'un message WM_MOUSEWHEEL, Delta de positionnement (sur les huit premiers bits).
VI. Exemples▲
VI-A. Présentation▲
Nous allons dans cet exemple illustrer l'utilisation d'un Hook souris local, d'un Hook souris global et d'un filtre de messages.
Une interface nous permettra de mettre en évidence le fonctionnement de chaque type de Hook et nous créerons également un « trou de souris » pour chaque type de Hook (par « trou de souris », j'entends une zone qui empêche la détection de l'activité de la souris).
VI-B. Conception▲
Les classes gérant nos trois types de Hook souris (je considère ici que le Prefilter peut être vu comme une sorte de Hook), devront respecter les règles suivantes :
- le Hook est posé le plus tard possible ;
- le Hook est retiré le plus tôt possible.
Pour ce faire, nous gérerons des « abonnements » à un événement « Activité de la souris » :
le Hook sera posé au premier abonnement et retiré lors du dernier désabonnement à l'événement.
Comme on ne peut forcer l'utilisation du désabonnement à cet événement dans les objets qui utiliseront le Hook, nous imposerons à ces objets d'implémenter un événement informant le Hook de leur destruction.
Nos classes de gestion de Hook devront également lors de leur destruction effectuer le nettoyage nécessaire, à savoir, au minimum retirer le Hook (ou le Filtre) posé.
Dans un but d'illustration, ces classes mettront également à disposition un événement de Log qui permettra de tracer leur fonctionnement.
Les trois types de Hook ayant donc ces fonctions communes, nous allons créer une classe de base qui gérera celles-ci.
Les Hook à base d'API nécessitant un ensemble de déclarations communes (dont les API !), nous utiliserons également une classe de base qui héritera de la classe précédente et mettra ces déclarations à disposition.
Pour finir, chacune des classes de gestion de l'un des types de Hook sera un singleton afin d'éviter, pour une même application, la création de plusieurs Hooks de même type.
C'est en effet complètement inutile dans notre exemple vu le mode de fonctionnement par abonnement à l'événement d'activité souris :
un objet devant récupérer l'activité de la souris pourra tout simplement s'abonner à l'événement sans créer une nouvelle instance de la classe gestion du Hook.
VI-C. Développement▲
VI-C-1. Création de la classe de base des Hooks souris▲
Notre classe de base par définition n'est pas instanciable (MustInherit Class).
Elle effectue les fonctions suivantes :
1) Déclarations communes aux différents types de Hooks
C'est l'ensemble des constantes décrivant les messages de la souris ;
2) Procédures communes aux différents types de Hooks
Tout Hook, quel que soit son type, doit être posé et retiré. Nous forçons donc les classes qui héritent de notre classe de base à mettre à disposition ces procédures (en utilisant des Protected MustOverride Sub) ;
3) Gestion des abonnements
Notre classe met à disposition un événement MouseActivity qui sera levé à chaque détection de l'activité de la souris.
Pour gérer les abonnements à cet événement, nous utilisons un événement personnalisé (Custom Event) qui nous permet de respecter les règles de pose du Hook au plus tard et de retirement au plus tôt.
L'abonnement à cet événement n'est autorisé que pour les objets implémentant IComponent, car ceux-ci lèvent un événement Disposed lors de leur destruction (en l'absence de surcharge de la méthode Dispose).
La destruction d'un objet abonné à l'événement entraîne son désabonnement d'office ;
4) Nettoyage
C'est l'implémentation de IDisposable et la gestion du retirement du Hook lors de la destruction de l'instance.
Option
Explicit
On
Option
Strict
On
Imports
System.Windows.Forms
Public
Delegate
Sub
MouseActivityEventHandler
(
ByVal
e As
MouseEventArgs)
Public
Enum
eMouseHookType
LocalMouseHook =
1
GlobalMouseHook =
2
PreFilterMouseHook =
3
End
Enum
Public
MustInherit
Class
CLFWAddHookBase
Implements
IDisposable
#Region
"Dispose"
Public
Overloads
Sub
Dispose
(
) Implements
IDisposable.Dispose
Dispose
(
True
)
GC.SuppressFinalize
(
Me
)
End
Sub
Private
Disposed As
Boolean
=
False
Private
Overloads
Sub
Dispose
(
ByVal
disposing As
Boolean
)
If
Not
Me
.Disposed
Then
If
disposing Then
' Pas de ressource managé à disposer
End
If
RaiseEvent
MouseLog
(
"Dispose de la class Hook"
)
StopHookingInternal
(
)
End
If
Disposed =
True
GC.SuppressFinalize
(
Me
)
End
Sub
Protected
Overrides
Sub
Finalize
(
)
Dispose
(
False
)
MyBase
.Finalize
(
)
End
Sub
#End
Region
#Region
"Declaration constantes"
' Type de messages souris
' -- Déplacement
Protected
Const
WM_MOUSEMOVE As
Integer
=
&H200
' -- Bouton gauche
' ---- Zone cliente
Protected
Const
WM_LBUTTONDOWN As
Integer
=
&H201
Protected
Const
WM_LBUTTONUP As
Integer
=
&H202
Protected
Const
WM_LBUTTONDBLCLK As
Integer
=
&H203
' ---- Zone non cliente
Protected
Const
WM_NCLBUTTONDBLCLK As
Integer
=
&HA3
Protected
Const
WM_NCLBUTTONDOWN As
Integer
=
&HA1
Protected
Const
WM_NCLBUTTONUP As
Integer
=
&HA2
' -- Bouton droit
' ---- Zone cliente
Protected
Const
WM_RBUTTONDOWN As
Integer
=
&H204
Protected
Const
WM_RBUTTONUP As
Integer
=
&H205
Protected
Const
WM_RBUTTONDBLCLK As
Integer
=
&H206
' ---- Zone non cliente
Protected
Const
WM_NCRBUTTONDBLCLK As
Integer
=
&HA6
Protected
Const
WM_NCRBUTTONDOWN As
Integer
=
&HA4
Protected
Const
WM_NCRBUTTONUP As
Integer
=
&HA5
' -- Bouton central
' ---- Zone cliente
Protected
Const
WM_MBUTTONDOWN As
Integer
=
&H207
Protected
Const
WM_MBUTTONUP As
Integer
=
&H208
Protected
Const
WM_MBUTTONDBLCLK As
Integer
=
&H209
' ---- Zone non cliente
Protected
Const
WM_NCMBUTTONDBLCLK As
Integer
=
&HA9
Protected
Const
WM_NCMBUTTONDOWN As
Integer
=
&HA7
Protected
Const
WM_NCMBUTTONUP As
Integer
=
&HA8
' -- X Bouton
' ---- Zone cliente
Protected
Const
WM_XBUTTONDOWN As
Integer
=
&H20B
Protected
Const
WM_XBUTTONUP As
Integer
=
&H20C
Protected
Const
WM_XBUTTONDBLCLK As
Integer
=
&H20D
' ---- Zone non cliente
Protected
Const
WM_NCXBUTTONDOWN As
Integer
=
&HAB
Protected
Const
WM_NCXBUTTONUP As
Integer
=
&HAC
Protected
Const
WM_NCXBUTTONDBLCLK As
Integer
=
&HAD
' Molette
Protected
Const
WM_MOUSEWHEEL As
Integer
=
&H20A
Protected
Const
WM_MOUSEHWHEEL As
Integer
=
&H20E
#End
Region
#Region
"Activité souris"
Protected
MouseActivityEventHandlerList As
New
ArrayList
Public
Custom
Event
MouseActivity As
MouseActivityEventHandler
AddHandler
(
ByVal
value As
MouseActivityEventHandler)
If
Not
MouseActivityEventHandlerList.Contains
(
value) Then
' Gestion des objets de type IComponent disposant de l'event Disposed
If
GetType
(
System.ComponentModel.IComponent
).IsAssignableFrom
(
_
value.Target.GetType
) Then
Dim
c As
System.ComponentModel.IComponent
=
CType
(
value.Target
, _
System.ComponentModel.IComponent
)
AddHandler
c.Disposed
, AddressOf
RemoveDisposedComponent
RaiseEvent
MouseLog
(
"Type IComponent détecté pour : "
&
_
value.Target.GetHashCode
)
End
If
AddMouseActivityEventHandler
(
value)
End
If
End
AddHandler
RemoveHandler
(
ByVal
value As
MouseActivityEventHandler)
If
MouseActivityEventHandlerList.Contains
(
value) Then
RemoveMouseActivityEventHandler
(
value)
End
If
End
RemoveHandler
RaiseEvent
(
ByVal
e As
System.Windows.Forms.MouseEventArgs
)
For
i As
Integer
=
0
To
MouseActivityEventHandlerList.Count
-
1
Dim
handler As
MouseActivityEventHandler =
_
CType
(
MouseActivityEventHandlerList
(
i), MouseActivityEventHandler)
If
handler IsNot
Nothing
Then
handler.Invoke
(
e)
End
If
Next
End
RaiseEvent
End
Event
Private
Sub
RemoveDisposedComponent
(
ByVal
sender As
Object
, ByVal
e As
EventArgs)
For
i As
Integer
=
0
To
MouseActivityEventHandlerList.Count
-
1
If
CType
(
MouseActivityEventHandlerList
(
i), MouseActivityEventHandler).Target
_
Is
sender Then
RemoveMouseActivityEventHandler
(
CType
(
MouseActivityEventHandlerList
(
i), _
MouseActivityEventHandler))
Exit
Sub
End
If
Next
End
Sub
Private
Sub
AddMouseActivityEventHandler
(
ByVal
value As
MouseActivityEventHandler)
' Premiére demande d'abonnement, on positionne le hook
If
MouseActivityEventHandlerList.Count
=
0
Then
RaiseEvent
MouseLog
(
"Start demandé par : "
&
value.Target.GetHashCode
)
StartHookingInternal
(
)
End
If
RaiseEvent
MouseLog
(
"Abonnement pour : "
&
value.Target.GetHashCode
)
MouseActivityEventHandlerList.Add
(
value)
End
Sub
Private
Sub
RemoveMouseActivityEventHandler
(
ByVal
value As
MouseActivityEventHandler)
' Derniére demande de désabonnement, on supprime le hook
MouseActivityEventHandlerList.Remove
(
value)
RaiseEvent
MouseLog
(
"Désabonnement pour : "
&
value.Target.GetHashCode
)
If
MouseActivityEventHandlerList.Count
=
0
Then
RaiseEvent
MouseLog
(
"Stop demandé par : "
&
value.Target.GetHashCode
)
StopHookingInternal
(
)
End
If
End
Sub
Protected
Sub
OnMouseActivity
(
ByVal
e As
MouseEventArgs)
RaiseEvent
MouseActivity
(
e)
End
Sub
#End
Region
#Region
"Log"
Public
Event
MouseLog
(
ByVal
Text
As
String
)
Protected
Sub
OnMouseLog
(
ByVal
Text
As
String
)
RaiseEvent
MouseLog
(
Text
)
End
Sub
#End
Region
#Region
"Gestion du Hook"
Protected
MustOverride
Sub
StartHookingInternal
(
)
Protected
MustOverride
Sub
StopHookingInternal
(
)
#End
Region
End
Class
VI-C-2. Création de la classe de base des Hooks souris via API▲
Cette classe de base hérite de la classe de base des Hooks souris.
Elle est également par définition non instanciable (MustInherit Class).
Son seul but est de mutualiser l'ensemble des déclarations communes aux types de Hook souris via API : déclaration des API ainsi que des structures et constantes qu'elles utilisent.
Option
Explicit
On
Option
Strict
On
Imports
System.Runtime.InteropServices
Imports
System.Windows.Forms
Public
MustInherit
Class
CLFWAddMouseHookBase
Inherits
CLFWAddHookBase
Public
Delegate
Function
HookProc
(
ByVal
nCode As
Integer
, _
ByVal
wParam As
Integer
, ByVal
lParam As
IntPtr) As
Integer
Protected
hMouseHook As
Integer
Protected
MouseHookProcedure As
HookProc
<
DllImport
(
"user32"
)>
_
Public
Shared
Function
CallNextHookEx
(
ByVal
hhk As
Integer
, _
ByVal
nCode As
Integer
, ByVal
wParam As
Integer
, ByVal
lParam As
IntPtr) As
Integer
End
Function
<
DllImport
(
"user32"
)>
_
Public
Shared
Function
SetWindowsHookEx
(
ByVal
idHook As
Integer
, _
ByVal
lpfn As
HookProc, ByVal
hMod As
IntPtr, ByVal
dwThreadId As
Integer
) As
Integer
End
Function
<
DllImport
(
"user32"
)>
_
Public
Shared
Function
UnhookWindowsHookEx
(
ByVal
hhk As
Integer
) As
Boolean
End
Function
<
DllImport
(
"kernel32"
)>
_
Public
Shared
Function
GetLastError
(
) As
Integer
End
Function
<
DllImport
(
"kernel32"
)>
_
Public
Shared
Function
GetCurrentThreadId
(
) As
Integer
End
Function
#Region
"Declaration constantes"
Protected
Const
HC_ACTION As
Integer
=
0
Protected
Const
HC_NOREMOVE As
Integer
=
3
' Type de Hooks souris
Protected
Const
WH_MOUSE As
Integer
=
7
Protected
Const
WH_MOUSE_LL As
Integer
=
14
#End
Region
#Region
"Structures"
<
StructLayout
(
LayoutKind.Sequential
)>
_
Protected
Class
PointMHS
Public
x As
Integer
Public
y As
Integer
End
Class
#End
Region
End
Class
VI-C-3. Création des classes de Hook souris▲
Il s'agit des classes permettant l'utilisation des trois types de Hooks souris.
VI-C-3-a. Hook souris local▲
C'est une classe singleton qui hérite de la classe de base des Hooks via API. Elle gère les spécificités d'un Hook local quant à la pose et au retirement du Hook et surtout quant à la procédure déléguée utilisée pour traiter le message (notamment l'utilisation de la structure MOUSEHOOKSTRUCTEX).
Option
Explicit
On
Option
Strict
On
Imports
System.Runtime.InteropServices
Imports
System.Windows.Forms
Public
NotInheritable
Class
CLFWAddMouseHookLocal
Inherits
CLFWAddMouseHookBase
#Region
"Singleton"
Public
Shared
ReadOnly
Property
Instance
(
) As
CLFWAddMouseHookLocal
Get
Return
Internal.instance
End
Get
End
Property
Private
Class
Internal
Friend
Shared
ReadOnly
instance As
CLFWAddMouseHookLocal =
_
New
CLFWAddMouseHookLocal
End
Class
Private
Sub
New
(
)
End
Sub
#End
Region
#Region
"Structures"
<
StructLayout
(
LayoutKind.Sequential
)>
_
Private
Class
MOUSEHOOKSTRUCTEX
Public
pt As
PointMHS
Public
hwnd As
Integer
Public
wHitTestCode As
Integer
Public
dwExtraInfo As
Integer
Public
mouseData As
Integer
End
Class
#End
Region
Protected
Overrides
Sub
StartHookingInternal
(
)
' Évite le CallbackOnCollectedDelegate
' Lors du passage de délégués à du code non managé,
' ils doivent être maintenus actifs par l'application managée
' jusqu'à ce qu'il soit garanti qu'ils ne seront jamais appelés.
MouseHookProcedure =
New
HookProc
(
AddressOf
MouseHookProc)
hMouseHook =
SetWindowsHookEx
(
WH_MOUSE, MouseHookProcedure, _
IntPtr.Zero
, _
GetCurrentThreadId)
If
hMouseHook =
0
Then
MyBase
.OnMouseLog
(
"Échec de l'installation du hook : "
&
GetLastError
(
))
Else
MyBase
.OnMouseLog
(
"Start effectué - Thread = "
&
_
System.Threading.Thread.CurrentThread.ManagedThreadId
)
End
If
End
Sub
Protected
Overrides
Sub
StopHookingInternal
(
)
Dim
blnUH As
Boolean
=
True
blnUH =
UnhookWindowsHookEx
(
hMouseHook)
MyBase
.OnMouseLog
(
"Stop effectué"
)
End
Sub
Private
Function
MouseHookProc
(
ByVal
nCode As
Integer
, ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
If
nCode =
HC_ACTION Then
Dim
mbMouseButton As
MouseButtons =
MouseButtons.None
Dim
intClick As
Integer
Select
Case
wParam
Case
WM_LBUTTONDBLCLK, WM_NCLBUTTONDBLCLK
mbMouseButton =
MouseButtons.Left
intClick =
2
Case
WM_RBUTTONDBLCLK, WM_NCRBUTTONDBLCLK
mbMouseButton =
MouseButtons.Right
intClick =
2
Case
WM_LBUTTONDOWN, WM_NCLBUTTONDOWN
mbMouseButton =
MouseButtons.Left
intClick =
1
Case
WM_RBUTTONDOWN, WM_NCRBUTTONDOWN
mbMouseButton =
MouseButtons.Right
intClick =
1
End
Select
If
mbMouseButton <>
MouseButtons.None
Then
Dim
mhs As
MOUSEHOOKSTRUCTEX =
CType
(
Marshal.PtrToStructure
_
(
lParam, GetType
(
MOUSEHOOKSTRUCTEX)), MOUSEHOOKSTRUCTEX)
Dim
meaArgs As
New
MouseEventArgs
(
mbMouseButton, intClick, _
mhs.pt.x
, mhs.pt.y
, 0
)
' On ne lève l'event que si des abonnements existent
' il s'agit ici de gérer l'absence d'appel à StopHooking
If
MouseActivityEventHandlerList.Count
>
0
Then
MyBase
.OnMouseLog
(
"Event MouseActivity levé pour "
&
_
MouseActivityEventHandlerList.Count.ToString
&
" abonnements"
)
MyBase
.OnMouseActivity
(
meaArgs)
Else
MyBase
.OnMouseLog
(
"Event MouseActivity NON levé"
)
End
If
End
If
End
If
Return
CallNextHookEx
(
hMouseHook, nCode, wParam, lParam)
End
Function
End
Class
VI-C-3-b. Hook souris global▲
C'est une classe singleton qui hérite de la classe de base des Hooks via API. Elle gère les spécificités d'un Hook Global quant à la pose et au retirement du Hook et surtout quant à la procédure déléguée utilisée pour traiter le message (notamment l'utilisation de la structure MSLLHOOKSTRUCT et l'absence de message de type NC).
Option
Explicit
On
Option
Strict
On
Imports
System.Runtime.InteropServices
Imports
System.Windows.Forms
Public
NotInheritable
Class
CLFWAddMouseHookGlobal
Inherits
CLFWAddMouseHookBase
#Region
"Singleton"
Public
Shared
ReadOnly
Property
Instance
(
) As
CLFWAddMouseHookGlobal
Get
Return
Internal.instance
End
Get
End
Property
Private
Class
Internal
Friend
Shared
ReadOnly
instance As
CLFWAddMouseHookGlobal =
_
New
CLFWAddMouseHookGlobal
End
Class
Private
Sub
New
(
)
End
Sub
#End
Region
#Region
"Structures"
<
StructLayout
(
LayoutKind.Sequential
)>
_
Private
Class
MSLLHOOKSTRUCT
Public
pt As
PointMHS
Public
mouseData As
Integer
Public
flags As
Integer
Public
time
As
Integer
Public
dwExtraInfo As
Integer
End
Class
#End
Region
Protected
Overrides
Sub
StartHookingInternal
(
)
' Évite le CallbackOnCollectedDelegate
' Lors du passage de délégués à du code non managé,
' ils doivent être maintenus actifs par l'application managée
' jusqu'à ce qu'il soit garanti qu'ils ne seront jamais appelés.
MouseHookProcedure =
New
HookProc
(
AddressOf
MouseHookProc)
hMouseHook =
SetWindowsHookEx
(
WH_MOUSE_LL, MouseHookProcedure, _
IntPtr.Zero
, 0
)
If
hMouseHook =
0
Then
MyBase
.OnMouseLog
(
"Échec de l'installation du hook : "
&
GetLastError
(
))
Else
MyBase
.OnMouseLog
(
"Start effectué"
)
End
If
End
Sub
Protected
Overrides
Sub
StopHookingInternal
(
)
Dim
blnUH As
Boolean
=
True
blnUH =
UnhookWindowsHookEx
(
hMouseHook)
MyBase
.OnMouseLog
(
"Stop effectué"
)
End
Sub
Private
Function
MouseHookProc
(
ByVal
nCode As
Integer
, ByVal
wParam As
Integer
, _
ByVal
lParam As
IntPtr) As
Integer
If
nCode =
HC_ACTION Then
Dim
mbMouseButton As
MouseButtons =
MouseButtons.None
Select
Case
wParam
Case
WM_LBUTTONDOWN
mbMouseButton =
MouseButtons.Left
Case
WM_RBUTTONDOWN
mbMouseButton =
MouseButtons.Right
End
Select
If
mbMouseButton <>
MouseButtons.None
Then
Dim
mhs As
MSLLHOOKSTRUCT =
CType
(
Marshal.PtrToStructure
_
(
lParam, GetType
(
MSLLHOOKSTRUCT)), MSLLHOOKSTRUCT)
Dim
meaArgs As
New
MouseEventArgs
(
mbMouseButton, 1
, mhs.pt.x
, _
mhs.pt.y
, 0
)
' On ne lève l'event que si des abonnements existent
' il s'agit ici de gérer l'absence d'appel à StopHooking
If
MouseActivityEventHandlerList.Count
>
0
Then
MyBase
.OnMouseLog
(
"Event MouseActivity levé pour "
&
_
MouseActivityEventHandlerList.Count.ToString
&
" abonnements"
)
MyBase
.OnMouseActivity
(
meaArgs)
Else
MyBase
.OnMouseLog
(
"Event MouseActivity NON levé"
)
End
If
End
If
End
If
Return
CallNextHookEx
(
hMouseHook, nCode, wParam, lParam)
End
Function
End
Class
VI-C-3-c. « Hook » via PreFilter▲
C'est une classe singleton qui hérite de la classe de base des Hooks souris (car elle n'a aucun besoin des déclarations nécessaires à l'utilisation des API).
Option
Explicit
On
Option
Strict
On
Imports
System.Windows.Forms
Public
NotInheritable
Class
CLFWAddPreFilter
Inherits
CLFWAddHookBase
Implements
Windows.Forms.IMessageFilter
#Region
"Singleton"
Public
Shared
ReadOnly
Property
Instance
(
) As
CLFWAddPreFilter
Get
Return
Internal.instance
End
Get
End
Property
Private
Class
Internal
Friend
Shared
ReadOnly
instance As
CLFWAddPreFilter =
New
CLFWAddPreFilter
End
Class
Private
Sub
New
(
)
End
Sub
#End
Region
Public
Function
PreFilterMessage
(
ByRef
m As
System.Windows.Forms.Message
) _
As
Boolean
Implements
System.Windows.Forms.IMessageFilter.PreFilterMessage
Dim
mbMouseButton As
MouseButtons =
MouseButtons.None
Dim
intClick As
Integer
Select
Case
m.Msg
Case
WM_LBUTTONDBLCLK, WM_NCLBUTTONDBLCLK
mbMouseButton =
MouseButtons.Left
intClick =
2
Case
WM_RBUTTONDBLCLK, WM_NCRBUTTONDBLCLK
mbMouseButton =
MouseButtons.Right
intClick =
2
Case
WM_LBUTTONDOWN, WM_NCLBUTTONDOWN
mbMouseButton =
MouseButtons.Left
intClick =
1
Case
WM_RBUTTONDOWN, WM_NCRBUTTONDOWN
mbMouseButton =
MouseButtons.Right
intClick =
1
End
Select
If
mbMouseButton <>
MouseButtons.None
Then
Dim
meaArgs As
New
MouseEventArgs
(
mbMouseButton, intClick, _
Control.MousePosition.X
, Control.MousePosition.Y
, 0
)
MyBase
.OnMouseLog
(
"Event MouseActivity levé pour "
&
_
MouseActivityEventHandlerList.Count.ToString
&
" abonnements"
)
MyBase
.OnMouseActivity
(
meaArgs)
End
If
Return
False
End
Function
Protected
Overrides
Sub
StartHookingInternal
(
)
Application.AddMessageFilter
(
Me
)
MyBase
.OnMouseLog
(
"Start effectué"
)
End
Sub
Protected
Overrides
Sub
StopHookingInternal
(
)
Application.RemoveMessageFilter
(
Me
)
MyBase
.OnMouseLog
(
"Stop effectué"
)
End
Sub
End
Class
VI-D. Utilisation▲
Nous allons maintenant utiliser ces classes dans une petite application qui met en évidence leur fonctionnement.
Je ne commenterai pas l'ensemble du code de cette application, mais :
Le source est disponible ici : UseMouseHooking.zip.
Au démarrage, la form principale se décompose en quatre parties :
- un cadre dédié au Prefilter ;
- un cadre dédié au Hook Local ;
- un cadre dédié au Hook Global ;
- un bouton [trou de souris].
Chaque cadre met à disposition :
- un bouton [Actif/Inactif] qui permet de démarrer le Hook du type concerné et de tracer l'activité de la souris remontée par ce type de Hook ;
- une liste « Log » de l'activité de la souris détectée par ce type de Hook ;
- un bouton [Test] qui déclenche l'ouverture d'un formulaire permettant de valider la gestion des abonnements/désabonnements à ce Hook (la fermeture du formulaire entraîne le désabonnement de l'ensemble des HookButtons activés par exemple).
Le bouton [Trou de souris] déclenche l'ouverture d'un formulaire « Trou de souris » composé de trois MouseHoleControl, le MouseHoleControl étant un composant personnalisé qui pose un hook local, global ou via PreFilter en bloquant le message une fois celui-ci traité :
- pas d'appel à CallNextHookEx pour le Hook Local et le Hook Global ;
- renvoi de true (message traité) dans le PreFilter.
Faites quelques tests est vous constaterez que :
- lorsque l'on ouvre le formulaire « Trou de souris » avant d'installer un Hook Local (donc via les API), le Hook du formulaire principal fonctionne correctement lorsque l'on clique sur le trou de souris « Local Hole » ;
- lorsque l'on ouvre le formulaire « Trou de souris » après avoir installé un Hook Local, le Hook du formulaire principal ne fonctionne pas lorsque l'on clique sur le trou de souris « Local Hole ».
Le même comportement est observé pour le Hook Global.
Ceci est normal puisque les Hooks via API sont toujours installés en début de chaîne.
- lorsque l'on ouvre le formulaire « Trou de souris » avant d'installer un Prefilter, le PreFilter du formulaire principal ne fonctionne pas lorsque l'on clique sur le trou de souris « PreFilter Hole » ;
- lorsque l'on ouvre le formulaire « Trou de souris » après avoir installé un Prefilter, le PreFilter du formulaire principal fonctionne correctement lorsque l'on clique sur le trou de souris « PreFilter Hole ».
C'est également normal, car les filtres de messages d'application sont utilisés dans l'ordre de leur positionnement.
VI-E. Utilisation dans le cadre d'un Popup▲
Cet exemple illustre l'utilisation du Prefilter dans le cadre de la gestion de la fermeture d'un Popup.
Nous allons modifier la classe UltraBasicPopup présentée dans l'article précédent Création d'un popup pour que le Popup se ferme lors d'un clic sur un élément de l'application (mais en dehors de la Region du Popup).
Nous ajoutons également la gestion de la désactivation du formulaire contenant le Control ayant levé le Popup.
Option
Explicit
On
Option
Strict
On
Public
Class
UltraBasicPopup
Inherits
Button
#Region
"Constantes"
Private
Const
WS_EX_TOOLWINDOW As
Integer
=
&H80
Private
Const
SW_SHOWNOACTIVATE As
Integer
=
4
#End
Region
Protected
Overrides
ReadOnly
Property
CreateParams
(
) _
As
System.Windows.Forms.CreateParams
Get
Dim
p As
CreateParams =
MyBase
.CreateParams
p.ExStyle
=
WS_EX_TOOLWINDOW
p.Parent
=
IntPtr.Zero
Return
p
End
Get
End
Property
Private
Declare
Function
SetParent Lib
"user32"
(
_
ByVal
hWndChild As
IntPtr, _
ByVal
hWndNewParent As
IntPtr) As
Integer
Private
Declare
Function
ShowWindow Lib
"user32"
(
_
ByVal
hWnd As
IntPtr, _
ByVal
nCmdShow As
Integer
) As
Integer
Private
Declare
Function
DestroyWindow Lib
"user32.dll"
(
_
ByVal
hWnd As
IntPtr) As
Boolean
#Region
"declaration"
Private
cControlParent As
Control
Private
blnOpened As
Boolean
#End
Region
#Region
"Propriétés"
Public
ReadOnly
Property
Opened
(
) As
Boolean
Get
Return
blnOpened
End
Get
End
Property
#End
Region
Private
Sub
ClosePopupOnDeactivate
(
ByVal
sender As
Object
, ByVal
e As
EventArgs)
ClosePopup
(
)
End
Sub
Private
Sub
ClosePopup
(
)
Dim
f As
Form =
cControlParent.FindForm
RemoveHandler
f.Deactivate
, AddressOf
ClosePopupOnDeactivate
RemoveHandler
CLFWAddPreFilter.Instance.MouseActivity
, _
AddressOf
HPreFilterMouseActivityProc
DestroyWindow
(
MyBase
.Handle
)
blnOpened =
False
End
Sub
Private
Sub
OpenPopup
(
)
' Gestion de la désactivation de la form parente du control ayant
' créé le popup
Dim
f As
Form =
cControlParent.FindForm
AddHandler
f.Deactivate
, AddressOf
ClosePopupOnDeactivate
' Gestion du clic au sein de l'application
AddHandler
CLFWAddPreFilter.Instance.MouseActivity
, _
AddressOf
HPreFilterMouseActivityProc
SetParent
(
MyBase
.Handle
, IntPtr.Zero
)
' Affichage
ShowWindow
(
MyBase
.Handle
, SW_SHOWNOACTIVATE)
blnOpened =
True
End
Sub
Private
Sub
HPreFilterMouseActivityProc
(
ByVal
e As
MouseEventArgs)
If
(Not
Me
.Bounds.Contains
(
e.Location
)) Then
ClosePopup
(
)
End
If
End
Sub
Public
Sub
ShowPopup
(
ByVal
IControlParent As
Control)
cControlParent =
IControlParent
Me
.SetLocation
(
)
Me
.Size
=
New
Size
(
150
, 30
)
Me
.Region
=
New
Region
(
_
New
Rectangle
(
3
, 3
, Me
.Size.Width
-
6
, Me
.Size.Height
-
6
))
Me
.Text
=
"Youpi quel beau popup !"
Me
.OpenPopup
(
)
End
Sub
Public
Sub
HidePopup
(
)
Me
.ClosePopup
(
)
End
Sub
Private
Sub
SetLocation
(
)
Dim
rParent As
Rectangle =
_
Me
.cControlParent.RectangleToScreen
(
Me
.cControlParent.ClientRectangle
)
Dim
ptLocation As
New
Point
(
_
rParent.X
+
rParent.Width
+
10
, _
rParent.Y
+
rParent.Height
+
10
)
Me
.Location
=
ptLocation
End
Sub
Private
cColor1 As
Color =
Color.Transparent
Private
cColor2 As
Color =
Color.Red
Protected
Overrides
Sub
OnClick
(
_
ByVal
e As
EventArgs)
MyBase
.OnClick
(
e)
Dim
f As
Form =
cControlParent.FindForm
If
f Is
Nothing
Then
Exit
Sub
Dim
c As
Color =
f.BackColor
If
c =
cColor1 Or
cColor1 =
Color.Transparent
Then
cColor1 =
f.BackColor
f.BackColor
=
cColor2
Else
f.BackColor
=
cColor1
End
If
End
Sub
End
Class
La source complète de l'exemple est disponible ici : UltraBasicPopupWithPreFilter.zip
VII. Remerciements▲
Merci à nicopyright(c) pour ses pistes et réponses éclairées ainsi qu'à SaumonAgile et Aspic pour leur avis sur cet article, et enfin à comtois pour sa relecture avisée.
Et merci à ceux qui ont pris la peine d'aller au bout de cet article, en espérant que celui-ci a répondu à leur attente.