<-- Capítulo V

Regresar a la página principal

Capítulo VII -->

Capítulo VI

Menus

Los menús deben de ser algo ya conocidos por ustedes, pero en este capitulo vamos a tratar funciones API que se encargan de la creación de un menú desde 0, es decir, si usar la utilidad que nos presenta VB para la creación de los mismos, ¿Y para que aprender eso?. Ciertamente es algo que muy pocas veces y tal vez nunca utilicen pero siempre es bueno saber como funcionan los componentes o recursos de Windows, por que estos nos ayuda a obtener mayor control sobre nuestro software, y veremos un proyecto muy interesante, donde la utilización de las funciones son primordiales ya que el editor de menú de VB no permite hacer lo que se muestra en el proyecto.

¿Qué es menú?. Se puede decir, que un menú es una lista de elementos o comandos, los cuales son desplegados de una manera ordenada, dando al usuario la opción de elegir uno de esos elementos, solamente pudiendo elegir un elemento a la vez.

Pero ese menú esta formado por una barra de menú “menú bar”, que es el menú principal, este es el que se encarga de desplegar otros menús que se llaman “sub-menús”. Todos estos elementos que conforman al menú se encuentran colocados en un orden de posición como se muestra a continuación:

Con estos números les indico la posición donde se encuentra cada elemento “item”, del menú. Llamamos barra de menú, aquella que contiene los elementos “Archivo”, “Edición” y “Salir”, y llamamos sub-menús, al que contiene “Nuevo”, “una línea de separación”, “Guardar”, “Cerrar”.

Ustedes dirían ¿Por qué hay dos 0, por que hay dos 1, por que no continuar con la secuencia de numeración?. Resulta que aquí ustedes creen que ven UN solo menú, pero a nivel de programación hay 2 menús, el primer menú es la barra de menús, y el segundo es un submenú. Es decir, hay 2 objetos menús en este ejemplo básico. Lo que sucede es que pensamos que todo es un menú, ciertamente es así, pero a nivel de programación se trabaja por separado. Ya lo veremos mejor en el primer proyecto.

Ya mencione que un menú, posee elementos “item”, los cuales generan una acción “command”, esa acción deberá de ser procesada por nuestro programa para hacer algo, por ejemplo, “Nuevo” es un elemento que puede realizar la acción de crear un documento, o imagen, etc. ¿Desde ese punto de vista “Archivo”, es un elemento que genera una acción?. Cuando un elemento lleva asociado un submenú, este elemento deja de generar una acción “command”, ya que la acción la procesa directamente Windows, y dicha acción sería desplegar el submenú asociado a dicho elemento.

Volviendo a los item, cada item, es como un pequeño objeto que puede tener diferentes estados, como desactivado, seleccionado, apagado, marcado, etc. Cuando vayamos a crear dicho elemento vamos a necesitar una estructura del tipo MENUITEMINFO, esta estructura contiene toda la configuración posible que se le puede asignar a dicho elemento, cada elemento que queramos añadir será una variable del tipo MENUITEMINFO, totalmente independiente.

Por esa razón procedo a explicar dicha estructura y sus diferentes constantes antes de entrar al primer proyecto.

La estructura esta configurada de la siguiente manera:

Public Type MENUITEMINFO

cbSize As Long

fMask As Long

fType As Long

fState As Long

wID As Long

hSubMenu As Long

hbmpChecked As Long

hbmpUnchecked As Long

dwItemData As Long

dwTypeData As String

cch As Long

End Type

CbSize = es el tamaño de dicha estructura se valor se obtiene con la función Len() de VB.

FMask = puede tomar las constantes que se presentan a continuación:

Public Const MIIM_CHECKMARKS = &H8 : Permite asignar o recuperar los valores correspondiente a las variables hbmpChecked y hbmpUnChecked.

Public Const MIIM_DATA = &H20 : Permite asignar o recuperar el valor correspondiente de la variable dwItemData.

Public Const MIIM_ID = &H2 : Permite asignar o recuperar el valor correspondiente de la variable wID.

Public Const MIIM_STATE = &H1 : Permite asignar o recuperar el valor correspondiente de la variable fState

Public Const MIIM_SUBMENU = &H4 : Permite asignar o recuperar el valor correspondiente de la variable hSubMenu

Public Const MIIM_TYPE = &H10 : Permite asignar o recuperar el valor correspondiente de la variable dwTypeData y fType.

El como se usa esta parámetro fMask lo veremos cuando explique el código del primer proyecto.

Ftype = este parámetro se utiliza para configurar al elemento del menú, y puede tomar las siguientes constantes:

Public Const MFT_BITMAP = &H4& : Con esto indicamos que el elemento del menú, será bitmap. El bitmap debe de ser pasado en la parte baja “low-order” de la variable dwTypeData. Y el parámetro “cch” es ignorado.

Public Const MFT_MENUBARBREAK = &H20& : Este parámetro coloca al elemento en una nueva línea en caso de la barra de menú, o en una nueva columna en caso de ser un sub-menú, y dicha columna es separada por una línea vertical, es decir, un submenú no tiene por que ser un listado completamente vertical, pueden haber varias columnas con elementos. En el proyecto lo verán un poco mejor.

Public Const MFT_MENUBREAK = &H40& : Hace lo mismo que la constante antes explicada, con la diferencia que la línea vertical que separa a dicha columna no es dibujada.

Public Const MFT_OWNERDRAW = &H100& : Con esta constante especificamos que vamos hacer nosotros lo que vamos a pintar el menú. Para esta constante existe un proyecto que veremos mas adelante.

Public Const MFT_RADIOCHECK = &H200& : Despliega un circulo negro al lado del elemento en caso de estar seleccionado, si el parámetro de la estructura hbmpChecked, tiene una imagen bitmap, pues se mostrara en ves del circulo dicho bitmap.

Public Const MFT_RIGHTJUSTIFY = &H4000& : Justifica el texto hacia la derecha, solo puede ser usado en la barra de menú “menú bar”

Public Const MFT_RIGHTORDER = &H2000& :

Public Const MFT_SEPARATOR = &H800& : Indica que el elemento es una línea de separación.

Public Const MFT_STRING = &H0& : Indica que el elemento viene representado por una cadena de caracteres.

Para este parámetro “fType” pueden usar varias constantes, pero con lógica, es decir, es ilógico usar “MFT_BITMAP or MFT_STRING”, ya que el elemento o es un bitmap o es una cadena de caracteres, pero no puede ser las dos cosas juntas.

FState: con esto indicamos o modificamos el estado de una elemento, puede tomar las siguientes constantes:

Public Const MFS_CHECKED = &H8& : configura al elemento como “seleccionado” o activo, es decir, coloca el circulo negro al lado del texto, o coloca la imagen pasada en el parámetro hbmpChecked

Public Const MFS_DEFAULT = &H1000& : Especifica que dicho elemento, es el elemento por defecto, y lo marca con letras negritas.

