Tutorial 22: Superclassification
(Superclassing)

Dans ce Tutorial, nous allons voir, ce qu'est la superclassification et à quoi ça sert. Vous allez aussi apprendre à munir vos 'Controls' de la fonction de la touche TAB. (vous savez ! en appuyant sur TAB on met en sélection les boutons ou le contrôle d'édition… les uns après les autres)
Downloadez l'exemple ici

Théorie:

Dans votre carrière de programmeur, vous avez sûrement déjà rencontré une situation où vous avez besoin de plusieurs 'Controls' avec un comportement *peu* différent. Par exemple, vous pouvez avoir besoin de 10 'contrôles d'édition' qui acceptent seulement des nombres. Il y a plusieurs façons de réaliser ceci : La première méthode est trop ennuyeuse. On doit réaliser chaque fonctionnalité du contrôle d'édition par nous-même. Il vaut mieux laisser tomber ça. La deuxième méthode est meilleure que la première, mais c'est encore un travail de longue haleine. C'est bon si vous sous-classez uniquement quelques 'controls' mais ce sera un véritable cauchemar si vous sous-classer plus d'une douzaine de 'Controls'. La superclassification est la technique que vous devez employer pour cette occasion.
La sousclassification est la méthode qu'on doit utiliser pour *prendre le contrôle* d'une classe de fenêtre particulière. Par *la prise de contrôle*, je veux dire que vous pouvez modifier les propriétés de la (Window Class) classe de fenêtre pour la modeler à votre convenance, ainsi on crée une série de 'Controls'.
Les étapes de la superclassification sont décrites ci-dessous : La superclassification, c'est mieux que la sousclassification si vous souhaitez créer pas mal de 'Controls' ayant les mêmes caractéristiques.

Exemple:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WM_SUPERCLASS equ WM_USER+5
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName db "SuperclassWinClass",0
AppName    db "Superclassing Demo",0
EditClass db "EDIT",0
OurClass db "SUPEREDITCLASS",0
Message db "You pressed the Enter key in the text box!",0

