Tutorial 27: Control 'Tooltip'

Aprenderemos sobre el control tooltip [sugerencia]: ¿Qué es, y cómo crearlo y usarlo. Baja el ejemplo.

Teoría:

Un 'tooltip' [una sugerencia] es una pequeña ventana rectangular que se despliega cuando el ratón pasa sobre algún área específica. Una ventana 'tooltip' contiene algún texto que el programador quiere que sea desplegado. En este aspecto, un 'tooltip' tiene el mismo rol que una ventana de estado pero desaparece cuando el usuario mueve el ratón o hace click lejos del área diseñada. Probablemente estarás familiarizado con los 'tooltips' asociados a los botones de barras de herramientas. Esos 'tooltips' son funcionalidades convenientes suministradas por las barras de herramientas. Si quieres 'tooltips' para otras ventanas/controles, necesitas crear tu propio control 'tooltip'.

Ahora que sabes qué es un 'tooltip', veamos cómo podemos crearlo y usarlo. Los pasos están esbozados aquí:

  1. Crear un control 'tooltip' con CreateWindowEx
  2. Definir una región que el control 'tooltip' monitoreará para el movimiento del puntero del ratón.
  3. Subsumir la región del control 'tooltip'
  4. Transmitir los mensajes del ratón del área subsumida al control 'tooltip' (este paso puede ocurrir antes, dependiendo del método usado para to transmitir los mensajes)

Ahora examinaremos cada paso en detalle.

Creación del Tooltip

El control 'tooltip' es un control común. Por eso, necesitas llamar InitCommonControls en algún lugar de tu código fuente de manera que MASM implícitamente enlace tu programa a comctl32.dll. Creas un control 'tooltip' con CreateWindowEx. El escenario típico sería más o menos este:

.data
TooltipClassName db "Tooltips_class32",0
.code
.....
invoke InitCommonControls
invoke CreateWindowEx, NULL, addr TooltipClassName, NULL, TIS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL

Nota el estilo de ventana: TIS_ALWAYSTIP. Este estilo especifica que el 'tooltip' será mostrado cuando el puntero del ratón esté sobre el área designada independientemente del esrtdo de la ventana que contiene el área. Para ponerlo con mayor simplicidad, si usas esta bandera, cuando el puntero del ratón pase sobre el área que registraste para el control 'tooltip', la ventana 'tooltip' aparecerá incluso si la ventana debajo del puntero del ratón está activa.

No tienes que incluir los estilos WS_POPUP y WS_EX_TOOLWINDOW en CreateWindowEx porque el procedimiento de ventana del control 'tooltip' la agrega automáticamente. Tampoco necesitas especificar, la altura y el ancho de la ventana del control 'tooltip': el control 'tooltip' los ajustará automáticamente para fijar el texto del 'tooltip' que será desplegado, así que suministramos CW_USEDEFAULT en todos los cuatro parámetros. Los restantes parámetros no son tan relevantes.

Especificando la herramienta

El control 'tooltip' es creado pero no mostrado de inmediato. Queremos que la ventana 'tooltip' se muestre cuando el puntero del ratón pase sobre algún área. Ahora es el momento de especificar ese área. Llamamos a tal área "herramienta" [tool]. Una herramienta es un área rectangular sobre el área cliente de una ventana que el control tooltip monitoreará para el puntero del ratón. Si el puntero del ratón pasa sobre la herramienta, aparecerá la ventana 'tooltip'. El área rectangular puede cubrir todo el área cliente o sólo parte de ella. Así que podemos dividir la herramienta en dos tipos: una implementada como una ventana y otra implementada como un área rectangular en el área cliente de alguna ventana. Ambas tienen sus usos. La herramienta que cubre todo el área cliente de una ventana es usada con mucha frecuencia con controles tales como botones, controles de edición, etc. No necesirtas especificar la coordenada y las dimensiones de la herramienta: se asume que estará sobre todo el área cliente de la ventana. La herramienta que es implementada como un área rectangular sobre el área cliente es útil cuando quieres dividir el área cliente de una ventana en varias regiones sin usar ventanas hijas. Con este tipo de herramienta, necesitas especificar la coordenada de la esquina superior izquierda y el ancho y la altura de la herramienta. Con este tipo de herramienta, no necesitas especificar la esquina superior izquierda y el ancho y la altura de la herramienta.