Public Const MFS_GRAYED = &H3& : Desactiva el elemento del menú, colocando el texto en gris claro.

Public Const MFS_DISABLED = MFS_GRAYED : Hace lo mismo que MFS_GRAYED.

Public Const MFS_ENABLED = &H0& : Activa el elemento del menú, regresándolo a su estado original.

Public Const MFS_HILITE = &H80& : Marca en azul al elemento cuando este visible. ¿Qué es eso de azul?. Bueno el azul dependerá de la configuración de los colores de Windows. Cuando ustedes despliegan un menú y pasan el mouse por encima de los elementos, este se marca, en mi maquina y en la configuración por defecto se marca en azul. A eso es lo que me refiero.

Public Const MFS_UNCHECKED = &H0& : configura al elemento como “deseleccionado” o desactivado, es decir, quita el circulo negro al lado del texto, o coloca la imagen pasada en el parámetro hbmpUnChecked

Public Const MFS_UNHILITE = &H0& : Si MFS_HILITE marcaba como seleccionado al elemento, es decir, lo pintaba de “azul” pues este parámetro hace lo contrario.

WID = Es un número de 16 bit único que identifica a dicho elemento.

HSubMenu = si dicho elemento al ser seleccionado despliega un submenú, pues aquí se pasa el “handle” de dicho menú. En caso de ser un elemento que realiza una acción “command”, pues se le coloca 0.

hbmpChecked = aquí le pasamos la imagen a mostrar en caso de que se haya definido en el parámetro fType la constante MFT_RADIOCHECK. Esta imagen es colocada al lado izquierdo del texto mostrado. Y dicha imagen es mostrada cuando el status del elemento esta como “seleccionado”

hbmpUnchecked = aquí le pasamos la imagen a mostrar en caso de que se haya definido en el parámetro fType la constante MFT_RADIOCHECK. Esta imagen es colocada al lado izquierdo del texto mostrado. Y dicha imagen es mostrada cuando el status del elemento esta como “deseleccionado”

dwItemData = Valor asociado al elemento del menú, en realidad, particularmente yo no le he visto uso a este parámetro.

DwTypeData = Se puede asignar información en dicho parámetro, cuando en fMask se especifica la constante MIIM_TYPE, este parámetro en caso de que en “ftype” se coloque la constante MFT_STRING, va la cadena de caracteres a mostrar en el menú. En caso de que sea MFT_BITMAP la parte baja de esta variable “low-order word”, tendrá la imagen a mostrar.

Cch = En caso de que se haya configurado MFT_STRING, en este parámetro va la longitud de la cadena pasada en “dwTypeData”.

Esta estructura como pueden ver es muy completa, y refleja la configuración de los elementos de un menú. Esta estructura es utilizada en todas las funciones que involucren la manipulación de los elementos de un menú, en caso de haber quedado confundidos en el uso de algunos parámetros no se preocupen que en el transcurso del capitulo aclararemos algunos de estos parámetro.

CREANDO MENUS

Ya explicado la configuración de los elementos de un menú, vamos a proceder a crear un menú, usando puras funciones API’S es decir, no vamos a usar el editor de menús, que presenta VB, con esto entenderemos un poco mejor el funcionamiento interno de los menús en un programa.

Para este proyecto necesitan un modulo y un formulario, en el formulario van a colocar 2 controles Frame, y un Label, dentro del frame1, colocan 3 botones, y en el otro frame2 colocan 3 botones mas. Déjenles los nombres por defecto a los controles. Pero al formulario lo van a llamar “frmMenu”.

Tambien busquen en su computadora si poseen este archivo “TLBINF32.dll” en caso de no tenerlo, en el zip que contiene dicho documento, se encuentra el archivo, el cual debe de ser copiado en la carpeta Window-System.

En el modulo colocan este código:

Ver Codigo

Es claro ver que estamos usando la técnica estudiada en el capitulo anterior llamada SubClassing, en este caso se lo aplicamos al menú. ¿Por qué usamos SubClassing?. Como estamos creando un menú sin usar el editor de VB, los mensajes hay que capturarlos “manualmente”, es decir, tenemos nosotros que interceptar el mensaje en el gestor de mensajes del formulario.

Se corren el programa verán, que un frame corresponde a un menú muy básico, es decir, solamente se crea la barra de menú, y el otro frame crea un menú un poco mas complejo ya que el elemento “Archivo”, posee internamente un Submenú. Vamos a ver ahora las funciones encargadas de dicha creación.

CreateMenu

Api: Declare Function CreateMenu Lib "user32" () As Long

Esta función es sencilla y lo que hace es crear un objeto del tipo menú. Si la creación es un éxito el valor que retorna es distinto de 0.

En nuestro proyecto usamos esta función, en el frame de “barra de menú simple”; el botón de “crear menú” llama a una función llamada “CreacionBarraMenu” aquí tenemos esta línea:

hMenu1 = CreateMenu()

Es decir, hMenu1 es nuestra variable del tipo “menú”. En el otro frame, en el botón “crear menu” tenemos 2 líneas:

hMenu1 = CreateMenu()

hMenu2 = CreateMenu()

¿Por qué 2 menús? Recuerden que la barra de menú es un “menú”, y el Submenú que abre cuando se presiona en “Archivo” es otro “menú”. Ambos independientes uno del otro.

DestroyMenu

Api: Declare Function DestroyMenu Lib "user32" (ByVal hMenu As Long) As Long

Todo lo que se crea a “mano” debe de ser destruido y esta función es la que se encarga de destruir los objetos del tipo menú. Como primer parámetro se le pasa el “handle” del menú que se obtuvo en la creación del mismo, en pocas palabras se pasan en el primer parámetro las variables utilizados para la creación de los menús, que en nuestro caso son hMenu1 y hMenu2.

NOTA

Antes de continuar, vamos a explicar un poco lo que hace la función InicializarEstructurasMenuBar en esta función es donde se configuran los elementos que conformaran en este caso a la barra de menú, y la función InicializarEstructurasSubMenu es la encargada de configurar los elementos que conforman al submenú que se habilita en “Archivo”, voy a explicar un poco como configurar los elementos y la explicación de uno vale para todos los demás.

Vamos a ver como se configura el elemento “Archivo”.

Archivo.cbSize = Len(Archivo)

Archivo.fMask = MIIM_ID Or MIIM_STATE Or MIIM_TYPE

Archivo.fType = MFT_STRING

Archivo.fState = MFS_ENABLED

Archivo.wID = ID_ARCHIVO

Archivo.hSubMenu = 0

Archivo.dwItemData = 0

Archivo.dwTypeData = "Archivo"

Archivo.cch = Len("Archivo")

Archivo.hbmpChecked = 0

Archivo.hbmpUnchecked = 0

