Bajar el primer ejemplo aquí, el segundo ejemplo aquí.
Si juegas bastante con los ejemplos del tutorial anterior, encontrarás que no puedes cambiar el foco de entrada de un control de ventana hija a otra con la tecla Tab. La única manera de realizar eso es haciendo click sobre el control que deseas que gane el foco de entrada. Esta situación es más bien incómoda. Otra cosa que deberías notar es que cambié el color del fondo de la ventana padre a gris en vez de a blanco, como lo había hecho en los ejemplos previos. Esto se hace así para que el color de la ventana hija pueda armonizar con el color del área cliente del ventana padre. Hay otra manera de salvar este problema pero no es fácil. Tienes que subclasificar todos los controles de ventana hija en tu ventana padre.
La razón de la existencia de tal inconveniente es que los controles de ventana hija están originalmente diseñados para trabajar dentro de cajas de diálogo, no en una ventana normal. Los colores por defecto de los controles de ventanas hijas, como los botones, es gris porque el área cliente de la caja de diálogo normalmente es gris para que armonicen entre sí sin ninguna intervensión por parte del programador.
Antes de entrar en detalles, deberíamos saber qué es una caja de diálogo. Una caja de diálogo no es más que una ventana normal diseñada para trabajar con controles de ventanas hijas. Windows también proporciona un administrador interno de cajas de diálogo ["dialog box manager"] responsable por gran parte de la lógica del teclado tal como desplazamiento del foco de entrada cuando el ususario presiona Tab, presionar el botón por defecto si la tecla Enter es presionada, etc; así los programadores pueden ocuparse de tareas de más alto nivel. Las cajas de diálogo son usadas primero como dispositivos de entrada/salida. Como tal, una caja de diálogo puede ser considerada como una "caja negra" de entrada/salida lo que siginifica que no tienes que saber cómo funciona internamente una caja de diálogo para usarla, sólo tienes que saber cómo interactuar con ella. Es un principio de la programación orientada a objetos [object oriented programming (OOP)] llamado encapsulación u ocultamiento de la información. Si la caja negra es *perfectamente* diseñada , el usuario puede emplarla sin tener conocimiento de cómo funciona. Lo único es que la caja negra debe ser perfecta, algo difícil de alcanzar en el mundo real. La API de Win32 API también ha sido diseñada como una caja negra.
Bien, parece que nos hemos alejado de nuestro camino. Regresemos a nuestro tema. Las cajas de diálogo han sido diseñadas para reducir la carga de trabajo del programador. Normalmente si tienes que poner controles de ventanas hijas sobre una ventana normal, tienes que subclasificarlas y escribir tú mismo la lógica del teclado. Pero si quieres ponerlas en una caja de diálogo, Windows manejará la lógica por tí. Sólo tienes que saber cómo obtener la entrada del usuario de la caja de diálogo o como enviar órdenes a ella.
Como el menú, una caja de diálogo se define como un recurso. Escribes un plantilla describiendo las características de la caja de diálogo y sus controles y luego compilas el guión de recursos con un compilador de recursos.
Nota que todos los recursos se encuentran en el mismo archivo de guión de recursos. Puedes emplear cualquier editor de texto para escribir un guión de recursos, pero no lo recomiendo. Deberías usar un editor de recursos para hacer la tarea visualmente ya que arreglar la disposición de los controles en la caja de diálgo es una tarea dura de hacer manualmente. Hay disponibles algunos excelentes editores de recursos. Muchos de las grandes suites de compiladores incluyen sus propios editores de recursos. Puedes usar cualquiera para crear un guión de recursos para tu programa y luego cortar las líneas irrelevantes tales como las relacionadas con MFC.
Hay dos tipos principales de cajas de diálogo: modal y no-modal. Una caja de diálogo no-modal te deja cambiar de foco hacia otra ventana. Un ejempo es el diálogo Find de MS Word. Hay dos subtipos de caja de diálogo modal: modal de aplicación y modal de sistema. Una caja de diálogo modal de aplicación no permite cambiar el foco a otra ventana en la misma aplicación sino cambiar el foco de entrada a la ventana de OTRA aplicación. Una caja de diálogo modal de sistema no te permite cambiar de foco hacia otra ventana hasta que respondas a la primera.
Una caja de diálogo no-modal se crea llamando a la función de la API CreateDialogParam. Una caja de diálogo modal se crea llamando a DialogBoxParam. La única diferencia entre una caja de diálogo de no-modal y una modal de sistema es el estilo DS_SYSMODAL. Si quieres incluir el estilo DS_SYSMODAL en una plantilla de caja de diálogo, esa caja de diálogo será modal de sistema.
Puedes comunicarte
con cualquier control de ventana hija sobre una caja de diálogo usando
la función SendDlgItemMessage. Su sintaxis es:
SendDlgItemMessage proto hwndDlg:DWORD,\Esta llamada a la API es inmensamene útil para interactuar con un control de ventana hija. Por ejemplo, si quieres obtener el texto de un control de edición, puedes hacer esto:
idControl:DWORD,\
uMsg:DWORD,\
wParam:DWORD,\
lParam:DWORD
call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer
Con el fin
de saber qué mensaje enviar, deberías consultar la referencia
de la API de Win32.
Windows también
provee algunas funciones específicas de la API para controles que permiten
obtener y poner datos en los controles rápidamente, por ejemplo, GetDlgItemText,
CheckDlgButton etc. Estas funciones específicas para controles son suministradas
para conveniencia de los programadores de manera que él no tenga que
revisar el significado de wParam y lParam para cada mensaje. Normalmente, deberías
usar llamadas
a las funciones específicas de la API para controles
cada vez que sean disponibles ya que ellas facilitan el mantenimiento del código
fuente. Recurre a SendDlgItemMessage sólo si no hay disponible llamadas
a funciones
específicas de la API.
El manejador de Windows de cajas de diálogos envía varios mensajes a una función " callback" particular llamada procedimiento de caja de diálogo que tiene el siguiente formato:
DlgProc proto hDlg:DWORD ,\
iMsg:DWORD ,\
wParam:DWORD ,\
lParam:DWORD
El procedimiento de diálogo es similar al procedimiento de ventana excepto por el tipo de valor de retorno, que es TRUE/FALSE en vez de LRESULT. El administrador interno de la caja de diálogo dentro de Windows ES el verdadero procedimiento de ventana para la caja de diálogo. Llama a nuestra caja de diálogo con algunos mensajes que recibió. Así que la regla general es que: si nuestro procedimiento de diálogo procesa un mensaje, DEBE regresar TRUE (1) en eax y si no procesa el mensaje, debe regresar FALSE (0) en eax. Nota que un procedimiento de caja de diálogo no pasa los mensajes no procesados a la llamada DefWindowProc ya que no es realmente un procedimiento de ventana.
Hay dos usos distintos de una caja de diálogo. Puedes usarlas como ventanas principal de tu aplicación o usarla como un dispositivo de entrada. Examinaremos el primer acercamiento en este tutorial. "Usar una caja de diálogo como una ventana principal" puede ser interpretado en dos sentidos.
.data
ClassName
db "DLGCLASS",0
MenuName
db "MyMenu",0
DlgName
db "MyDialog",0
AppName
db "Our First Dialog Box",0
TestString
db "Wow! I'm in an edit box now",0
.data?
hInstance
HINSTANCE ?
CommandLine
LPSTR ?
buffer
db 512 dup(?)
.const
IDC_EDIT
equ 3000
IDC_BUTTON
equ 3001
IDC_EXIT
equ 3002
IDM_GETTEXT
equ 32000
IDM_CLEAR
equ 32001
IDM_EXIT
equ 32002
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
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 hDlg: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,DLGWINDOWEXTRA
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 CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,eax
invoke ShowWindow, hDlg,SW_SHOWNORMAL
invoke UpdateWindow, hDlg
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.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_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.IF dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc
endp
end
start
#define
IDC_EDIT
3000
#define
IDC_BUTTON
3001
#define
IDC_EXIT
3002
#define
IDM_GETTEXT
32000
#define
IDM_CLEAR
32001
#define
IDM_EXIT
32003
MyDialog
DIALOG 10, 10, 205, 60
STYLE
0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU
| WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION
"Our First Dialog Box"
CLASS
"DLGCLASS"
BEGIN
EDITTEXT IDC_EDIT,
15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13,
WS_GROUP
END
MyMenu
MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
Vamos a analizar el primer ejemplo.
Este ejemplo muestra como registrar una plantilla de diálogo como una clase de ventana y crear una "ventana" a partir de esa clase. Simplifica tu programa ya que no tienes que crear tú mismo los controles de ventana hija. Vamos a analizar primero la plantilla de diálogo.
MyDialog DIALOG 10, 10, 205, 60
Declarar el nombre del diálogo, en este caso, "MyDialog" seguido por la palabra clave "DIALOG". Los siguientes cuatro números son: x, y , ancho, y alto de la caja de diálogo en unidades de caja de diálogo no de pixeles [not the same as pixels].
STYLE
0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU
| WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
Declarar los estilos de la caja de diálogo.
CAPTION "Our First Dialog Box"
Este es el texto que aparecerá en la barra de título de la caja de diálogo.
CLASS "DLGCLASS"
Esta línea es crucial. Es esta palabra clave, CLASS, lo que nos permite usar la caja de diálogo como una clase de ventana. Después de la palabra clave está el "nombre de la clase"
BEGIN
EDITTEXT IDC_EDIT,
15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END
El bloque de arriba define los controles de ventana hija en la caja de diálogo. Están definidos entre las palabras claves BEGIN y END. Generalmente la sintaxis es la siguiente:
los tipos de controles son constantes del compilador de recursos así que tienes que consultar el manual.
Ahora vamos a ensamblar el código fuente. La parte interesante es la estructura de la clase de ventana:
Después de registrar la "clase de ventana", creamos nuestra caja de diálogo. En este ejemplo, lo creo como una caja de diálogo modal con la función CreateDialogParam. Esta función toma 5 parámetros, pero sólo tienes que llenar los primeros dos: el manejador de instancia y el puntero al nombre de la plantila de la caja de diálogo. Nota que el segundo parámetro no es un puntero al nombre de la clase.
En este punto, la caja de diálogo y sus controles de ventana hija son creados por Windows. Tu procedimiento de ventana recibirá el mensaje WM_CREATE como es usual.
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
El programa
introduce el bucle de mensajes y antes de que traduzcamos y despachemos mensajes,
llamamos a la función IsDialogMessage para permitir que el administrador
[manager] de la caja de diálogo maneje el teclado lógico de nuestra
caja de diálogo. Si la función regresa TRUE, significa que el
mensaje es enviado a la caja de diálogo y es procesado por el administrador
de la caja de diálogo. Nota ahora la diferencia respecto al tutorial
previo. Cuando el procedimiento de ventana quiere obtener el texto del control
de edición, llama a la fución GetDlgItemText en vez de GetWindowText.
GetDlgItemText acepta un ID de control en vez de un manejador de ventana. Eso
facilita la llamada en el caso de que utilices una caja de diálogo.
DlgProc 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
DlgName
db "MyDialog",0
AppName
db "Our Second Dialog Box",0
TestString
db "Wow! I'm in an edit box now",0
.data?
hInstance
HINSTANCE ?
CommandLine
LPSTR ?
buffer
db 512 dup(?)
.const
IDC_EDIT
equ 3000
IDC_BUTTON
equ 3001
IDC_EXIT
equ 3002
IDM_GETTEXT
equ 32000
IDM_CLEAR
equ 32001
IDM_EXIT
equ 32002
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax
DlgProc
proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_GETTEXT
invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSEIF ax==IDM_CLEAR
invoke SetDlgItemText,hWnd,IDC_EDIT,NULL
.ELSEIF ax==IDM_EXIT
invoke EndDialog, hWnd,NULL
.ENDIF
.ELSE
mov edx,wParam
shr edx,16
.if dx==BN_CLICKED
.IF ax==IDC_BUTTON
invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString
.ELSEIF ax==IDC_EXIT
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc
endp
end
start
#define
IDC_EDIT
3000
#define
IDC_BUTTON
3001
#define
IDC_EXIT
3002
#define IDR_MENU1 3003
#define
IDM_GETTEXT
32000
#define
IDM_CLEAR
32001
#define
IDM_EXIT
32003
MyDialog
DIALOG 10, 10, 205, 60
STYLE
0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU
| WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
CAPTION
"Our Second Dialog Box"
MENU
IDR_MENU1
BEGIN
EDITTEXT IDC_EDIT,
15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END
IDR_MENU1
MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
Declaramos el prototipo de función para DlgProc de manera que podamos referirnos a él con el operador addr en la línea de abajo:
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
la línea de arriba llama a la función DialogBoxParam que toma 5 parámetros: el manejador de instancia, el nombre de la plantilla de la caja de diálogo, el manejador de la ventana padre, la dirección del procedimiento de de ventana, y los datos específicos del diálogo. DialogBoxParam crea una caja de diálogo modal. No regresará hasta que la caja de diálogo sea destruida.
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
El procedimiento de la caja de diálogo se observa como un procedimiento de ventana excepto en que no recibe el mensaje WM_CREATE. El primer mensaje que recibe es WM_INITDIALOG. Normalmente puedes poner aquí el código de inicialización. Nota que debes regresar el valor TRUE en eax si procesas el mensaje.
El administrador interno de la caja de diálogo no envía a nuestro procedimiento de diálogo el mensaje WM_DESTROY por defecto cuando WM_CLOSE es enviado a nuestra caja de diálogo. Así que si queremos reaccionar cuando el usuario presiona el botón cerrar [close] en nuestra caja de diálogo, debemos procesar el mensaje WM_CLOSE. En nuestro ejemplo, enviamos el mensaje WM_COMMAND con el valor IDM_EXIT en wParam. Esto tiene el mismo efecto que cuando el usuario selecciona el elemento del menú Exit. EndDialog es llamado en respuesta a IDM_EXIT.
El procesamiento de WM_COMMAND se mantiene igual.
Cuando quieres destruir la caja de diálogo, la única manera es llamar a la función EndDialog. ¡No emplees DestroyWindow! EndDialog no destruye la caja de diálogo de inmediato. Sólo pone una bandera para ser revisada por el administrador interno de la caja de diálogo y continúa para ejecutar la siguiente instrucción.
Ahora vamos a revisar el archivo de recursos. El cambio notable es que en vez de usar una cadena de texto como nombre de menú usamos un valor, IDR_MENU1. Esto es necesario si quieres agregar un menú a la caja de diálog creada con DialogBoxParam. Nota que en la plantilla de caja de diálogo tienes que agregar la palabra clave MENU seguida por el ID del recurso.
Una diferencia
que puedes observar entre los dos ejemplos
de este tutorial es la carencia de un icono en el último ejemplo. Sin
embargo, puedes poner el icono enviando el mensaje WM_SETICON a la caja de diálgo
durante WM_INITDIALOG.
[Iczelion's Win32 Assembly Homepage]
n u M I T_o r's Programming Page
Este tutorial, original de Iczelion, ha sido traducido por: n u M I T_o r