Especificas la herramienta con la estructura TOOLINFO que tiene la siguiente definición:

TOOLINFO STRUCT
  cbSize             DWORD      ?
  uFlags             DWORD      ?
  hWnd               DWORD      ?
  uId                DWORD      ?
  rect               RECT      <>
  hInst              DWORD      ?
  lpszText           DWORD      ?
  lParam             LPARAM     ?
TOOLINFO ENDS

Nombre del Campo Explicación
cbSize El tamaño de la estructura TOOLINFO. DEBES llenar este miembro. Windows no monitoreará el error si este campo no es llenado debidamente y recibirás extraños e impredecibles resultados.
uFlags Los bits de bandera que especifican . Este valor puede ser una combinación de las siguientes banderas:
  • TTF_IDISHWND  "ID is hWnd". Si especificas esta bandera, significa que quieres usar una herramienta que cubre todo el área cliente de una ventana (el primer tipo de herramienta de arriba). Si usas esta bandera, debes llenar el miembro uId de esta estructura con el manejador de la ventana que quieres usar. Si no especificas esta bandera, significa que quieres el segundo tipo de herramienta, la que es implementada como área rectangular sobre la ventana cliente. En ese caso, necesitas llenar el miembro rect con la dimensión del rectángulo.
  • TTF_CENTERTIP  Normalmente la ventana 'tooltip' a la derecha y abajo del puntero del ratón. Si especificas esta bandera, la ventana 'tooltip' aparecerá directamente debajo de la herramienta y será centrada independientemente de la posición del puntero del ratón.
  • TTF_RTLREADING  Pudes olvidar esta bendera si tu programa no está diseñado específicamente para sistemas arábicos o hebreos. Esta bandera despliega el texto de la sugerencia [tooltip] de derecha a izquierda. No trabaja bajo otros sistemas.
  • TTF_SUBCLASS  Si usas esta bandera, significa que le dices al control 'tooltip' que subclasifique la ventana que la herramienta sobre la cuál está la herramienta de manera que el control 'tooltip' pueda interceptar mensajes de ratón que son enviados a la ventana. Esta manera es muy conveniente. Si no usas esta bandera, tienes que trabajar más para transmitir los mensajes del ratón al control 'tooltip'.
hWnd Manejador a la ventana que contiene la herramienrta. Si especificas la bendera TTF_IDISHWND, este campo es ignorado ya que Windows usará el valor en el miembro uId como manejador de ventana. Necesitas llenar este campo si:
  • No usas la bendera TTF_IDISHWND (en otras palabras, usas una herramienta rectangular)
  • Especificas el valor LPSTR_TEXTCALLBACK en el mimebro lpszText. Este valor le dice al control 'tooltip' que, cuando necesita desplegar la ventana 'tooltip', debe consultar a la ventana que contiene la herramienta para el texto a ser desplegado. Si quieres cambiar tu texto de sugerencia [tooltip] dinámicamente, deberías especificar el valor LPSTR_TEXTCALLBACK en el miembro lpszText. El control 'tooltip' enviará el mensaje de notificación TTN_NEEDTEXT a la ventana identificada por el manejador en el campo hWnd.
uId El valor en este campo puede tener dos significados, dependiendo si el miembro uFlags contiene la bandera TTF_IDISHWND.
  • ID de la herramienta definido para la aplicación si la bandera TTF_IDISHWND no está especificada. Puesto que esto significa que usas una herramienta que cubre sólo una parte del área cliente, es lógico que puedas tener muchas de tales herramientas sobre el mismo área cliente (sin superponerse). El control 'tooltip' necesita una manera de diferenciarlas. En este caso, el manejador de ventana en el miembro hWnd no es suficiente ya que todas las herramientas está sobre la misma ventana. Los IDs definidos para la aplicación son entonces necesarios. Los IDs pueden ser de cualquier valor con tal de que su valor sea único entre ellos.
  • El manejador de la ventana cuya totalidad del área cliente es usada como herramienta si la bandera TTF_IDISHWND es especificada. Te puedes preguntar por qué este campo se usa para almacenar el manejador de ventana en vez del campo hWnd de arriba. La respuesta es: el miembro hWnd ya puede ser llenado si se especifica el valor LPSTR_TEXTCALLBACK en el miembro lpszText y la ventana que es responsable de suministrar el texto del 'tooltip' y la ventana que contiene la herramienta NO no puede ser la misma (Puedes diseñar tu programa de manera que una ventana pueda servir para ambos roles pero esto restringe bastante. En este caso, Microsoft te da mayor libertad. Ánimo.)