El parámetro de la estructura que mas puede confundir es “fMask”, los valores de este parámetro dependerá de lo que vayamos a configurar, es decir, en esta estructura configuramos los parámetros “fType”, “fState” y “wID”, para que sea valido la asignación de dichos valores, es necesario indicarle que estamos cambiando esas variables y lo hacemos aplicando un “or” lógico a las constantes MIIM_ID Or MIIM_STATE Or MIIM_TYPE, por ejemplo, si quitáramos la constante MIIM_TYPE el menú no muestra nada ¿Por que no muestra nada si colocamos la cadena en “dwTypeData”?. Recuerden la definición de la constante MIIM_TYPE: “Permite asignar o recuperar el valor correspondiente de la variable dwTypeData y fType”. Cuando nosotros llenamos el valor de “dwTypeData”, si queremos que ese valor se le asigne realmente al elemento es necesario especificar en “fMask” la constante que permite que las funciones que manipulan a los elementos tomen en cuenta dicho valor.

Vean por ejemplo como se configura la barra separable:

Barra.cbSize = Len(Barra)

Barra.fMask = MIIM_TYPE

Barra.fType = MFT_SEPARATOR

Barra.fState = 0

Barra.wID = ID_BARRA

Barra.hSubMenu = 0

Barra.dwItemData = 0

Barra.dwTypeData = 0

Barra.cch = 0

Barra.hbmpChecked = 0

Barra.hbmpUnchecked = 0

Lo único que configuramos aquí es el “fType”, ya que todos los demás valores no importa, cuando ponemos en “fMask”, MIIM_TYPE, en este caso toma en cuenta “fType” y omite todos los demás parámetros. Ustedes podrían decir ¿Para que entonces configuras “wID”?. Lo hago para seguir un patrón, el parámetro “wID” sirve al momento de capturar los mensajes, pero como en una barra de separación no nos interesa capturar el mensaje ya que ella no es un elemento de acción. Pues no importa el valor que pongamos, en este caso es omitido.

InsertMenuItem

Api: Declare Function InsertMenuItem Lib "user32" Alias "InsertMenuItemA" (ByVal hMenu As Long, ByVal un As Long, ByVal bool As Boolean, lpcMenuItemInfo As MENUITEMINFO) As Long

Ya creado un menú con CreateMenu, a ese menú le tenemos que insertar “elementos” y esta función es la que se encarga de ello.

Como primer parámetro “hMenu”, le pasamos la variable utilizada en la función CreateMenu. El segundo y tercer parámetro funcionaran de acuerdo a lo que se le asigne. Para entender esto expliquemos algo: un elemento puede ser insertado por “POSICIÓN” o por su “IDENTIFICADOR DE MENU”. ¿Qué significa?. Vamos al grafico del menú básico que colocamos arriba:

Si ya hago esto: InsertMenuItem hBarra, 2, True, Prueba, suponiendo que el menú de la barra de menú esta en la variable hBarra, ¿Dónde se colocara el elemento prueba?. Como segundo parámetro indique la posición que me interesa, que en este caso es 2, si en el segundo parámetro esta la posición del elemento, entonces en el tercer parámetro se colocará TRUE, con esto se le indica a la función que en el segundo parámetro se encuentra es la POSICIÓN del elemento.

Es decir, “Salir” sería desplazado a la posición 3 ya que “Prueba” estaría en la posición 2.

En caso de que haya colocado: InsertMenuItem hBarra, ID_PRUEBA, False, Prueba, al poner false en el tercer parámetro implica que tenemos que colocar el ID del elemento en el segundo parámetro. ¿Pero, a donde se coloca el elemento?. Se coloca al final del menú, es decir, después del elemento “Salir” o lo que es lo mismo en la posición 3.

Así es como se manejan los parámetro 2 y 3 de esta función, como cuarto parámetro se le pasa la estructura del elemento a insertar.

En nuestro código lo usamos de esta manera:

InsertMenuItem hMenu1, 0, True, Archivo

InsertMenuItem hMenu1, 1, True, Edicion

InsertMenuItem hMenu1, 2, True, Salir

Pueden darse cuenta que estoy ubicando los elementos por “posición”.

Ya creado los elementos, en el menú, lo que nos falta es asignarle el menú al formulario, y para eso es la función que viene a continuación.

SetMenu

Api: Declare Function SetMenu Lib "user32" (ByVal hwnd As Long, ByVal hMenu As Long) As Long

Esta función es la encargada de asignarle un menú a una ventana, especificada en el primer parámetro, y como segundo parámetro le pasamos el menú que queremos asignar.

DrawMenuBar

Api: Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) As Long

Esta función se encarga de “repintar” la barra de menú (menú bar) de una ventana, cuyo “hwnd” se especifica en el primer parámetro.

NOTA

¿Cómo capturamos los mensajes de los menú?. Los mensajes que envían los menús, se encuentran especificado en la documentación oficial, en este proyecto nosotros capturamos los mensajes WM_MENUSELECT y WM_COMMAND, dejo claro que WM_COMMAND no es especifico del menú, ya que un botón, por ejemplo, también manda dicho mensaje, WM_COMMAND se origina cuando hacemos clic en un elemento de acción del menú, es decir, un elemento que abre otro submenú no manda dicho mensaje, solo los elementos de acción “command” mandan el mensaje.

El WM_MENUSELECT es enviado cuando se selecciona un elemento de acción del menú. OJO seleccionar no es darle clic al elemento, es marcarlo nada mas, es decir, tener el puntero del mouse encima de él. En nuestro gestor de mensajes usamos el WM_MENUSELECT de la siguiente manera:

If mensaje = WM_MENUSELECT Then

Select Case loword(wParam)

Case ID_NUEVO

frmMenu.Label1 = "ESTAS EN NUEVO"

Case ID_SELECCION

frmMenu.Label1 = "ESTAS EN SELECCCION"

Case ID_SELECCION2

frmMenu.Label1 = "ESTAS EN SELECCCION 2"

End Select

End If

¿Cómo saber que elemento esta seleccionado?. La parte baja de memoria de la variable “wParam” contiene dicho ID. Este ID es el valor que se le pasa al estructura del elemento en su parámetro “wID”.

¿Qué es parte baja de la memoria?. “wParam” es una variable de 32 Bits, que es lo equivalente a 16 bits + 16 bits. Bueno existen dos funciones llamadas hiword y loword, que pueden tomar esos 16 bits, en este caso usamos loword(wParam), ya que la documentación oficial establece que en los 16 bits de la parte baja de la memoria de dicha variable se encuentra el valor del ID del elemento seleccionado. Y en la parte alta se encuentra una especie de “flag”, es decir, tiene un valor que dependiendo de dicho valor pueden saber si el elemento esta activado, desactivado, etc. Para ver dichos valores vayan a la ayuda de MSDN y busquen dicho mensaje.

¿En el mensaje WM_COMMAND, como saber el elemento seleccionado?. En este caso también la parte baja de la memoria que ocupa “wParam” contiene dicho valor. ¿Pero si en el código veo en el Select a wParam y no loword(wParam)?.

Ejm:

If mensaje = WM_COMMAND Then

Select Case wParam

