Introducción:
Tras
haber visto cómo heredar y sustituir un control dentro de un cuadro de
diálogo, ahora vamos a ver cómo aprovechar la infraestructura del propio
control mediante la sobreescritura de métodos virtuales.
En la primera entrada de esta serie vimos cómo se podía
sustituir un control existente por uno nuestro heredado del sustituido y
cómo MFC soporta esta práctica sin problemas y sin florituras.
Ahora es el momento de ver qué tan buenos son los
programadores de Microsoft y cómo nos han dado soporte extendido para este
control ya que, como vimos, no hay nada de forma inmediata.
Si os dais cuenta, tampoco hay documentación sobre el
control, apenas unas líneas describiendo qué hace… No os extrañéis, pero
esto es bastante habitual no sólo con Microsoft, sino con la mayora de
implementadores de extensiones para MFC, y la verdad es que cabrea mucho,
pero es lo que hay.
La solución, algo bastante habitual en C++, es navegar
a través del código fuente de MFC hasta encontrar el del componente.
Podríamos buscar la carpeta con los fuentes de MFC, y luego con alguna
utilidad de búsqueda dentro de ficheros, encontrar la definición de la
clase, pero hay otra forma más rápida, y es a través de la depuración.
Nos vamos a MyVSListbox.cpp y ponemos un punto de
interrupción en el constructor de la clase. Ejecutamos y el código se nos
parará en dicho lugar. Luego le damos a “step into” y ¡tachán!, el IDE ha
hecho nuestro trabajo:
Él solo ha encontrado no solo el fichero fuente, sino
la propia definición de la clase. Únicamente nos falta apuntar con el ratón
a algún elemento de la clase y elegir “Go To declaration”. En este caso se
nos abre una ventana de “Find Symbols” porque Visual Sutdio no tiene del
todo claro dónde está la clase. Pero si miramos en la ventana lo que está
haciendo es apuntarnos a dos lugares de la misma clase y del mismo fichero.
Elegimos uno de ellos y se nos abre el fichero “faxvslistbox.h”, y ahí está
la declaración de nuestra clase.
[Nota: esto también podríamos haberlo hecho desde el
principio sin necesidad de lanzar el depurador, pero así os damos otra forma
más para hacerlo y para navegar dentro del código fuente de MFC].
¿Qué soporte nos han dado? Pues si vemos la clase,
observamos que lo único que se ha hecho es redefinir algunos métodos ya
existentes en un cuadro de lista normal y corriente y capturar algunos
mensajes (y ese es el motivo por el que nosotros no vemos los mensajes: los
métodos de captura ni son virtuales ni permiten que se siga llamando a la
cadena de mensajes).
Debemos, pues, seguir mirando en la clase padre, que si
nos fijamos es CVSListBoxBase. Repitiendo el “Go To Declaration” se nos
abrirá la clase en cuestión, que resulta está en el mismo fichero.
Y ahora sí, ahora sí que hemos encontrado algo
interesante:
virtual void
OnSelectionChanged() {}
// "Standard" action overrides
virtual BOOL
OnBeforeRemoveItem(int
/*iItem*/) {
return TRUE; }
virtual void
OnAfterAddItem(int
/*iItem*/) {}
virtual void
OnAfterRenameItem(int
/*iItem*/) {}
virtual void
OnAfterMoveItemUp(int
/*iItem*/) {}
virtual void
OnAfterMoveItemDown(int
/*iItem*/) {}
Eso es lo que queríamos encontrar: cómo reaccionar ante
los eventos más comunes que pueden ocurrir en un cuadro de lista: El cambio
de elemento seleccionado, el acto de añadir, renombrar o cambiar de sitio un
elemento. Y una especial que si devolvemos FALSE el elemento no será
eliminado.
Vemos, por ejemplo, que los métodos son virtuales, por
lo que cualquier hijo que los implemente será llamado independientemente de
si ha sido instanciado desde el padre o desde el propio hijo, por lo que nos
viene como anillo al dedo para nuestros propósitos: nuestra CMyVSListbox
implementa cualquiera de dichos métodos, y debería funcionar.
¿Cómo sabemos que dichos métodos funcionan tal y como
os he contado?: pues o bien ponemos puntos de interrupción en ellos y
comprobamos cómo funcionan o bien nos vamos leyendo el código. Y desde luego
hacer ambas cosas no es excluyente.
En el caso que nos ocupa, el programador ha capturado
los mensajes pertinentes de cada control y nos ha integrado un interfaz
común, cosa que, en este caso, es de agradecer a primera vista, pero nos
impide capturar los mensajes en las clases propietarias (el cuadro de
diálogo en nuestro caso).
El primer paso, para evitarnos errores, es copiar tal
cual el código de las funciones de ese fichero cabecera en nuestra propia
clase, y compilar a ver qué pasa:
class
CMyVSListbox :
public CVSListBox
{
DECLARE_DYNAMIC(CMyVSListbox)
public:
CMyVSListbox();
virtual ~CMyVSListbox();
virtual void
OnSelectionChanged() {}
// "Standard" action overrides
virtual BOOL
OnBeforeRemoveItem(int
/*iItem*/) {
return TRUE; }
virtual void
OnAfterAddItem(int
/*iItem*/) {}
virtual void
OnAfterRenameItem(int
/*iItem*/) {}
virtual void
OnAfterMoveItemUp(int
/*iItem*/) {}
virtual void
OnAfterMoveItemDown(int
/*iItem*/) {}
protected:
DECLARE_MESSAGE_MAP()
};
Vemos que todo funciona perfecto, y que ahora se está
llamando a nuestros nuevos métodos en lugar de a los de la clase
CVSListBoxBase.
Ya sólo nos queda rellenar el cuerpo de dichos métodos
con nuestro código.
Este comportamiento es muy similar al de los mensajes
por reflexión: es el propio control el que trastea con sus propios mensajes
en lugar de ser el contenedor o el propietario, como se hace en los cuadros
de diálogo. En la entrega siguiente veremos cómo solventar esto.
Ir al índice de los artículos de RFOG en el
sitio del Guille