rect Una estructura RECT que especifica la dimensión de la herramienta. Esta estructura define un rectángulo relativo a la esquina izquierda superior del área cliente de la ventana especificada dpor el miembro hWnd. En pocas palabras, debes llenar esta estructura si quieres especificar una herramienta que cubra sólo una parte del área cliente. El control 'tooltip' ignorará este miembro si especificas la bandera TTF_IDISHWND (eliges usar una herramienta que cubra todo el área cliente)
hInst El manejador de la instancia que contiene el recurso de cadena que será usado como texto de sugerencia [tooltip text] si el valor en el miembro lpszText especifica el identificador del recurso de cadena. Esto puede sonar confuso. Lee primero la explicación del miembro lpszText y entenderás para qué es usado este campo. El control 'tooltip' ignorará este campo si el campo lpszText no contiene un identificador del recursos de cadena.
lpszText Este campo puede tener diversos valores:
  • Si especificas el valor LPSTR_TEXTCALLBACK en este campo, el control tooltip enviará el mensaje de notificación TTN_NEEDTEXT a la ventana identificada por el manejador en el campo hWnd para la cadena de texto a ser desplegada en la ventana tooltip. Este es el método más dinámico de actualización del texto de sugerencia: puedes cambiar el texto de sugerencia cada vez que la ventana 'tooltip' sea desplegada.
  • Si especificas un identificador de recursos de cadena en este campo, cuando el control 'tooltip' necesite desplegar el texto de sugerencia en la ventana 'tooltip', busca por le cadena en la tabla de cadenas de la instancia especificada por el mimbro hInst. El control 'tooltip' identifica un identificador de cadena de recurso chequeando la palabra alta de este campo. Ya que un identificador de cadena es un valor de 16-bits, la palabra alta de este campo siempre será cero. Este método es útil si planificas portear [to port] tu programa a otros lenguajes. Puesto que el recurso de cadena es definido en el guión de recursos, no necesitas modificar el código fuente. Sólo tienes que modificar las tablas de cadenas y los textos de sugerencia cambiarán sin el riesgo de introducir errores en tu programa
  • Si el valor de este campo no es LPSTR_TEXTCALLBACK y la palabra alta no es cero, el control 'tooltip' interpreta el valor como el puntero a una cadena de texto que será usada como texto de sugerencia. Este método es el más fácil de usar pero el menos flexible.

Para recapitular, necesitas llenar la estructura TOOLINFO antes de subsumirla al control 'tooltip'. Esta estructura describe las características de la herramienta que deseas.

Registrar la herramienta [tool] con el control 'tooltip'

Después de que llenes la estructura TOOLINFO, debes subsumirla al control 'tooltip'. Un control 'tooltip' puede servirse de muchas herramientas, por lo que es innecesario crear más de un control 'tooltip' para una ventana. Para registrar una herramienta con un control 'tooltip', envías el mensaje TTM_ADDTOOL al control 'tooltip'. El valor de wParam no se usa y lParam debe contener la dirección de la estructura TOOLINFO que quieres registrar.

.data?
ti TOOLINFO <>
.......
.code
.......
<fill the TOOLINFO structure>
.......
invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti

SendMessage para este mensaje regresará TRUE si la herramienta es satisfactoriamente registrada con el control 'tooltip', FALSE si ese no es el caso.
Puedes cancelar el registro de la herramienta enviando el mensaje TTM_DELTOOL al control 'tooltip'.

Transmitiendo los Mensajes del Ratón al Control 'Tooltip'