Lo que sucede es que en los menús la parte alta de dicha memoria es 0, por lo que da lo mismo usar wParam o loword(wParam). ¿Siempre es así?, cuando trabajamos con menús SI!!, pero con otros controles NO!!, recuerden que el mensaje WM_COMMAND no es exclusivo de los menús, sino es usado por otros controles. Si usamos un botón la parte alta de “wParam” no es 0, sino que posee un valor. De todas maneras en la ayuda pueden documentarse un poco mas.

GetMenuItemInfo

Api: Declare Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As Long, ByVal b As Boolean, lpMenuItemInfo As MENUITEMINFO) As Long

Con esta función tomamos, o retornamos información de un elemento. Los parámetros son iguales a la función encargada de insertar dichos elementos. Por lo que me ahorro repetir la explicación. Lo que voy a explicar es como usamos dicha función. En el menú “hMenu1”, tenemos que dos elementos son del tipo CHECK, es decir, aquellos que pintan o una imagen en el lado izquierdo o un circulito negro al lado del texto.

Si usaran el editor de menú de VB, VB se encarga automáticamente de marcar y desmarcar dicho elemento, pero como creamos el menú a “mano” nosotros somos los encargados de marcar y desmarcar dicho elemento. ¿Cómo lo hacemos?. Capturamos el mensaje WM_COMMAND. Para saber si esta marcado o desmarcado obtenemos dicha información con la función GetMenuItemInfo ¿Cómo?. Veamos el ejemplo:

inf.cbSize = Len(inf)

inf.fMask = MIIM_STATE

GetMenuItemInfo hMenu2, ID_SELECCION, False, inf

En “fMask” especificamos que parámetro deseamos recuperar, en nuestro caso es MIIM_STATE, es decir, el estado actual del elemento, por consiguiente, después de ejecutar la función GetMenuItemInfo, el parámetro de la estructura “fState” contendrá el valor actual:

If inf.fState = MFS_CHECKED Then

inf.fState = MFS_UNCHECKED

Else

inf.fState = MFS_CHECKED

End If

Verifico dicho estado, y si esta seleccionado, lo deselecciono o viceversa. Ya cambiada la información, ahora hay que asignársela al elemento en cuestión.

SetMenuItemInfo

Api: Declare Function SetMenuItemInfo Lib "user32" Alias "SetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As Long, ByVal bool As Boolean, lpcMenuItemInfo As MENUITEMINFO) As Long

Y esta función se encarga de ello, cuando queremos modificar información de un elemento ya creado tenemos que usar esta función. Los 4 parámetros ya fueron explicado. En nuestro ejemplo lo tenemos de esta manera:

SetMenuItemInfo hMenu2, ID_SELECCION, False, inf

Pero dense cuenta, que la ubicación de dicho elemento no lo hacemos por “POSICION” sino usando el identificador del menú, ya que así es mas cómodo.

Por esa razón al presionar dicho elemento, el se marca si estaba desmarcado, o se desmarca si estaba marcado., La diferencia entre los dos elementos de selección, es que uno usa el circulito negro y el otro usa imágenes para indicar la selección, y deselección.

Es evidente que va ser muy raro, crear un menú de esta manera, pero es importante saber que funciones participan en dicha creación, por que ahora vamos a ver como crear un menú que no puede ser creado con el editor de VB, que son los menús “owner draw”.

MENU “OWNER DRAW”

Lo que vamos hacer en este proyecto es algo que no podemos hacer con el editor de VB, en primer lugar estarán preguntándose ¿Qué es OWNER DRAW?. Owner Draw implica que el encargado de dibujar el menú eres tu!. Windows se lava las manos y deja de parte del programador dibujar dicho menú. Ustedes dirán ¡que fastidioso!, ciertamente lo es, pero se pueden lograr efectos espectaculares que no son posibles si dejamos que Windows se encargué del dibujo.

Por ejemplo, imaginen que tienen un menú cuyo elemento se llama “COLORES”, al seleccionar este elemento, usando la forma tradicional, mostraríamos un submenú con los nombres de los colores que vamos a utilizar, y cada ves que seleccionemos dicho nombre el fondo de nuestro formulario se pinta de dicho color. Pero no sería sorprendente que en ves de mostrar el nombre de los colores, el menú mostrara el “color”, es decir, que si el primer elemento es azul, no muestre el nombre “Azul” sino que dicho elemento se dibuje de azul. Dicho efecto sería imposible con el editor de menú tradicional y hay que usar las funciones de Windows y SubClassing para lograr dicho efecto.

Para este proyecto es necesario, crear un menú con el editor de VB, que va a tener un solo elemento en la barra de menú, cuyo nombre le pueden poner “menú Grafico”, a este menú le van a poner un elemento cuyo caption sea “Colores”, es decir, cuando se le de clic a “menú Grafico” se abrirá un submenú que tiene el elemento “Colores”, la parte de “name” les dejo a ustedes que pongan lo que quieran. Al formulario le ponen el nombre de “frmmenudraw”

Luego en un modulo pegan este código:

Ver Codigo

Cuando arrancan el proyecto, se meten por “Menú Grafico”, verán que el elemento “Colores” abre un submenú, pero este submenú son 3 colores, que al ser seleccionados pintan el fondo del formulario de dicho color. ¡Sorprendente!. En este proyecto no hay muchas funciones nuevas que explicar, solamente hay dos sencillas, pero voy a concentrar la explicación al como funciona el “protocolo para crear un menú Owner Draw”.

¿Qué es esto de protocolo?. Me refiero a los pasos a seguir para lograr dicho efecto. Estos pasos están llenos de mucho detalles así que presten mucha atención, ya que explicare dicho código paso a paso.

Empecemos entonces en el load:

Private Sub Form_Load()

nuevoGestor Me.hwnd ‘ Cambiamos el gestor de mensajes, ya deberían de estar familiarizados con esto.

CreaMenuSubMenu ‘ llamamos a la función en donde creamos el submenú que va a contener dichos colores.

End Sub

Vamonos a “CreaMenuSubMenu”:

hMenu1 = GetSubMenu(GetMenu(Me.hwnd), 0) ‘ Obtenemos el submenú que creamos con el editor de VB.

hMenu2 = CreateMenu() ‘ Inicializamos nuestro nuevo menú.

InicializarEstructurasSubMenu ‘ Configuramos las estructuras que contienen la configuración de los elementos que conformaran dicho submenú.

InsertMenuItem hMenu2, 0, True, Amarillo ‘ Insertamos los elementos

InsertMenuItem hMenu2, 1, True, Rojo

InsertMenuItem hMenu2, 2, True, Azul

Archivo.cbSize = Len(Archivo) ‘ Tomamos la longitud de la estructura llamada “Archivo”.

Archivo.fMask = MIIM_SUBMENU ‘ Indico que voy a modificar el submenú del elemento.

Archivo.hSubMenu = hMenu2 ‘ Coloco el menú o submenú que mostrara el elemento del menú.

