Assembleur 64 avec le compilateur nasm
Vous souhaitez réagir à ce message ? Créez un compte en quelques clics ou connectez-vous pour continuer.

Une boite de dialogue personnalisée

Aller en bas

Une boite de dialogue personnalisée Empty Une boite de dialogue personnalisée

Message par Admin Ven 9 Déc - 15:54

Il existe d'autres boites de dialogue spécialisées (mais que je n'ai pas encore utilisées ) comme une boite de recherche dans du texte, mais maintenant nous allons voir la boite de dialogue où c'est vous qui définissez toutes les zones necessaires et gerez les évenements qui peuvent les toucher. Dans la terminologie windows, les zones sont appelées contrôles et dans l'API windows, vous trouverez des fonctions pour créer ces contrôles à partir d'un fichier des ressources. Comme avec nasm, nous n'utilisons pas de fichiers ressources, nous allons utiliser une fonction qui crée la boite de dialogue à partir de la description en mémoire.
Dans le programme ci dessous, nous commençons par décrire tous les textes qui seront affichés dans la boite : libelles statiques, titre de la boite et de chaque bouton avec une instruction pour calculer leur longueur. Ceci est necessaire car curieusement tous les textes de la boite doivent lui être passés en caractères UNICODE (sauf les textes des zones de saisies). Il nous faut donc convertir ces textes d'ANSI en UNICODE.
Ensuite nous trouvons la description de l'entete de la boite de dialogue. C'est une structure de type DLGTEMPLATE plus quelques données complémentaires. Nous y trouvons les constantes definissant le style de la fenêtre : visible avec bordure, modale avec la barre système standard.
Puis nous avons le nombre de controles que va contenir la boite : cad les zones textes, les boutons, les zones de saisies et les listbox. Ici nous avons 5 controles.
Puis nous trouvons les données qui positionnent la boite sur l'écran et sa taille.Dans les données complémentaires, nous trouvons une zone pour indiquer la présence de menu et une identification de la classe (utilisation à approfondir un jour !!) puis le titre de la boite de dialogue en caractères unicode. C'est pourquoi sa longueur est le double de la longueur du texte proprement dit.
Après cela nous allons trouver la description des 5 contrôles qui doivent être alignés sur une frontière de double mots.
La description commence par une structure de type DLGITEMTEMPLATE qui donne le style du contrôle : enfant, visible, accessible par la touche <TAB> ou bouton par défaut etc. puis son positionnement à l’intérieur de la boite et enfin l'identification du contôle : cette zone est importante car elle sera véhiculée à l’intérieur des messages et nous servira à determiner quelle action entreprendre sur ce contrôle.
Ensuite nous trouvons une zone qui sera toujours à FFFF et une zone qui donne le type du controle (80 pour un bouton,81 pour une zone de saisie, 82 pour du texte statique 83 pour une list box) puis le libellè du contrôle pour celle qui en ont un. Il y a aussi une zone finale et quelquefois des caractères d'ajout pour assurer l'alignement des structures. En effet comme tenu de la longueur de la zone titre qui peut être variable, il n'est pas facile de déterminer exactement la longueur de chaque définition d'un contrôle et si l'alignement n'est pas réalisé, nous avons un crash du programme. Donc par expérience, lors de la mise au point, je commence par mettre 0 contrôle dans la boite puis 1 puis 2 et en ajustant ce nombre de caractères complémentaire. Il y a peut être mieux à faire Shocked
Au niveau du code, nous commençons par récupérer le handle de notre programme par la fonction GetModuleHandleA puis nous convertissons chaque libellé d'ANSI en UNICODE par la fonction MultiByteToWideChar. Nous passons en paramètre la zone d'entree, la zone de sortie la longueur et les paramètres de conversion. Comme il y a plus de 4 paramètres, il faut gérer l'alignement et la réservation sur la pile.
Maintenant nous allons creer notre boite de dialogue par la fonction CreateDialogIndirectParamA. Nous commençons par lui passer en paramètre un texte qui servira lors de l'initialisation, le handle du programme, l'adresse du début de la structure d'entête  de la boite et l'adresse de la procèdure qui va gérer les évènements.Ici elle s’appelle Proc_dialogue et il faudra la créer dans la suite du programme.
Remarque : nous ne passons à cette fonction que l'adresse de l'entête et donc c'est la fonction qui à partir du nombre de contrôles spécifiés dans cette entête, va retrouver les informations de chacun. Vous comprenez pourquoi les structures doivent être alignées correctement.
Après la création de la boite, nous devons l'afficher par la fonction ShowWindow (remarque la documentation précise que cette fonction est inutile si on a indiqué VISIBLE dans le style de la boite, mais dans mon cas, cela ne fonctionne pas).
Puis nous trouvons une boucle de traitement des messages. Nous retrouverons ce mécanisme pour la création des fenêtres. En fait, chaque controle et la boite de dialogue vont à une action de l'utilisateur envoyer un message. Tous ces messages vont être analysés et transmis à la procédure gestionnaire. Lorsque le message PostQuitMessage sera envoyé à la fermeture de la boite, le registre rax sera à zero et la boucle se terminera. Ici nous avons la fonction GetMessageA pour la récupèration et la fonction IsDialogMessageA pour l'analyse et l'envoi à la procédure.
Voyons le contenu de la fonction Proc_dialogue. Contrairement aux autres fonctions de l'API que nous avons vu jusqu'à maintenant, celle -ci sera écrite par nos soins et appelée par windows pour traiter les messages qui nous sont destinés. Et windows passera les paramètres nécessaires dans les 4 registres rcx,rdx,r8 et r9.
Pour assurer la compatiblité, nous recopions ces 4 paramètres dans les zones réservées de la pile (à creuser un peu plus pour voir l'utilité).
Ensuite nous testons la zone uMsg qui contient le type de message reçu. Ici nous allons traiter les messages WM_INITDIALOG WM_DESTROY,WM_CLOSE et WM_COMMAND.
Le message WM_INITDIALOG est envoyé lors de la création de la boite de dialogue et permet d'initialiser certaines zones. Comme exemple, nous récuperons l'adresse du texte passé en premier paramètre de la fonction CreateDialogIndirectParamA. Celle ci est contenu dans le paramètre lParam. Et nous envoyons le texte dans la zone de saisie de la boite par la fonction SendDlgItemMessageA gràce à son identifiant ID_EDIT1.

Les messages WM_DESTROY et WM_CLOSE ne feront que fermer la boite de dialogue par la fonction EndDialog et d'envoyer le message de fin par PostQuitMessage.

Le message WM_COMMAND necessite d'abord de connaitre quel bouton a été appuyé. Nous testons d'abord l'identifiant IDOK pour le 1er bouton puis IDCLOSEpour le 2ième et IDCANCEL au cas où l'utilisateur aurait utiliser la touche <ESC>.
Dans le cas du bouton OK, nous récuperons dans un buffer le texte de la zone de saisie par la fonction GetDlgItemTextA, nous le convertissons en majuscule par CharUpperA et nous l'ajoutons à une ligne du contrôle LISTBOX par la fonction SendDlgItemMessageA mais avec le paramètre LB_ADDSTRING. Ainsi à chaque saisie d'un texte dans la zone de saisie, et appuie sur le bouton OK, le texte sera mis en majuscule et ajouté aux lignes de la listBox. Vous remarquerez que au delà de la taille de la listbox, un ascenseur apparait automatiquement : c'est pas beau ça !!!!
Code:

;---programme d'utilisation d'une boite de dialogue  64 bits
;BoiteDialogue1
; les routines de vidage et d'affichage sont déportées dans le fichier routineswin.obj
%include "../windowsinc64.inc"
;=======================================
; segment des données initialisées
;=======================================
segment .data

szMessFin  db "Fin programme",0
szRetourLigne db 10,13,0
szTexteBD  db "abcde",0  ; pour initialiser la zone de saisie

;************************************************************
;identification des controles de la boite
ID_EDIT1    equ 200  ; zone de saisie
IDD_LB1      EQU   201  ; list box
ID_TXT1      EQU   202  ; texte simple
;************************************************************
;libellés des textes à afficher dans la Boite de Dialogue
szTitreDialog db "DIALOGUE1",0
ILGTD  equ $ - szTitreDialog  ;et leur longueur
szTitreBouton db "OK",0
ILGTB1  equ $ - szTitreBouton
szTitreBouton2 db "FIN",0
ILGTB2  equ $ - szTitreBouton2
szTitreTexte db "Saississez un texte :",0
ILGTT1  equ $ - szTitreTexte
;************************************************************
;Description de la boite de dialogue
;entête des données générales
lpdta:  align 4              ; Important
lpdt: istruc DLGTEMPLATE
  at DLGTEMPLATE.style, dd  WS_POPUP | WS_BORDER | WS_SYSMENU | WS_CAPTION    | DS_MODALFRAME |  WS_VISIBLE
  at DLGTEMPLATE.cdit, dw 5  ; nombre de controles
  at DLGTEMPLATE.x, dw 10    ; position du coin à gauche  de la boite de dialogue
  at DLGTEMPLATE.y, dw 10    ; et en haut
  at DLGTEMPLATE.cx, dw 300  ; largeur de la boite
  at DLGTEMPLATE.cy, dw 200  ; hauteur de la boite
iend 
lpdtmenu  dw 0  ; pas de menu
lpdtclass  dw 0 
szTitreD  Times (ILGTD * 2) db 0 ; titre de la fenêtre mais hélas en caractères Unicode

;description du premier controle (attention ce n'est peut être pas le premier de l'écran)
lpditC1a:  align 4            ; Important
lpditC1:  istruc DLGITEMTEMPLATE
   at DLGITEMTEMPLATE.style, dd WS_CHILD | WS_VISIBLE  | BS_DEFPUSHBUTTON | WS_TABSTOP
   at DLGITEMTEMPLATE.dwExtendedStyle, dd WS_EX_NOPARENTNOTIFY
   at DLGITEMTEMPLATE.x, dw 10  ;position à gauche du controle dans la boite
   at DLGITEMTEMPLATE.y, dw 100  ; position en haut du controle dans la boite
   at DLGITEMTEMPLATE.cx, dw 80  ; largeur du controle
   at DLGITEMTEMPLATE.cy, dw 15  ; hauteur du controle
   at DLGITEMTEMPLATE.id, dw IDOK  ; identification
iend
sysClassC1  dw 0xFFFF    ; toujours cette valeur
idClassC1  dw 0x0080    ; c'est un bouton
szTitreC1  Times (ILGTB1 * 2) db 0  ; libellé en caractères unicode
cbCreationDataC1 dw 0  ; à voir
;description du 2ième controle 
lpditC2a:  align 4
lpditC2:  istruc DLGITEMTEMPLATE
   at DLGITEMTEMPLATE.style, dd ES_LEFT | WS_BORDER |  WS_CHILD |WS_TABSTOP| WS_VISIBLE
   at DLGITEMTEMPLATE.x, dw 10
   at DLGITEMTEMPLATE.y, dw 50
   at DLGITEMTEMPLATE.cx, dw 80
   at DLGITEMTEMPLATE.cy, dw 15
   at DLGITEMTEMPLATE.id, dw ID_EDIT1  ; identification
iend
sysClassC2  dw 0xFFFF
idClassC2  dw 0x0081  ; c'est une zone de saisie
            db 0      ; pas de libellé
cbCreationDataC2 dw 0
szfinC2 db 0
;description du 3ieme controle
lpditC3a:  align 4
lpditC3:  istruc DLGITEMTEMPLATE
   at DLGITEMTEMPLATE.style, dd LBS_NOTIFY  | WS_VISIBLE | WS_BORDER | WS_VSCROLL  | WS_TABSTOP
   at DLGITEMTEMPLATE.x, dw 10
   at DLGITEMTEMPLATE.y, dw 120
   at DLGITEMTEMPLATE.cx, dw 63
   at DLGITEMTEMPLATE.cy, dw 42
   at DLGITEMTEMPLATE.id, dw IDD_LB1  ; identification
iend
sysClassC3  dw 0xFFFF
idClassC3  dw 0x0083  ; c'est une list box
szTitreC3  dw 0    ; pas de libellé
cbCreationDataC3 dw 0
;description du 4ième controle
lpditC4a:  align 4
lpditC4:  istruc DLGITEMTEMPLATE
   at DLGITEMTEMPLATE.style, dd  WS_VISIBLE    ; on peut ajouter  WS_BORDER  si on veut l'entourer
   at DLGITEMTEMPLATE.x, dw 10                ; d'une bordure
   at DLGITEMTEMPLATE.y, dw 10
   at DLGITEMTEMPLATE.cx, dw 100
   at DLGITEMTEMPLATE.cy, dw 20
   at DLGITEMTEMPLATE.id, dw ID_TXT1 ; identification
iend
sysClassC4  dw 0xFFFF
idClassC4  dw 0x0082  ; c'est du texte statique
szTitreC4  Times (ILGTT1 * 2) db 0
cbCreationDataC4 dw 0
lpditC5a:  align 4
lpditC5:  istruc DLGITEMTEMPLATE
   at DLGITEMTEMPLATE.style, dd WS_CHILD | WS_VISIBLE | WS_TABSTOP
   at DLGITEMTEMPLATE.dwExtendedStyle, dd WS_EX_NOPARENTNOTIFY
   at DLGITEMTEMPLATE.x, dw 10
   at DLGITEMTEMPLATE.y, dw 170
   at DLGITEMTEMPLATE.cx, dw 80
   at DLGITEMTEMPLATE.cy, dw 15
   at DLGITEMTEMPLATE.id, dw IDCLOSE  ; identification
iend
sysClassC5  dw 0xFFFF    ; toujours cette valeur
idClassC5  dw 0x0080    ; c'est un bouton
szTitreC5  Times (ILGTB2 * 2) db 0  ; libellé en caractères unicode
cbCreationDataC5 dw 0  ; à voir
;
szfinb db 0,0,0,0,0,0    ; zone de sécurité si pb de longueur

;instance message
msg: istruc MSG
    at MSG.fin,  db  "<<<  "
  iend

;=======================================
; segment des données non initialisées
;=======================================
segment .bss
hInst  resq 1    ; handle du programme
hDlg    resq 1    ; handle de la boite de dialogue
ILGBUFFER equ 100
szBuffer  resb ILGBUFFER

;=======================================
; segment de code
;=======================================
segment .text
    global Main
   ; les fonctions de l'API sont dans le fichier des includes
   ; ces fonctions sont mes routines utilitaires du fichier routines64.obj
   extern afferreur,affmessageP,affconsoleP,afftoutregistreP,afftoutreg8a15P,affmemoireP,affmessage,saisieClavier
Main:
    sub rsp, 8h 
   sub rsp, 20h 
    ;recup handle de l'instance du programme
   mov rcx, NULL
   call GetModuleHandleA
   cmp  rax,NULL
   je  .gestionerreurs
    mov  [hInst],rax
   ;tous les textes sont en caractères unicode.
   ;il faut donc les convertir d'ANSI en UNICODE
   ;et donc leur taille est multiplié par deux
   add rsp,20h    ; 2 push donc pas d'alignement
   push ILGTD * 2    ; taille de la zone de reception (voir s'il ne faut pas ajouter un pour la fin)
   push szTitreD    ; zone de reception
   sub rsp, 20h 
   mov rcx,CP_ACP  ; code page ANSI par défaut
   mov rdx,0      ; flags qui indiquent les types de conversion (voir la doc )
   mov r8,szTitreDialog ; zone à convertir
   mov r9,-1  ; taille de la zone à convertir, si -1 la chaine est terminée par zero
               ; et c'est la fonction qui determinara la taille
   call MultiByteToWideChar  ;fonction de conversion ANSI ==> UNICODE
   add rsp,10h  ; pour les 2 push
   mov r9,__LINE__
   cmp eax,NULL
   je .gestionerreurs
   add rsp,20h
   push ILGTB1 * 2
   push szTitreC1
   sub rsp, 20h 
   mov rcx,CP_ACP
   mov rdx,0
   mov r8, szTitreBouton
   mov r9,-1
   call MultiByteToWideChar
   add rsp,10h  ; pour les 2 push
   mov r9,__LINE__
   cmp eax,NULL
   je .gestionerreurs
   add rsp,20h
   push ILGTT1 *2
   push szTitreC4
   sub rsp, 20h 
   mov rcx,CP_ACP
   mov rdx,0
   mov r8, szTitreTexte
   mov r9,-1
   call MultiByteToWideChar
   add rsp,10h  ; pour les 2 push
   mov r9,__LINE__
   cmp eax,NULL
   je .gestionerreurs
   add rsp,20h
   push ILGTB2 *2
   push szTitreC5
   sub rsp, 20h 
   mov rcx,CP_ACP
   mov rdx,0
   mov r8, szTitreBouton2
   mov r9,-1
   call MultiByteToWideChar
   add rsp,10h  ; pour les 2 push
   mov r9,__LINE__
   cmp eax,NULL
   je .gestionerreurs
   ;************************************
   push  __LINE__  ; routine personnelle de vidage d'une zone mémoire
   push  lpdt      ; instructions à supprimer
   push 20          ; ou à remplacer par votre propre routine de vidage
    call affmemoireP
   ;***********************************
   ; création boite de dialogue
   add rsp,20h
   sub rsp,8h
   push szTexteBD  ; passage de l'adresse du texte comme parametre à la procédure
   sub rsp,20h
   mov rcx,[hInst]  ; instance du programme à laquelle sera attachée la boite
   mov rdx,lpdt    ; adresse de début de la structure d'entête de la boite de dialogue
   mov r8,NULL   
   mov r9,Proc_Dialogue  ; procédure de gestion des évenements de la boite
   call CreateDialogIndirectParamA  ; création de la boite
   add rsp,10h
   mov r9,__LINE__
   cmp rax,NULL
   je .gestionerreurs
   mov [hDlg],rax    ; conservation du handle de la boite attribuée par windows
   ;affichage de la boite
   mov rcx,rax
   mov rdx,SW_SHOWDEFAULT
   call ShowWindow
.boucle_commande:   ; boucle standard de l'API windows
    mov rcx,msg    ; pour traiter les évenements qui arrivent à une fenêtre
   mov rdx,NULL    ; Chaque evenement envoie un message qui est analysé ici et renvoyé
   mov r8,0        ; vers la procèdure qui les traitera
   mov r9,0
   call GetMessageA    ;récuperation des messages
   cmp rax,0          ; fin de boucle : l'utilisateur a fermé la fenetre
   jz .main_fin
    mov rcx,[hDlg]
   mov rdx,msg
   call IsDialogMessageA    ; messages pour la boite de dialogue
   jmp .boucle_commande
.gestionerreurs:
   call afferreur
   mov  rax,1
.main_fin:             ; fin du prograame , ici on ne fait rien
    ;push szMessFin
   ;call affconsoleP
   ;call saisieClavier
   mov  rax,0
.fin:   
   mov rcx,rax          ; code retour
   call ExitProcess  ; fin du programme
;===================================================
;procedure boite de dialogue  ; même paramètre que fenetre
;===================================================
; va traiter les évenements de la boite dialogue ( clic, appui sur bouton, fermeture etc). 
; windows appelle cette fonction en lui passant 4 parametres dans les registres rcx,rdx,r8,r9
Proc_Dialogue:
%define hWnd rbp+16     ; pour simplifier l'ecriture dans les instructions suivantes
%define uMsg rbp+24
%define wParam rbp+32
%define lParam rbp+40
    mov [rsp+8],rcx  ; handle fenêtre  voir l'utilité est-ce bien necessaire
   mov [rsp+16],rdx  ; type de message
   mov [rsp+24],r8  ; wparam
   mov [rsp+32],r9  ; lparam
   enter 0,0        ; pour récuperer les valeurs definies plus haut
   sub rsp,20h      ; reservation standard
    mov rax,[uMsg] ; umsg contient le type de message lié à l'evenement de la boite 
   cmp rax,WM_INITDIALOG  ; initialisation de la boite
   je  .init
   cmp eax,WM_DESTROY    ; fermeture de la fenêtre
   je .close
   cmp rax,WM_CLOSE    ; fermeture de la fenetre
   je .close
   cmp rax,WM_COMMAND    ; appui sur les boutons
   je .bouton
   add rsp,20h
   leave
   mov rax,FALSE    ; IMPORTANT pour tous les autres messages non traités
   ret
;******************************Appui sur les boutons
.bouton:
    mov rax,[wParam] ; wparam
   cmp rax,IDOK      ; c'est lequel ?
   jne .autrebouton
   ;traitement du bouton OK
   mov rcx,[hWnd]    ; handle de la boite
   mov rdx,ID_EDIT1  ; identification de la zone de saisie
   mov r8,szBuffer  ; buffer de reception
   mov r9,ILGBUFFER    ; longueur maxi du buffer
   call GetDlgItemTextA; recuperation du texte de la zone saisie   
   mov r9,__LINE__ - 1
   cmp rax,0
   je .erreur

   ;pour exemple on convertit le texte saisi en majuscule
   mov rcx, szBuffer    ;buffer à convertir
   call CharUpperA      ; appel de la conversion en place
   mov r9,__LINE__ - 1
   cmp rax,0
   je .erreur
   add rsp,20h
   sub rsp,8h  ; 1 seul push donc alignement pile à faire
   push  szBuffer  ; zone à afficher
   sub rsp,20h
   mov rcx,[hWnd]    ; handle de la BdD
   mov rdx,IDD_LB1    ; identifiant de la list box
   mov r8,LB_ADDSTRING ; parametre pour ajouter la ligne dans la listbox
   mov r9,0
    call  SendDlgItemMessageA  ; pour maj de la listbox avec le texte modifié
   add rsp,10h    ; 1 push et 1 alignement
   jmp .fin
.autrebouton:   
   cmp rax,IDCLOSE  ; c'est le bouton fin donc on ferme la boutique
   je .close
   cmp rax,IDCANCEL  ;fermeture  par <ESC>
   je .close
   jmp .fin
;==========================
.init:  ; initialisation de la BdD
   add rsp,20h
   sub rsp,8h  ; 1 seul push donc alignement pile à faire
   push  qword [lParam]  ; récupération du paramétre passé à la procèdure
   sub rsp,20h
   mov rcx,[hWnd]    ; handle de la BdD
   mov rdx,ID_EDIT1    ; identifiant de la zone d'edition
   mov r8,WM_SETTEXT  ; c'est du texte
   mov r9,0
    call  SendDlgItemMessageA  ; pour maj de la zone d'edition avec le texte
   add rsp,10h    ; 1 push et 1 alignement
   jmp .fin

;==================================================
.close:
    mov rcx,[hWnd]    ; handle de la boite
   mov rdx,IDCANCEL
    call EndDialog  ; fermeture de la boite de dialogue
   mov rcx,0        ; valeur de retour
    call PostQuitMessage   ; envoie du message de fin (WM_QUIT)
   jmp .fin                ; attention, il ne faut pas l'envoyer systématiquement
.erreur:
    call afferreur   
.fin:
    add rsp,20h  ; libération place
   leave        ; pendant du enter
   mov rax,TRUE  ; IMPORTANT pour le bon fonctionnement
   ret          ;
   

Remarque : les premières mises au point d'une boite de dialogue peuvent être laborieuses. En effet il faut que les définitions des contrôles soient bonnes mais aussi que la procédure de traitement soit correcte (rax doit toujours retourner ou FALSE ou TRUE). En cas de doute, ne mettre qu'un seul controle dans la définition de la BAL puis augmenter quand le précedent est OK. Mettre aussi une procèdure de vidage en début de la procèdure de gestion pour voir quel type de message arrive et le suivre.

Nous verrons que la création de fenêtres repose aussi sur le même mécanisme de gestion de message et de procèdure de réception.
Admin
Admin
Admin

Messages : 38
Date d'inscription : 28/11/2016

https://assembleur64.kanak.fr

Revenir en haut Aller en bas

Revenir en haut

- Sujets similaires

 
Permission de ce forum:
Vous ne pouvez pas répondre aux sujets dans ce forum