Cuando el paso de arriba es completado, el control 'tooltip' sabe qué área debería monitorearse para los mensajes del ratón y qué texto debería desplegar el conrtrol tooltip. Lo único que falta es el *gatillo* [trigger] para la acción. Piensa en ello: el área especificada por la herramienta está sobre el área cliente de la otra ventana. ¿Cómo puede interceptar el control 'tooltip' los mensajes del ratón para esa ventana? Necesita hacerlo así con el fin de que pueda medir la cantidad de tiempo que el puntero del ratón se halla sobre un punto en la herramienta de manera que cuando caduque el lapso de tiempo especificado, el control 'tooltip' muestre la ventana con la sugerencia [tooltip window]. Hay dos métodos para alcanzar esta meta, una requiere la cooperación de la ventana que contiene la herramienta y la otra sin la cooperación sobre la parte de esa ventana.

Eso es. En este paso, tu control tooltip es completamente funcional. Hay varios mensajes útiles relativos al 'tooltip' sobre los cuáles deberías tener conocimiento.

Ejemplo:

El siguiente ejemplo es una simple caja de diálogo con dos botones. El área cliente de la caja de diálogo es dividida en 4 áreas: superior izquierda, superior derecha, baja izquierda, baja derecha. Cada área es especificada como una herramienta con sus propios textos de sugerencia [tooltip text]. Los dos botones también tienen sus propios textos de sugerencia.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
EnumChild proto :DWORD,:DWORD
SetDlgToolArea proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
.const
IDD_MAINDIALOG equ 101
.data
ToolTipsClassName db "Tooltips_class32",0
MainDialogText1 db "This is the upper left area of the dialog",0
MainDialogText2 db "This is the upper right area of the dialog",0
MainDialogText3 db "This is the lower left area of the dialog",0
MainDialogText4 db "This is the lower right area of the dialog",0
.data?
hwndTool dd ?
hInstance dd ?
.code
start:
    invoke GetModuleHandle,NULL
    mov hInstance,eax
    invoke DialogBoxParam,hInstance,IDD_MAINDIALOG,NULL,addr DlgProc,NULL
    invoke ExitProcess,eax

DlgProc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    LOCAL ti:TOOLINFO
    LOCAL id:DWORD
    LOCAL rect:RECT
    .if uMsg==WM_INITDIALOG
        invoke InitCommonControls
        invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
            TTS_ALWAYSTIP,CW_USEDEFAULT,\
            CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
            hInstance,NULL
        mov hwndTool,eax
        mov id,0
        mov ti.cbSize,sizeof TOOLINFO
        mov ti.uFlags,TTF_SUBCLASS
        push hDlg
        pop ti.hWnd
        invoke GetWindowRect,hDlg,addr rect
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText2,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText3,id,addr rect
        inc id
        invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText4,id,addr rect
        invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
    .elseif uMsg==WM_CLOSE
        invoke EndDialog,hDlg,NULL
    .else
        mov eax,FALSE
        ret
    .endif
    mov eax,TRUE
    ret
DlgProc endp

EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
    LOCAL buffer[256]:BYTE
    mov edi,lParam
    assume edi:ptr TOOLINFO
    push hwndChild
    pop [edi].uId
    or [edi].uFlags,TTF_IDISHWND
    invoke GetWindowText,hwndChild,addr buffer,255
    lea eax,buffer
    mov [edi].lpszText,eax
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
    assume edi:nothing
    ret
EnumChild endp

SetDlgToolArea proc uses edi esi hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD
    mov edi,lpti
    mov esi,lprect
    assume esi:ptr RECT
    assume edi:ptr TOOLINFO
    .if id==0
        mov [edi].rect.left,0
        mov [edi].rect.top,0
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        mov [edi].rect.bottom,eax
    .elseif id==1
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        inc eax
        mov [edi].rect.left,eax
        mov [edi].rect.top,0
        mov eax,[esi].right
        sub eax,[esi].left
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .elseif id==2
        mov [edi].rect.left,0
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        inc eax
        mov [edi].rect.top,eax
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .else
        mov eax,[esi].right
        sub eax,[esi].left
        shr eax,1
        inc eax
        mov [edi].rect.left,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        shr eax,1
        inc eax
        mov [edi].rect.top,eax
        mov eax,[esi].right
        sub eax,[esi].left
        mov [edi].rect.right,eax
        mov eax,[esi].bottom
        sub eax,[esi].top
        mov [edi].rect.bottom,eax
    .endif
    push lpText
    pop [edi].lpszText
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti
    assume edi:nothing
    assume esi:nothing
    ret