.data?
hInstance dd ?
hwndEdit dd 6 dup(?)
OldWndProc dd ?

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov wc.cbSize,SIZEOF WNDCLASSEX
    mov wc.style, CS_HREDRAW or CS_VREDRAW
    mov wc.lpfnWndProc, OFFSET WndProc
    mov wc.cbClsExtra,NULL
    mov wc.cbWndExtra,NULL
    push hInst
    pop wc.hInstance
    mov wc.hbrBackground,COLOR_APPWORKSPACE
    mov wc.lpszMenuName,NULL
    mov wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov wc.hIcon,eax
    mov wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,WS_EX_CLIENTEDGE+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR AppName,\
        WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
           CW_USEDEFAULT,350,220,NULL,NULL,\
           hInst,NULL
    mov hwnd,eax

    .while TRUE
        invoke GetMessage, ADDR msg,NULL,0,0
        .BREAK .IF (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .endw
     mov eax,msg.wParam
    ret
WinMain endp

WndProc proc uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL wc:WNDCLASSEX
    .if uMsg==WM_CREATE
        mov wc.cbSize,sizeof WNDCLASSEX
        invoke GetClassInfoEx,NULL,addr EditClass,addr wc
        push wc.lpfnWndProc
        pop OldWndProc
        mov wc.lpfnWndProc, OFFSET EditWndProc
        push hInstance
        pop wc.hInstance
        mov wc.lpszClassName,OFFSET OurClass
        invoke RegisterClassEx, addr wc
        xor ebx,ebx
        mov edi,20
        .while ebx<6
            invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
                 WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
                 edi,300,25,hWnd,ebx,\
                 hInstance,NULL
            mov dword ptr [hwndEdit+4*ebx],eax
            add edi,25
            inc ebx
        .endw
        invoke SetFocus,hwndEdit
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    .if uMsg==WM_CHAR
        mov eax,wParam
        .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
            .if al>="a" && al<="f"
               sub al,20h
            .endif
            invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
            ret
        .endif
    .elseif uMsg==WM_KEYDOWN
        mov eax,wParam
        .if al==VK_RETURN
            invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
            invoke SetFocus,hEdit
        .elseif al==VK_TAB
            invoke GetKeyState,VK_SHIFT
            test eax,80000000
            .if ZERO?
                invoke GetWindow,hEdit,GW_HWNDNEXT
                .if eax==NULL
                    invoke GetWindow,hEdit,GW_HWNDFIRST
                .endif
            .else
                invoke GetWindow,hEdit,GW_HWNDPREV
                .if eax==NULL
                    invoke GetWindow,hEdit,GW_HWNDLAST
                .endif
            .endif
            invoke SetFocus,eax
            xor eax,eax
            ret
        .else
            invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
            ret
        .endif
    .else
        invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
EditWndProc endp
end start
 

Analyse:

Le programme créera une simple fenêtre avec 6 contrôles d'édition "retouchés" dans son secteur client. Les contrôles d'édition accepteront uniquement des chiffres hexdécimaux. En réalité, j'ai modifié l'exemple de 'Sousclassification' pour le transformer en superclassification. Le début du programme reste le même mais la partie intéressante c'est quand on crée la fenêtre principale :

    .if uMsg==WM_CREATE
         mov wc.cbSize,sizeof WNDCLASSEX
        invoke GetClassInfoEx,NULL,addr EditClass,addr wc

Nous devons d'abord remplir la structure de WNDCLASSEX avec les données de class que nous souhaitons imposer comme superclasse, dans ce cas, c'est (l'Edit Class) la classe D'ÉDITION. Rappelez-vous aussi que vous devez mettre le membre cbSize (de la structure WNDCLASSEX) avant que vous n'appeliez GetClassInfoEx, sinon la structure WNDCLASSEX ne sera pas correctement remplie. Après les retours de GetClassInfoEx, wc est rempli de toute l'information que nous avons besoin pour créer une nouvelle 'Window Class'.

        push wc.lpfnWndProc
        pop OldWndProc
        mov wc.lpfnWndProc, OFFSET EditWndProc
        push hInstance
        pop wc.hInstance
        mov wc.lpszClassName,OFFSET OurClass

Maintenant nous devons modifier certains membres de wc. Le premier c'est le pointer de la procédure de fenêtre. Puisque nous avons besoin d'enchaîner notre propre procédure de fenêtre avec l'originale, nous devons la sauvegarder dans une variable, c'est pourquoi on l'appelle avec CallWindowProc. Cette technique est identique à la sousclassification sauf que vous modifiez directement la structure WNDCLASSEX sans avoir besoin d'appeler SetWindowLong. Les deux membres suivants doivent aussi être modifiés, sinon vous serez dans l'incapacité d'enregistrer votre nouvelle 'Window Classe',idem pour ce qui concerne hInstance et lpsClassName. Vous devez remplacer l'hInstance original par l'hInstance de votre propre programme. Et vous devez choisir un nouveau nom pour la nouvelle 'class'.

        invoke RegisterClassEx, addr wc

Quand tout est enfin prêt, on registre la nouvelle classe. on obtient une nouvelle class avec quand même certaines caractéristiques de la vieille class.

        xor ebx,ebx
        mov edi,20
        .while ebx<6
            invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
                 WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
                 edi,300,25,hWnd,ebx,\
                 hInstance,NULL
            mov dword ptr [hwndEdit+4*ebx],eax
            add edi,25
            inc ebx
        .endw
        invoke SetFocus,hwndEdit

Maintenant que nous avons enregistré la class, nous pouvons y créer des fenêtres de base. Dans le susdit petit bout de code, j'emploie ebx en tant que compteur de nombre de fenêtres créées. edi est employé comme coordonnée 'Y' du coin gauche supérieur de la fenêtre. Quand une fenêtre est créée, son handle est stocké dans un tableau (informations de type dword). Quand toutes les fenêtres sont créées, le centre d'entrée est sur la première fenêtre. (le centre d'entrée = ex : un bouton est encadré de pointillé où un contrôle d'édition est activée [curseur présent], alors c'est que le centre d'entrée est sur lui.)
À ce niveau, on a 6 contrôles d'édition qui acceptent uniquement des chiffres hexadécimaux. La procédure de fenêtre de substitution manipule les filtres (donc pas de caractère autres que 0-9 ou a-f). En réalité, c'est la même chose que pour la procédure de fenêtre de l'exemple de sousclassification. Donc, comme vous pouvez le voir, vous n'avez pas besoin de refaire le travail de sousclassification pour chacun des contrôles d'édition.

Pour rendre cet exemple encore plus intéressant, je rajoute un petit bout de code qui manipule le contrôle de navigation grâce à la touche TAB. Normalement, si vous mettez des 'Controls' dans une boîte de dialogue, le manager de boîte de dialogue gère les touches de navigation pour vous. De cette manière vous pouvez grâce à la touche TAB passer au contrôle suivant ou bien avec Shift-Tab revenir au contrôle précédent. Hélas, cette particularité n'est pas disponible si vous placez vos 'Controls' dans une simple fenêtre (au lieu d'une Dialog Box). Vous devez les sous-classer, comme ça vous pourrez gérer les touches Tab et Shift-Tab par vous-même. Dans notre exemple, nous n'avons pas besoin de sous-classer les 'Controls' l'un après l'autre vu qu'on les a déjà superclassés, donc nous pouvons leur fournir "un manager de navigation de contrôle" commun.
 

        .elseif al==VK_TAB
            invoke GetKeyState,VK_SHIFT
            test eax,80000000
            .if ZERO?
                invoke GetWindow,hEdit,GW_HWNDNEXT
                .if eax==NULL
                    invoke GetWindow,hEdit,GW_HWNDFIRST
                .endif
            .else
                invoke GetWindow,hEdit,GW_HWNDPREV
                .if eax==NULL
                    invoke GetWindow,hEdit,GW_HWNDLAST
                .endif
            .endif
            invoke SetFocus,eax
            xor eax,eax
            ret

Le susdit petit bout de code est la procédure EditWndClass. Il vérifie si l'utilisateur appuie sur la touche TAB, si c'est le cas, on appelle GetKeyState pour vérifier si la TOUCHE DES MAJUSCULES (SHIFT) est aussi pressée. GetKeyState renvoie une valeur dans eax qui détermine si la touche indiquée est pressée ou non. Si la touche est appuyée, le Bit de poids fort de eax est rempli. Si 'SHIFT' n'est pas pressé, on laisse ce Bit à 0. Donc on compare la valeur de retour avec 80000000. Si le Bit de poids fort est à 8, ça signifie que l'utilisateur a appuyé sur shift+tab, que nous devons gérer séparément.
Un Dword , donc 32 bit donc (0000 0000 0000 0000 0000 0000 0000 0000)h on met sa partie haute à 1 (1111 0000 0000 0000 0000 0000 0000 0000)h =(8 0 0 0 0 0 0 0)d, car Masm manipule des décimaux par défaut.

Si l'utilisateur appuie seulement sur la touche TAB, nous appelons GetWindow pour retrouver l'handle du contrôle suivant. Nous utilisons le flag GW_HWNDNEXT pour dire à GetWindow de récupérer l'handle de la fenêtre qui suit la ligne du hEdit courant. Si cette fonction renvoie un NULL, on ne le considère pas comme un handle, ainsi le courant hEdit est le dernier contrôle. Nous "revenons" donc au premier contrôle en appelant GetWindow avec son flag GW_HWNDFIRST. C'est la même chose pour la configuration SHIFT-TAB que pour la touche TAB, sauf qu'on inverse tout.


[Iczelion's Win32 Assembly Homepage]
Traduit par Morgatte