[C con Clase] consulta

Steven Davidson srd4121 en njit.edu
Dom Oct 9 14:01:46 CEST 2011


Hola Santiago,

2011/10/8 Santiago Tabarez <santiago230792 en gmail.com>:
> Hola. Tengo una consulta:

Antes de continuar, te doy la bienvenida a esta lista de correo-e.

> El otro día, revisando los archivos del API de Windows, encontré una opción
> para incluir una barra de estado en mi programa. Me pareció buena idea
> intentar implementar esta utilidad, para mostrar información acerca de los
> comandos. Pero luego me puse a pensar que sería mejor poder agregarle un
> botón a la barra de estado, ya que sería aún mas útil, y ahorraría espacio.
> Pude agregarle las opciones (con ayuda de su muy buen curso), pero a la hora
> de pulsar ese botón, no funciona el comando que le asigné. No sé si podrán
> ayudarme a ver qué es lo que hice mal o qué omití.
> Para ello les envió un adjunto simplificado como ejemplo.
> Agradezco su respuesta.
>

El problema es que el botón es una ventana hija de la barra de estado
la cual es una ventana hija de la ventana que es tu aplicación. Por lo
tanto, no podrás conseguir los mensajes enviados por tus "nietas". El
caso es que el botón estaría enviando mensajes a la barra de estado -
su pariente - y no a "tu ventana", que sería su abuelo.

La forma más profesional y suficientemente sencilla de solucionar este
problema es modificando el procedimiento de ventana de la barra de
estado para que procese el mensaje 'WM_COMMAND' para su ventana hija
del botón. Para esto, debemos implementar una técnica llamada
"subclase de ventana", que la verdad se parece bastante a la herencia
de la POO. A partir de la versión 5.8 de "comctl32.dll" y a partir de
MS-Windows XP, podemos usar la función 'SetWindowSubclass()'. Por
ejemplo,

// Nos aseguramos que desarrollamos para XP
#define _WIN32_WINNT 0x0501
#define _WIN32_IE	0x0501
...
// Subclase para la barra de estado que contiene un botón
LRESULT CALLBACK SBWndProc( HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam,
                            UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
  if( WM_COMMAND == message && ID_STATUSBUTTON == LOWORD(wParam) )
  {
    // Procesamos el botón ID_STATUSBUTTON
    ...
    return 0;
  }

  // Deja que la ventana procese con normalidad los demás mensajes
  return DefSubclassProc( hWnd, message, wParam, lParam );
}

En el procedimiento de tu ventana principal, estableceríamos la
siguiente subclase para la barra de estado:

case WM_CREATE:
{
  /* Creamos la barra de estado */
  hStatusBar = CreateWindowEx( ... );
  ...
  SetWindowSubclass( hStatusBar, SBWndProc, 0, 0 );
  return 0;
}

Eso sí, tenemos que eliminar la subclase al terminar de usarla. Como
mucho tardar mientras destruimos la ventana principal. Esto es,

case WM_DESTROY:
  RemoveWindowSubclass( hStatusButton, SBWndProc, 0 );

También podríamos optar por invocar esta función dentro de nuestro
procedimiento de ventana de la barra de estado. Esto es,

LRESULT CALLBACK SBWndProc( HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam,
                            UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
  if( WM_DESTROY == message )
    RemoveWindowSubclass( hWnd, SBWndProc, 0 );

  if( WM_COMMAND == message && ID_STATUSBUTTON == LOWORD(wParam) )
  {
    ...
    return 0;
  }

  return DefSubclassProc( hWnd, message, wParam, lParam );
}


Claro está, si quieres permitir que el procedimiento de la ventana
principal se encargue de recibir los mensajes del botón que está en la
barra de estado, entonces simplemente enviamos esos mensajes a nuestra
ventana. Esto es,

// Subclase para la barra de estado que contiene un botón
LRESULT CALLBACK SBWndProc( HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam,
                            UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
  if( WM_COMMAND == message && ID_STATUSBUTTON == LOWORD(wParam) )
  {
    // Enviamos a la cola de mensajes de la ventana pariente
(principal) los del botón ID_STATUSBUTTON
    PostMessage( GetParent(hwnd), message, wParam, lParam );
    return 0;
  }

  // Deja que la ventana procese con normalidad los demás mensajes
  return DefSubclassProc( hWnd, message, wParam, lParam );
}


Por cierto, te corrijo algunas cosas que he visto en tu código fuente:

- Sugiero que te acostumbres a usar 'TCHAR' en lugar de 'char', cuando
te sea posible, claro está. Hay muchas funciones "nuevas" que sólo
funcionan con Unicode. Por ejemplo,

TCHAR tszClassName[ ] = TEXT( "WindowsApp" );  // Convierte la cadena
literal a Unicode o la deja como ASCII
...
hwnd = CreateWindowEx (
           WS_EX_CLIENTEDGE,
           szClassName,
           TEXT( "Practicando Windows" ),
           ... );

- En 'WinMain()', sugiero que invoques 'InitCommonControlsEx()' para
asegurarte de que la biblioteca "comctl32.dll" de los controles
comunes se cargue correctamente al principio de tu aplicación y que
las funciones de los controles que quieres estén a tu disposición. En
tu caso, esto sería,

INITCOMMONCONTROLSEX iccex = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES };
if( !InitCommonControlsEx( &iccex ) )  return 0;

- En 'WM_CREATE', escribes:

/* Lo enviamos a esta */
SendMessage(hStatusBar, ID_STATUSBUTTON, 2, 0);

Esto no tiene sentido. Ten presente que el segundo parámetro debe
representar un mensaje, pero en tu caso, envías el número de
identificación del botón como mensaje.

Puedes eliminar esta sentencia sin problemas.

- A continuación, creas un tipo de fuente:

hfont = CreateFont( ... );

Pero se te ha olvidado eliminar este recurso después de necesitarlo. A
más tardar, "destruye" este recurso al procesar el mensaje
'WM_DESTROY' de tu ventana principal. Esto es,

case WM_DESTROY:
  DeleteObject( hfont );

- En 'WM_SIZE', escribes:

MoveWindow( (HWND)szClassName, 0, uToolHeight, rectClient.right,
uClientAlreaHeight - uStatusHeight - uToolHeight, TRUE );

El primer parámetro no tiene sentido. En primer lugar, 'szClassName'
contiene el nombre de la clase de tu ventana principal, pero
'MoveWindow()', como la gran mayoría de las funciones del API,
requiere el manipulador de la ventana a mover. En este caso, creo que
necesitas mover la barra de estado, por lo que sería,

MoveWindow( hStatusBar, 0, uToolHeight, rectClient.right,
uClientAlreaHeight - uStatusHeight - uToolHeight, TRUE);

Dicho esto, no es necesario enviar el mensaje 'WM_SIZE', que hiciste
previamente:

SendMessage( hStatusBar, WM_SIZE, 0, 0 );

Simplemente elimina esta sentencia.


Espero que todo esto te ayude.

Steven




Más información sobre la lista de distribución Cconclase