SetDlgToolArea endp
end start

Análisis:

Después de que es creada la ventana del diálogo principal, creamos el control 'tooltip ' con CreateWindowEx.

invoke InitCommonControls
invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
       TTS_ALWAYSTIP,CW_USEDEFAULT,\
       CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
       hInstance,NULL
mov hwndTool,eax

Después de eso procedemos a definir cuatro herramientas para cada esquina de la caja de diálogo.

    mov id,0        ; usado como el ID de la herramienta
    mov ti.cbSize,sizeof TOOLINFO
    mov ti.uFlags,TTF_SUBCLASS   ; dice al control tooltip que subclasifique la ventana del diálogo.
    push hDlg
    pop ti.hWnd    ; manejador para la ventana que contiene la herramienta
    invoke GetWindowRect,hDlg,addr rect; obtener la dimensión del área cliente
    invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect

Inicializamos los miembros de la estructura TOOLINFO. Nota que queremos dividir el área cliente en 4 herramientas, así que necesitamos saber la dimensión del área cliente. Esa es la razón por la cual llamamos a GetWindowRect. No queremos transmitir mensajes de ratón al control 'control' nosotros mismos, así que especificamos la bandera TIF_SUBCLASS.

SetDlgToolArea es una función que calcula el rectángulo asociado de cada herramienta y registra la herramienta para el control 'tooltip'. No entraré en detalles engorrosos sobre el cálculo, es suficiente decir que divide el área cliente en 4 áreas con los mismos tamaños. Luego envía el mensaje TTM_ADDTOOL al control 'tooltip', pasando la dirección de la estructura TOOLINFO en el parámetro lParam.

    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti

Después de que son registradas las 4 herramientas, podemos ocuparnos en los botones de la caja de diálogo. Podemos manejar cada botón a través de su ID pero esto es muy tedioso.En vez de eso usaremos la llamada a la API EnumChildWindows para enumerar todos los controles en la caja de diálogo y luego registrarlos para el control 'tooltip'. EnumChildWindows tiene la siguiente sintaxis:

EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD

hWnd es el manejador de la ventana padre. lpEnumFunc es la dirección de la función EnumChildProc que será llamada para cada control enumerado. lParam es el valor definido de la aplicación [application-defined value] que será pasado a la función EnumChildProc. La a función EnumChildProc tiene la siguiente definición:

EnumChildProc proto hwndChild:DWORD, lParam:DWORD

hwndChild es el manejador a un control enumerado por EnumChildWindows. lParam es el mismo valor lParam que pasas a 0 EnumChildWindows.
En nuestro ejemplo, llamamos a EnumChildWindows más o menos así:

invoke EnumChildWindows,hDlg,addr EnumChild,addr ti

Pasamos la dirección de la estructura TOOLINFO en el parámetro lParam porque registraremos cada control de ventana hija para el control 'tooltip' en la función EnumChild. Si no queremos usar este método, necesitaremos declarar ti como una variable global que puede introducir errores [bugs].

Cuando llamamos a EnumChildWindows, Windows enumerará los controles de ventanas hijas sobre nuestra caja de diálogo y llamamos a la función EnumChild una vez para cada control enumerado. De esta manera, si nuestra caja de diálogo tiene dos controles, EnumChild será llamada dos veces.

La función EnumChild llena los miembros relevantes de la estructura TOOLINFO y luego registra la herramienta con el control 'tooltip'.

EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
    LOCAL buffer[256]:BYTE
    mov edi,lParam
    assume edi:ptr TOOLINFO
    push hwndChild
    pop [edi].uId ; usamos todo el área cliente del control como herramienta
    or [edi].uFlags,TTF_IDISHWND
    invoke GetWindowText,hwndChild,addr buffer,255
    lea eax,buffer ; usar el texto de la ventana como texto de sugerencia
    mov [edi].lpszText,eax
    invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
    assume edi:nothing
    ret
EnumChild endp

Nota que en este caso, un tipo diferente de herramienta: una que cubre todo el área cliente de la ventana. Así que necesitamos llenar todo el campo uID con el manejador de ventana que contiene la herramienta. También debemos especificar la benadera TTF_IDISHWND en el miembrio uFlags.


Index

Next

[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

www.000webhost.com