el Guille, la Web del Visual Basic, C#, .NET y más...

Obteniendo mensajes de los controles normales

 

Autor: RFOG (MVP de Visual C++)
Publicado: 10/May/2010 (26/Abr/2010)
Actualizado: 10/May/2010

En esta segunda entrada vamos a ver cómo una vez que tenemos una ventana compuesta de sub controles, podremos modificar y trabajar con ellos desde la clase que los contiene y no desde la propia clase que representa el control, lo que nos obligaría a heredar de él.




 

Introducción:

Una vez que tenemos la ventana llena de controles, es hora de obtener funcionalidad de los mismos. Da igual que sean controles con actualización mediante DDX o ventanas como las que hemos creado en la entrada anterior: la forma de hacerlo es la misma.

En detalle

El primer paso es decidir quién va a controlar los mensajes. En general podemos hacerlo a dos niveles: o bien en la ventana marco o bien en la propia ventana que contiene los controles, e incluso también en ambos sitios a la vez. Una tercera opción es reflejar los mensajes sobre el propio control, de este modo tendríamos un control "autosuficiente".

El concepto está en que el gestor de mensajes de MFC recorre toda la jerarquía de ventanas padre/hijas/propietarias intentando encontrar un manejador para dicho mensaje, y cuando lo encuentra hace la llamada pertinente, y si no hay ninguno realiza la acción por defecto. También hay que tener en cuenta que hay muchos mensajes que también son capturados y controlados por MFC, y a veces entran en conflicto con los nuestros. Según qué tipo de mensaje capturemos tendremos que llamar a su método padre o no, y ciertamente no hay una regla clara para ello.

No penséis que se trata de un proceso lento. Pese a recorrer varias tablas, suele ser más rápido que tener esos gigantescos switch/case que tan habituales eran hace un tiempo. Además, dada la jerarquía de objetos, es relativamente fácil eliminar, añadir o permitir comportamiento por defecto. El mayor problema es siempre la documentación, que es escasa y mala.

Bueno, una vez que sabemos quién controlará los mensajes, y sabemos qué mensaje vamos a capturar, es cuestión de añadir una entrada al mapa de mensajes de la clase en cuestión. Un mapa de mensajes típico de una ventana marco podría ser:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx)
	ON_WM_CREATE()
	ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize)
	ON_COMMAND(ID_FILE_OPEN, OnImportFirmware)
	ON_COMMAND(ID_FIRMWARE_SPECIAL, OnFirmwareSpecial)
	ON_REGISTERED_MESSAGE(AFX_WM_CREATETOOLBAR, &CMainFrame::OnToolbarCreateNew)
	ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_OFF_2007_AQUA, 
		&CMainFrame::OnApplicationLook)
	ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_OFF_2007_AQUA, 
		&CMainFrame::OnUpdateApplicationLook)
	ON_NOTIFY(TVN_SELCHANGED, ID_TREEVIEW, OnTvnSelchanged)
	ON_COMMAND(ID_PROPERTIES2,OnSaveThisConfiguration)
	ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, &OnUpdateFileSave)
	ON_COMMAND(ID_FILE_SAVE, &OnFileSave)
END_MESSAGE_MAP()

Si os fijáis hay varios tipos de entradas, pero la que nos interesa es esta:

ON_NOTIFY(TVN_SELCHANGED, ID_TREEVIEW, OnTvnSelchanged)

Es del tipo notificación, y toma tres parámetros. El primero es el tipo de notificación, que en este caso es un cambio de elemento seleccionado. Es decir, cuando nosotros estamos en un cuadro de lista o en un árbol, cada vez que cambiemos o hagamos clic en un nuevo elemento, recibiremos esta notificación (realmente la recibiremos más veces, tres por cada cambio de elemento: se ha deseleccionado un elemento, no hay elemento seleccionado y se ha seleccionado otro nuevo).

El siguiente parámetro es el ID del recurso que representa al control. En el caso de la entrada anterior, nuestra ventana creó el cuadro de lista con el ID número 8:

    if(!m_items.Create(dwViewStyle,rectDummy,this,8))
    {
        TRACE0("Failed to create times control\n");
        return -1;      // fail to create
    }

Está claro que lo ideal hubiera sido utilizar un identificador como se hace en los cuadros de diálogo, y es lo que aconsejamos. De hecho, en el mapa de arriba el ID es TREEVIEW, que se corresponde con un control de árbol embebido en una ventana de igual modo que nosotros embebimos el listbox en la entrada anterior.

Finalmente tenemos el método que se ejecutará cada vez que se reciba la notificación. No tiene que ser un método de nuestra clase, podría serlo de cualquiera tal y como se podría deducir de otras entradas de mapa.

La firma del mensaje es:

    afx_msg void OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult);

En general cada mensaje o cada tipo de mensajes tiene una firma diferente, y esta suele ser la habitual en el caso de las notificaciones. El primer parámetro contiene un puntero a una cabecera que en general tendremos que mutar a otro tipo de estructura, y el segundo es el valor que debemos devolver al control que generó el evento.

El cuerpo del método contendrá qué queremos hacer cada vez que se haya modificado el ítem seleccionado:

void CMainFrame::OnTvnSelchanged (NMHDR *pNotifyStruct, LRESULT *)
{
    NMLISTVIEW *plv=(NMLISTVIEW *)pNotifyStruct;
    if(plv->uNewState==3)     //Only when selecting
    GetDataManager()->FillMachinesForThisManufacturer(/*…*/);
}

Podéis ver aquí cómo hemos mutado el puntero de un NMHDR a un NMLISTVIEW y sólo en el caso de que sea una selección, ejecutamos algo.

Cosa interesante aparte de la mutación es el comparar con un número y no con un valor definido. Seguro que existe dicho valor, pero el autor no ha sido capaz de encontrarlo y lo ha tenido que hacer poniendo un punto de interrupción justo ahí y comprobar qué valor era el correcto para un cambio de selección.

No os extrañe que tengáis que hacer cosas así en MFC. Eso y espiar los estilos de ventanas con el programa "Spy" son cosas que están a la orden del día.

Si añadimos este código al ejemplo de la entrada anterior, podremos ejecutar una acción cada vez que se haya seleccionado un nuevo elemento en el listbox.


Ir al índice de los artículos de RFOG en el sitio del Guille




La fecha/hora en el servidor es: 23/12/2024 20:32:55

La fecha actual GMT (UTC) es: 

©Guillermo 'guille' Som, 1996-2024