SetMenuItemInfo hMenu1, 0, True, Archivo ‘ A hMenu1 (el menú que contiene el elemento “Colores” que creamos con en el editor), a dicho elemento de posición “0”, le asigno la estructura “Archivo”, lo que hicimos aquí fue cambiar los atributos del elemento “Colores”, el atributo que cambiamos fue de que ahora dicho elemento no es un elemento de acción, sino que es un elemento que abre un submenú. Este submenú es hMenu2.

GetMenu

Api: Declare Function GetMenu Lib "user32" (ByVal hwnd As Long) As Long

Esta función es utilizada para tomar la referencia del menú de una ventana, dicha ventana es indicada en el “hwnd” que se le pasa como primer parámetro.

GetSubMenu

Api: Declare Function GetSubMenu Lib "user32" (ByVal hMenu As Long, ByVal nPos As Long) As Long

Con esta función obtenemos un “submenú” o menú despejable de un menú. Como primer parámetro le pasamos el menú original “hMenu”, y como segundo parámetro le pasamos la posición del submenú que queremos capturar.

TODO CONFUSO!!. Veamos el ejemplo de arriba:

hMenu1 = GetSubMenu(GetMenu(Me.hwnd), 0)

¿Que tiene “hMenu1”? El tiene el submenú que contiene al elemento “Colores” ¿Por qué?. A la función GetSubMenu le pasamos como primer parámetro nuestro menú principal, GetMenu(Me.hwnd), con esta línea en azul estamos retornando el menú donde se encuentra el elemento “Menú Grafico”, ya obtenida la barra de menú, el paso que sigue es obtener la referencia del submenú que abre “Menú Grafico”, para ello usamos GetSubMenu, le pasamos el menú donde sabemos que esta dicho submenú, y como segundo parámetro pasamos la posición donde se encuentra, en este caso la posición es 0, ya que el submenú que contiene “Colores” se encuentra en la posición 0.

En pocas palabras “hMenu1”, contiene el submenú o menú, que contiene el elemento “Colores”.

Con la función InicializarEstructurasSubMenu configuramos los elementos que contendrá el submenú que abrirá el elemento “Colores”. Mostremos uno solo y la explicación es valido para los demás:

Amarillo.cbSize = Len(Amarillo)

Amarillo.fMask = MIIM_ID Or MIIM_STATE Or MIIM_TYPE

Amarillo.fType = MFT_OWNERDRAW ‘ Con esto indicamos que vamos a ser nosotros los encargados de dibujar dicho elemento.

Amarillo.fState = MFS_ENABLED ‘ Indicamos que esta activo

Amarillo.wID = ID_COLOR_AMARILLO ‘ Le configuramos un ID único que vamos a usar cuando recibamos los mensajes.

Amarillo.hSubMenu = 0

Amarillo.dwItemData = 0

Amarillo.dwTypeData = ""

Amarillo.cch = 0

Amarillo.hbmpChecked = 0

Amarillo.hbmpUnchecked = 0

En el momento que colocamos esta constante MFT_OWNERDRAW ya nuestra ventana sufre cambios, en el sentido que ahora va a recibir 2 mensajes, que antiguamente no recibía. Dichos mensajes son: WM_MEASUREITEM y WM_DRAWITEM, para poder usar dichos mensajes tuve que recurrir a ciertas trampitas, así que presten mucha atención a lo que explicare a continuación.

WM_MEASUREITEM

Este mensaje se genera cuando se crea el menú o submenú (owner draw) por primera vez, y las veces que se genera dependerá de la cantidad de elementos que tenga el submenú, que quiero decir, si tienen un submenú con 5 elementos este mensaje se genera 5 veces cuando se despliega el menú por primera vez. ¿Qué hacemos aquí?. Normalmente lo que se hace es determinar el ancho y alto de dicho elemento, y hacer ciertas configuraciones preliminares a cada elemento antes de ser creado el submenú, en nuestro ejemplo, lo único que hacemos es cambiar el ancho y alto del elemento.

Cuando se genera dicho mensaje la variable “wParam” contiene 0, siempre y cuando sea un menú. En caso de que sea un ComboBox, Listbox u otro control, “wParam” contiene el valor que identifica a dicho control.

NOTA

¿ComboBox, ListBox u otro control?. Esta técnica de “Owner Draw” no es exclusivo de los menús, los ComboBox, Listbox, button, etc, también tienen la opción de personalizar los elementos que dichos controles muestran, se trabaja prácticamente de la misma manera, por lo que voy a explicar como se usa en los menús, y en caso de estar interesados de hacerlo en dichos controles, entonces vean la ayuda de MSDN. Pero si entienden esta parte, les será fácil llevar esta técnica de Owner Draw a dichos controles.

La variable interesante es “lParam”, esta variable contiene un puntero a una estructura del tipo MEASUREITEMSTRUCT. Esta estructura la presento a continuación:

Public Type MEASUREITEMSTRUCT

CtlType As Long

CtlID As Long

itemID As Long

itemWidth As Long

itemHeight As Long

itemData As Long

End Type

CtlType: identifica el tipo de control, puede tener estos valores:

Private Const ODT_BUTTON = 4

Private Const ODT_COMBOBOX = 3

Private Const ODT_LISTBOX = 2

Private Const ODT_LISTVIEW = 102

Private Const ODT_MENU = 1

Creo que en las constantes es fácil ver que control identifica cada una.

CtlID: Especifica el identificador de dicho control. En los menús no se usa.

ItemID: Especifica el identificador del elemento de un menú. En caso de ser un ComboBox o Listbox el parámetro contiene la posición del elemento.

ItemWidth: Determina el ancho del elemento.

ItemHeight: Determina el alto del elemento.

ItemData: Especifica una información adicional de 32 Bits, asociado con un elemento del menú. En caso de ser un ComboBox y Listbox, y dependiendo los estilos configurados en dichos controles tendrán un valor asignado. Para ver que valores pueden ser, vean en la ayuda de MSDN.

En los menús normalmente lo que interesa son los parámetros ItemWidth e ItemHeight, pero cambiar dichos valores es todo un reto, y no es tan fácil como lo imaginan ¿Por qué?.

En primer lugar “lParam” es una variable de tipo “LONG”, es decir, que no se puede hacer esto:

LParam.ItemWidth = ancho

LParam.ItemHeight = alto

Para entender “el como lo hice”, voy a explicar lo que yo intente hacer al principio, que no sirvió y luego explicare “el por que” lo que hice después funciono.

Antes de eso, vamos a explicar una función que sirve para manipular información en la memoria.

MoveMemory

Api: Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, ByVal Source As Long, ByVal length As Long)

Esta función mueve la información de un lugar de la memoria a otro lugar. Como primer parámetro pasamos la variable que recibirá dicha información, como segundo parámetro pasamos la variable que contiene la información a mover, y como tercer parámetro, pasamos la cantidad de bytes que queremos copiar.

