Tutorial 9: Child Window Controls

Dans ce tutorial, nous allons voir les 'Child Window Controls' qui sont, pour nos programmes, des mécanismes d'entrée - sortie très importants.

Downloadez le fichier en example ici.

Theorie:

Windows fournit plusieurs classes de fenêtre prédéterminées que nous pouvons aisément employer dans nos propres programmes. La plupart du temps nous les employons, comme par exemple les composants d'une boîte de dialogue. Donc on les appelle généralement les 'Child Window Controls'. Les 'Child Window Controls' traitent leurs propres messages de souris et de clavier et en informent la fenêtre parente quand leurs états ont étés changés. Ils soulagent les programmeurs d'un fardeau énorme, donc vous devez essayer de les employer autant que possible. Dans ce tutorial, je les ai mis sur une fenêtre normale juste pour voir comment vous pouvez les créer et les employer mais en réalité vous devez les mettre dans une boîte de dialogue.
Les exemples de classes de fenêtre prédéterminées sont le bouton, la liste, la boîte à cocher, le bouton radio, d'édition etc.
Pour utiliser un 'Child Window Control', vous devez d'abord le créer avec 'CreateWindow' ou 'CreateWindowEx'. Notez que vous ne devez pas enregistrer la classe de fenêtre puisque Windows l'enregistrera pour vous. Le paramètre 'class name' DOIT ÊTRE le nom de la classe prédéterminée. Quand vous voulez créer un bouton, vous devez mettre "BUTTON" comme nom de classe dans CreateWindowEx. Les autres paramètres que vous devez remplir sont l'handle de la fenêtre parente' et le contrôle ID. Le contrôle ID doit être unique parmi les commandes. Le contrôle ID est le n° d'ID de ce contrôle. Vous l'employez pour le différencier des autres commandes.
Après que ce contrôle ait été créé, des messages sont envoyés pour avertir que l'état de la fenêtre parente a changé. Normalement, vous créez les child window grâce au message WM_CREATE de la fenêtre parente. La fenêtre enfant (child window) envoie le message WM_COMMAND à la fenêtre parente, dont son contrôle ID dans le mot de poids faible du paramètre wParam, son code dans le mot de poids fort de wParam et son 'Window Handle' dans lParam. Chaque 'Child Window Control' a des codes différents, à attribuer à l'instruction qui fait référence à votre API Win32 pour plus d'information.
La fenêtre parente peut envoyer des commandes aux '' aussi, en faisant appel à la fonction SendMessage. La fonction SendMessage envoie donc le message en question en l'accompagnant en plus des valeurs de wParam et lParam vers la child windows ciblée par son 'Window Handle'. C'est une fonction extrêmement utile puisque elle peut envoyer des messages à n'importe quelle fenêtre pourvu que vous sachiez son 'Window Handle' (Window Handle = le n° qui identifie une fenêtre ou bien un objet quelconque comme par exemple un 'champs de saisie de texte' ou bien un 'bouton').
Ainsi, après la création des child windows, la fenêtre parente doit traiter des messages WM_COMMAND pour être capable de recevoir les codes de notification des fenêtres enfant (child windows).

Example:

Nous allons créer une fenêtre qui contient une boîte d'édition (boîte de saisie de texte) et un bouton. Quand vous cliquez sur le bouton, une (MessageBox) boîte de message apparaîtra montrant le texte vous avez tapé la boîte d'édition. Il y a aussi un menu avec 4 articles de menu :
  1. Say Hello  -- Met une chaîne de caractères dans la boîte d'édition
  2. Clear Edit Box -- Efface le contenu de la boîte d'édition
  3. Get Text -- Affiche une MessageBox avec le texte de la boîte d'édition
  4. Exit -- Ferme le programme.
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

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

.data
ClassName db "SimpleWinClass",0
AppName  db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?)                    ; Buffer pour stocker le texte qu'on inscrit dans la boîte d'édition.

.const
ButtonID equ 1                                ; The control ID of the button control
EditID equ 2                                    ; The control ID of the edit control
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3
IDM_EXIT equ 4

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke GetCommandLine
    mov CommandLine,eax

    invoke WinMain, hInstance,NULL,CommandLine, 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_BTNFACE+1
    mov   wc.lpszMenuName,OFFSET MenuName
    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,ADDR ClassName, \
                        ADDR AppName, WS_OVERLAPPEDWINDOW,\
                        CW_USEDEFAULT, CW_USEDEFAULT,\
                        300,200,NULL,NULL, hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    .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 hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_CREATE
        invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
                        WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
                        ES_AUTOHSCROLL,\
                        50,35,200,25,hWnd,8,hInstance,NULL
        mov  hwndEdit,eax
        invoke SetFocus, hwndEdit
        invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
                        WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
                        75,70,140,25,hWnd,ButtonID,hInstance,NULL
        mov  hwndButton,eax
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF lParam==0
            .IF ax==IDM_HELLO
                invoke SetWindowText,hwndEdit,ADDR TestString
            .ELSEIF ax==IDM_CLEAR
                invoke SetWindowText,hwndEdit,NULL
            .ELSEIF  ax==IDM_GETTEXT
                invoke GetWindowText,hwndEdit,ADDR buffer,512
                invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
            .ELSE
                invoke DestroyWindow,hWnd
            .ENDIF
        .ELSE
            .IF ax==ButtonID
                shr eax,16
                .IF ax==BN_CLICKED
                    invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
                .ENDIF
            .ENDIF
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
     xor    eax,eax
    ret
WndProc endp
end start

Analyse:

On va analyser le programme. Nous créons les commandes pendant le traitement du message WM_CREATE. Nous appelons CreateWindowEx avec un style de fenêtre supplémentaire, WS_EX_CLIENTEDGE. Le nom de chaque contrôle est prédéterminé. On a "EDITION" pour le contrôle de la zone d'édition (ou boîte de saisie de texte), "BUTTON" pour le contrôle du bouton. Ensuite nous spécifions le style de chaque 'Child Window' (le style de chacun des objets qui constituent notre fenêtre. Comme le style du bouton par exemple). Chaque contrôle a des styles supplémentaires en plus des styles de normaux. Par exemple, le style pour le bouton a le préfixe BS_" pour "Button Style", le style de la zone d'édition a pour préfixe "ES _" pour "Edit Style". Notez que vous mettez un contrôle ID à la place de l' Handle du menu. Ceci ne causera aucun problème puisque un (Chlid Window Control) contrôle de fenêtre d'enfant ne peut pas avoir de menu.
Après la création de chaque contrôle, nous obtenons en retour son handle dans une variable pour une future utilisation.
La fonction 'SetFocus' est appelée pour définir le milieu de la boîte d'édition, comme ça l'utilisateur pourra taper son texte tout de suite après l'appel de cette fonction.
Vient maintenant la partie vraiment passionnante. Chaque 'Child Window Control' envoie ses notifications (indications) à sa 'Parent Window' (sa fenêtre parente) avec WM_COMMAND.

    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF lParam==0

Rappelez-vous bien qu'un menu envoie lui aussi des messages WM_COMMAND vers la fenêtre parente pour l'informer de son propre état. Comment pouvez-vous faire la différence entre des messages WM_COMMAND, émis par un menu (Child Window) ou par un contrôle (effectué par la Parent Window) ? Voici ci-dessous la réponse :
 

Low word of wParam High word of wParam lParam
Menu Menu ID 0 0
Control Control ID Notification code Child Window Handle

Vous pouvez voir que vous devez vérifier lParam. Si c'est zéro, l'actuel message WM_COMMAND provient d'un menu. Vous ne pouvez pas employer wParam pour faire la différence entre un menu et un contrôle, puisque le 'menu ID' et 'control ID' peuvent être identique et le code de notification peut être zéro.

            .IF ax==IDM_HELLO
                invoke SetWindowText,hwndEdit,ADDR TestString
            .ELSEIF ax==IDM_CLEAR
                invoke SetWindowText,hwndEdit,NULL
            .ELSEIF  ax==IDM_GETTEXT
                invoke GetWindowText,hwndEdit,ADDR buffer,512
                invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

Vous pouvez écrire une chaîne de caractère (du texte) dans la boîte d'édition en appelant la fonction SetWindowText. Vous pouvez aussi effacez le contenu de cette boîte d'édition en appelant SetWindowText avec le NULL. SetWindowText une fonction API d'usage flexible. Vous pouvez employer SetWindowText pour modifier du titre dans une fenêtre ou pour changer le texte inscrit sur un bouton.
Pour importer du texte dans une boîte d'édition, vous employez GetWindowText. (Comprenez seulement que, dans ce cas on écrit pas manuellement le texte dans la boîte d'édition (de saisie), mais on le fait s'inscrire dans la zone de saisie comme on aurait pu faire s'inscrire un texte dans une MessageBox).

            .IF ax==ButtonID
                shr eax,16
                .IF ax==BN_CLICKED
                    invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
                .ENDIF
            .ENDIF

Ce petit bout de code réagit à la seule condition que l'utilisateur presse le bouton. D'abord, il vérifie le mot bas (ou de poids faible) de wParam pour voir si le 'control ID' correspond à celui du bouton. S'il l'est, il vérifie le haut mot (ou de poids fort) de wParam pour savoir si on a bien l'indication BN_CLICKED, qui est envoyée quand le bouton est cliqué.
La partie qui est intéressante, c'est après qu'on soit certain que le code de notification soit BN_CLICKED. Nous voulons récupérer le texte qu'on vient d'écrire dans la boîte d'édition et le montrer dans une boîte de message (une MessageBox). Nous pourrions faire un double du code de la section IDM_GETTEXT ci-dessus mais ça n'a pas de sens. Si d'une façon ou d'une autre nous pouvions envoyer un message WM_COMMAND avec le mot bas de wParam, alors la valeur contenue dans IDM_GETTEXT serait transmise à notre propre procédure de fenêtre, nous pouvons donc éviter la duplication de ce code et simplifier notre programme. La solution, c'est la fonction SendMessage. Cette fonction envoie n'importe quel message à n'importe quelle fenêtre avec n'importe quel wParam et lParam. Ainsi au lieu de recopier du code, nous appelons SendMessage avec : 1- l'Handle de la fenêtre parente, 2- WM_COMMAND, 3- IDM_GETTEXT et 4- 0. Ceci a le même effet que de sélectionner le menu Item "Get Text" (un de nos sous-menu) du menu. La procédure de fenêtre ne voit aucune différence entre les deux. (La procédure de fenêtre = la procédure qui s'occupe du fonctionnement de notre fenêtre, comme la détection d'un appui sur un bouton ou bien une inscription de texte dans une zone de saisie)
Vous devez essayer d'employer cette technique le plus possible pour organiser votre code au mieux.
Dernier point mais non le moindre, n'oubliez pas d'utiliser la fonction TranslateMessage à l'intérieur de la boucle de message. Puisque vous devez taper du texte dans la boîte d'édition, votre programme doit traduire des entrées clavier brutes en un texte lisible. Si vous omettez cette fonction, vous ne serez incapables de taper quoique ce soit dans votre boîte d'édition.


[Iczelion's Win32 Assembly HomePage]


Traduit par Morgatte