Lo primero que intente hacer fue lo siguiente:

Case WM_MEASUREITEM

MoveMemory item, lparam, Len(item)

item.itemHeight = height_menu

item.itemWidth = width_menu

¿Que hiciste aquí Eduardo? Vean la siguiente grafica:

“lParam” como dije es un puntero ¿Que es esto?. Es la dirección de memoria, de donde se encuentra la información de la estructura, pero esa estructura como explique arriba no la puedo acceder por “lParam” directamente ya que “lParam” es una variable de tipo “long”, cuando se ejecuta esta línea:

MoveMemory item, lparam, Len(item)

La información de “lParam”, viaja y se mueve a la ubicación en memoria de la variable “item”, a partir de este momento en la memoria hay 2 bloques de información iguales ¿Ven por que?. Claro “lParam” sigue teniendo la información de antes, pero la moví a la variable “item”, el “mover” por las pruebas que hice no implica que “lparam” pierde la información, tan sencillamente mueve o superpone la información que había en “item” que originalmente no tenia nada, es decir, los parámetros tenían 0.

Aplicado esta instrucción, ya podía acceder a la información de la variable “item”, es decir, esta dos líneas eran validas:

item.itemHeight = height_menu

item.itemWidth = width_menu

Cabe mencionar que Windows tiene un tamaño por defecto, es decir, ya ItemWidth e ItemHeight tienen un valor. Pero lo mencionado arriba no funcionaba, no importa el tamaño que colocara, los elementos del menú siempre aparecían con el mismo tamaño, al principio no sabia que pasaba, pero veamos de nuevo el grafico:

Es decir, en un bloque de memoria estaba la nueva altura y ancho que queríamos, y en el otro bloque estaban la altura y ancho por defecto. Inmediatamente me pregunte ¿Como Windows sabrá que se modifican los valores?. Y pensé!!, Windows debe de estar esperando que se modifique la estructura que se encuentra en la dirección de “lParam”, evidentemente no funcionaba, por que Windows no esta al tanto de saber que yo moví el bloque de memoria y que la información actual la posee la variable “item”. Windows espera que yo modifique el bloque de memoria que referencia “lParam”.

El reto ahora quedaba, en como modificar el bloque que referenciaba “lParam”, ustedes dirán regresas el cambio:

MoveMemory lParam, item, Len(item)

ERROR!!, ¿por qué? como segundo parámetro la función MoveWindow NO ACEPTA estructuras sino variables del tipo LONG, al principio lo acepto por que como primer parámetro se puede pasar cualquier cosa, ya que la variable era del tipo “Any”. Segundo “lParam” es una variable de 4 bytes, y la estructura MEASUREITEMSTRUCT es de 24 Bytes, recuerden que “lparam” contiene es la dirección de memoria donde empieza la estructura.

¿Qué hacemos entonces? Creamos otra función que funcione como “oído” en este caso GestorMensajeI, pero este oído es especial:

Function GestorMensajeI(ByVal hwnd As Long, ByVal mensaje As Long, ByVal wParam As Long, ByRef item As MEASUREITEMSTRUCT) As Long

Se supone, que en todo gestor de mensajes “normal” en el último parámetro debería de estar la variable “lParam”, pero le hice una modificación al gestor, por lo que ahora recibe una “referencia” del tipo MEASUREITEMSTRUCT.

Cuando el gestor de mensajes principal recibe el mensaje, llamamos a nuestro “oído” especial:

Dim a As Long

a = CallWindowProc(AddressOf GestorMensajeI, hwnd, mensaje, wParam, lparam)

Ustedes se preguntaran ¿Por que funciona?. Veamos la grafica:

Por consiguiente, cuando Windows toma el bloque de memoria referenciado por “lParam” esta tomando la estructura con la nueva información modificada.

¿Es así tan complicado esto?. Esta parte NO!, es complicado en VB ya que esta lenguaje carece de punteros, en C se soluciona con una simple línea:

lpmis = (LPMEASUREITEMSTRUCT) lParam

Pero como VB carece de este tipo de conversiones que posee C, y tenemos que usar trucos para solucionar el problema, yo conseguí solucionarlo de esta manera, si un experto en memoria logra hacerlo de manera mas fácil, le agradecería que me dijera ¿cómo?.

WM_DRAWITEM

Este mensaje es enviado cuando se le cambia la apariencia visual a un ComboBox, ListBox, menú, button, etc. Es decir, es en este mensaje es donde dibujamos lo que queremos mostrar. Al igual que WM_MEASUREITEM, “wParam” contiene el identificador del control que genero el mensaje, y “lParam” tiene un puntero a una estructura del tipo DRAWITEMSTRUCT.

Public Type DRAWITEMSTRUCT

CtlType As Long

CtlID As Long

itemID As Long

itemAction As Long

itemState As Long

hwndItem As Long

hdc As Long

rcItem As Rect

itemData As Long

End Type

CtlType : identifica el tipo de control, puede tener los mismos valores especificados en el “CtlType” de la estructura MEASUREITEMSTRUCT.

CtlId: Especifica el identificador de dicho control. En los menús no se usa.

ItemID: Especifica el identificador del elemento de un menú. En caso de ser un ComboBox o Listbox el parámetro contiene la posición del elemento.

ItemAction: Especifica la acción que se ejecuta en dicho control. Puede tomar los siguientes valores:

Private Const ODA_DRAWENTIRE = &H1 : Este parámetro determina que el control debe de ser pintado por completo.

Private Const ODA_FOCUS = &H4 : El control ha perdido o ganado el foco.

Private Const ODA_SELECT = &H2 : El status de selección del elemento de dicho control ha cambiado.

ItemState: Especifica el estado del elemento. Con ello tomamos la decisión de ¿Qué pintar?. Puede tener los siguientes valores:

Private Const ODS_CHECKED = &H8 : El elemento del menú se encuentra en el estado “check” o “marcado”.

Private Const ODS_DEFAULT = &H20 : El elemento seleccionado es el que se encuentra configurado como elemento “por defecto”.

Private Const ODS_DISABLED = &H4 : El elemento se encuentra desactivado.

Private Const ODS_FOCUS = &H10 : El elemento tiene el foco del teclado.

Private Const ODS_GRAYED = &H2 : El elemento se encuentra configurado como desactivado. Solo para menús.

Private Const ODS_SELECTED = &H1 : El elemento se encuentra seleccionado

HwndItem: contiene el hwnd del control que contiene dichos elementos, en el caso de los menús, contiene el identificador de dicho menú.

Hdc: dispositivo de contexto, del control en donde dibujamos.

RcItem: Especifica el tamaño del rectángulo en donde podemos dibujar.

ItemData: Especifica una información adicional de 32 Bits, asociado con un elemento del menú. En caso de ser un ComboBox y Listbox, y dependiendo los estilos configurados en dichos controles tendrán un valor asignado. Para ver que valores puede ser vean en la ayuda de MSDN.

Estos parámetros, lo usamos para saber básicamente el estado del elemento activo, para así poder dibujar, recuerden que “owner draw” implica que tenemos que dibujar ¡TODO!, nosotros somos los encargado de programar lo que ve el usuario cuando un elemento tiene un estado especifico.

¿Cómo usamos este mensaje en el código?. Primero movemos el bloque de memoria de “lParam” a un bloque de memoria que ya esta configurado para recibir una estructura del tipo DRAWITEMSTRUCT.

Case WM_DRAWITEM

MoveMemory draw, lparam, Len(draw)

¿Por que aquí, usamos MoveMemory y no la técnica empleada con MEASUREITEMSTRUCT?. Esta estructura es de carácter informativo, es decir, no vamos a cambiar nada, el problema con la estructura anterior es que teníamos que cambiar la altura y ancho a nuestra conveniencia, y esta técnica no servia. Pero como solo vamos a usar esta estructura de carácter informativo, el usar MoveMemory sirve a la perfección.

Select Case draw.itemID

Con esto verificamos que elemento esta pidiendo ser dibujado.

Case ID_COLOR_AMARILLO

Select Case draw.itemAction

Poniendo el ejemplo que el elemento que solicita ser pintado es ID_COLOR_AMARILLO, procedemos a verificar que ACCIÓN se esta ejecutando en dicho elemento.

Case ODA_DRAWENTIRE

FillRect draw.hdc, draw.rcItem, CreateSolidBrush(RGB(255, 255, 0))

Los elementos ejecutaran este mensaje ODA_DRAWENTIRE, cada vez que el Sub-Menú se muestre, es decir, cada vez que cerremos el menú y lo volvamos abrir, se ejecuta dicho mensaje, indicando que debe de ser pintado dicho elemento.

Case ODA_SELECT

If draw.itemState And ODS_SELECTED Then

brocha = CreateSolidBrush(RGB(255, 255, 0))

SelectObject draw.hdc, brocha

Rectangle draw.hdc, draw.rcItem.Left, draw.rcItem.Top, draw.rcItem.Right, draw.rcItem.Bottom

DeleteObject brocha

Else

FillRect draw.hdc, draw.rcItem, CreateSolidBrush(RGB(255, 255, 0))

End If

Ya con el menú abierto, no se ejecuta mas ODA_DRAWENTIRE, y capturamos la acción ODA_SELECT, es decir, cuando pasamos el mouse encima de los elementos la acción que se ejecuta es ODA_SELECT. Verificamos el estado de dicho elemento:

If draw.itemState And ODS_SELECTED Then

Si esta seleccionado, es decir, el puntero del mouse esta encima del elemento, pintamos un rectángulo negro alrededor, para indicarle al usuario que ese es el color que se encuentra seleccionado.

En caso contrario pintamos de nuevo el rectángulo, sin el marco negro, indicando que dicho elemento fue deseleccionado.

Cuando el mouse esta sobre un elemento y pasa a otro elemento, Windows manda dos veces el mensaje WM_DRAWITEM, el primero es para el elemento que fue deseleccionado y el segundo es para el elemento actualmente seleccionado.

En este ejemplo nada mas programe un solo estado que es ODS_SELECTED. Dependiendo de lo que haga su menú, o control, deberán de programar los otros diferentes estados que pueden tener dicho elemento.

¿Cómo sabias el tamaño del rectángulo a dibujar?. El parámetro rcItem, contiene el rectángulo del elemento a dibujar. OJO : “del elemento a dibujar”, en este caso este parámetro no tiene el tamaño de todo el hdc, sino nada mas tiene el tamaño y ubicación de la porción que le corresponde a dicho elemento. Estos cálculos Windows los hace automáticamente, gracias a que en el mensaje WM_MEASUREITEM nosotros configuramos el ancho y alto de los elementos.

Pueden ver que el trabajo de esta técnica “Owner Draw” sin importar donde la apliquemos, ya sea en un menú, comboBox o listbox, es un trabajo arduo y largo, y ustedes dirán ¿Vale la pena?. Por supuesto, imaginen por ejemplo, que tienen un menú donde el usuario, selecciona un tipo de letra, e imaginen que el texto del menú, se encuentre escrito con el tipo de fuente que se quiere seleccionar, o todos esos menús con texturas, imágenes, efectos especiales, que ya con esta información ustedes se encuentran en la capacidad de desarrollarlos sin ningún problema.

MENU DEL SISTEMA

En este proyecto vamos a manipular el menú del sistema que posee un formulario, me refiero al menú mostrado cuando se le da clic al icono del formulario, a este menú se le llama “menú del sistema” o “system menú”. Para este proyecto necesitan un formulario llamado “frmmenusistema” que tenga 3 botones y un modulo.

En el modulo pegan este código:

Ver Codigo

Presionen los botones, y luego desplieguen el menú del sistema para que vean dichos efectos.

GetSystemMenu

Api: Declare Function GetSystemMenu Lib "user32" (ByVal hwnd As Long, ByVal bRevert As Long) As Long

Función que retorna el identificador o handle del menú del sistema. Como primer parámetro se le pasa la ventana que contiene al menú, y como segundo parámetro le pasamos TRUE o FALSE, si le indicamos FALSE, la función retorna el identificador del menú, en caso de pasarle TRUE el menú regresa a su estado original y retorna 0.

Podemos ver su uso en el evento:

Private Sub Command1_Click()

InsertMenuItem GetSystemMenu(Me.hwnd, False), GetMenuItemCount(GetSystemMenu(Me.hwnd, False)) + 1, True, Nuevo

End Sub

Como pueden ver se usan las funciones de manera directa, es decir, el valor que me retorna la función no la guardo en una variable sino voy directo a la función que necesita dicho valor.

En el otro evento:

Private Sub Command2_Click()

GetSystemMenu Me.hwnd, True

DrawMenuBar Me.hwnd

End Sub

Restablezco el menú a su estado original.

GetMenuItemCount

Api: Declare Function GetMenuItemCount Lib "user32" (ByVal hMenu As Long) As Long

Función que se encarga de enumerar cuantos elementos posee un menú. Como único parámetro posee el menú al cual queremos saber cuantos elementos tiene.

Su uso lo pueden ver en Command1_Click.

RemoveMenu

Api: Declare Function RemoveMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long

Esta función se usa para remover un elemento de un menú, este menú viene especificado en el primer parámetro de la función. Como segundo parámetro se le coloca, la posición del elemento o el identificador de dicho elemento, para que la función sepa “que valor se paso”, se usa el tercer parámetro, el cual puede tener los siguientes valores:

Public Const MF_BYCOMMAND = &H0& : Indica que al elemento se le esta identificando por su “identificador”, es decir, identificador especificado en el segundo parámetro.

Public Const MF_BYPOSITION = &H400& : Indica que al elemento se le esta identificando por su posición en el menú, es decir, el segundo parámetro tiene el valor de dicha posición.

En nuestro proyecto uso el MF_BYCOMMAND:

Private Sub Command3_Click()

RemoveMenu GetSystemMenu(Me.hwnd, False), SC_CLOSE, MF_BYCOMMAND

DrawMenuBar Me.hwnd

End Sub

WM_SYSCOMMAND

Este es el mensaje que es enviado a la ventana cuando algún elemento del sistema o del área nocliente es activado.

El valor que posee “wParam” es el tipo de mensaje enviado por el sistema. Y “lParam” se divide en dos valores, la parte alta “hiword” posee la coordenada “Y” del puntero del mouse. Y la parte baja “loword” posee la coordenada “X” del puntero del mouse.

Algunos de los tipos de mensajes que Windows puede enviarles a través del mensaje WM_SYSCOMMAND son:

Public Const SC_CLOSE = 61536 : Indica que la ventana será cerrada, es decir, se presiono la “X” del formulario, o se selecciono cerrar en el menú del sistema

Public Const SC_HSCROLL = 61568 : Se esta usando el Scroll horizontal de la ventana

Public Const SC_MAXIMIZE = 61488 : Indica que se maximizara la ventana, es decir, se presiono en el botón maximizar.

Public Const SC_MINIMIZE = 61472 : Indica que se minimizara la ventana, es decir, se presiono en el botón minimizar.

Public Const SC_MONITORPOWER = 61808 : Se manda cuando se activa el “ahorro de energía” o “power safe” por parte del computador o Windows, en este caso “lparam” puede contener 2 valores:

1 = significa que el sistema esta activo en baja energía o “low power”

2 = significa que el sistema regresa a la normalidad, es decir, se desactiva el ahorrador de energía.

Public Const SC_MOVE = 61456 : Se manda este mensaje cuando la ventana se mueve.

Public Const SC_RESTORE = 61728 : Indica que se restaura la ventana, es decir, se presiono en el botón restaurar.

Public Const SC_SCREENSAVE = 61760 : Indica que el protector de pantalla va ser activado.

Public Const SC_SIZE = 61440 : Indica que las dimensiones de la ventana se están alterando.

Public Const SC_VSCROLL = 61552 : Se esta usando el Scroll vertical de la ventana.

En el proyecto, se usa las constantes SC_CLOSE, SC_MAXIMIZE y SC_MINIMIZE, es decir, cada vez que presionan los botones de la esquina superior derecha de la ventana, captura dicho clic con el mensaje WM_SYSCOMMAND, y muestro un mensaje antes de que la acción solicitada se ejecute.

MENU CONTEXTUALES

Los menús contextuales, son esos menús que aparecen en las ventanas, cuando se presionan el botón secundario del mouse. Por ejemplo, si presionan en este documento el botón secundario del mouse, verán un menú corto, con distintas opciones como “editar”, “copiar” , etc, estos menús son llamados “menús contextuales”.

Para este proyecto, se necesita un formulario, que tenga un botón y un menú con la siguiente estructura:

Archivo

Nuevo

Salir

Y luego pegan este código, en el formulario:

Cuando corran, presionen el botón secundario en el botón mostrado y verán que se muestra el submenú, que contiene el elemento “Archivo”.

Ustedes dirán, ¿Cuál es la gracia, de mostrar un menú que se muestra en el formulario? Ciertamente no tiene sentido, lo hice solo para enseñar el funcionamiento de la función encargada de mostrar dicho menú, en la “realidad” tienes 2 opciones, o creas el menú con “CreateMenu”, opción muy fastidiosa, ya que tenemos que crear elemento por elemento y capturar los mensajes de cada elemento por SubClassing. O la segunda opción, que la use en un proyecto personal, esta consistía en crear un formulario donde configuraba cada menú contextual con el editor de menú. Este formulario “JAMAS” lo mostraba el programa, es decir, no tenia nada, lo único que tenia eran los menús, cuando un botón necesitaba algún menú contextual, lo que hacia era, cargar el formulario, y tomaba con las funciones API’S el menú, sub menú, etc, y mostraba el menú, cuando el usuario da clic a un elemento, dicho evento lo capturo en el formulario que posee el menú y listo!!!, ¡rápido y sencillo!.

TrackPopupMenu

Api: Declare Function TrackPopupMenu Lib "user32" (ByVal hMenu As Long, ByVal wFlags As Long, ByVal X As Long, ByVal Y As Long, ByVal nReserved As Long, ByVal hwnd As Long, lprc As RECT) As Long

Esta es la función que se encarga de desplegar un menú, en una posición especifica. Como primer parámetro le pasamos el handle o identificador del menú, como segundo parámetro “wFlags” le podemos pasar unas constantes que se muestran a continuación:

Private Const TPM_BOTTOMALIGN = &H20&

Private Const TPM_CENTERALIGN = &H4&

Private Const TPM_LEFTALIGN = &H0&

Private Const TPM_RIGHTALIGN = &H8&

Private Const TPM_TOPALIGN = &H0&

Private Const TPM_VCENTERALIGN = &H10&

Private Const TPM_RIGHTBUTTON = &H2&

Private Const TPM_LEFTBUTTON = &H0&

El efecto que tiene cada constante, véanlo en el proyecto, quitándole los comentarios a las líneas que contiene la función TrackPopupMenu.

Como segundo y tercer parámetro les pasamos las coordenadas X e Y donde será mostrado, dichas coordenadas son de “pantalla” o “screen”, ¿Qué significa coordenada de pantallas o screen?. Todos sabemos que la configuración de video de nuestra pantalla, posee una resolución, por ejemplo, 800x600 píxeles, coordenadas en pantalla significa un X e Y dentro del rango de la resolución de pantalla.

Al parámetro “nReserved” se le pasa 0, al parámetro “hwnd” se le pasa el “hwnd” de la ventana que contiene dicho menú. Y el último parámetro es “lprc” que es una variable de tipo RECT, la cual no es tomada en cuenta, es decir, no importa que valor tenga Windows hace caso omiso a este parámetro.

GetWindowRect

Api: Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long

Esta función lo que hace es tomar las coordenadas de “pantalla” de una ventana. Como primer parámetro le pasamos el “hwnd” de la ventana a la cual queremos saber su ubicación, y como segundo parámetro le pasamos una variable del tipo RECT en donde se almacenaran dichas coordenadas.

En nuestro proyecto la usamos para conocer las coordenadas de pantalla del botón:

GetWindowRect Command1.hwnd, r

r.Left = r.Left + (X \ 15)

r.Top = r.Top + (Y \ 15)

Es decir, luego a esas coordenadas le sumamos, el lugar donde se presiono el botón secundario del mouse, divido entre 15 ya que recuerden que estas coordenadas están en Twips, y 1 Twip equivale a 15 píxeles, es esa la razón del porque el menú se abre justo en el puntero del mouse.

<-- Capítulo V

Regresar arriba

Regresar a la página principal

Capítulo VII -->

Desarrollado por Eduardo Roa. Copyright 2002-2003 Todos los derechos reservados