Al recibir un mensaje una ventana decide como procesarlo y la gran mayoria de ventanas responden de la misma forma ante los mismos mensajes. Por ejemplo ante un mensaje "cerrar" la ventana se cierra, pero si lo interceptamos podriamos hacer un programa que en lugar de cerrarse le muestre un mensaje al usuario o que simplemente no se cierre o que haga cualquier otra cosa.
Si no sabes que es un mensaje en windows te recomiendo que primero leas el artículo Como funciona el sistema de ventanas en windows
Entendiendo como se procesan los mensajes
Cada vez que una ventana recibe un mensaje este pasa por la funcion WndProc que detecta el tipo de mensaje y procede según corresponda.
Para poder interceptar mensajes es necesario que hagamos nuestra propia implementacion de esta función de tal forma que los mensajes ya no pasen por la función por defecto sino por la nuestra.
Tienes que tener en cuenta que son muchisimos los mensajes que puede recibir una ventana y no vamos a poder responder a todos. Si dejamos de responder ante algún mensaje nuestra ventana dejará de funcionar como lo esperado. Me explico, si no procesamos el mensaje "cerrar" nuestra ventana no se cerrará, si no procesamos el mensaje "dibujar" nuestra ventana no se mostrará y asi sucesivamente.
Para evitar procesar todos los mensajes lo que tenemos que hacer es interceptar los que queramos y todo el resto de mensajes se los pasamos a la función original WndProc.
Aqui coloco el código de mi función wndproc que debería ir dentro de la implementación de tu formulario:
Visual Studio
Code Gear
Visual C#
public ref class Form1 : public System::Windows::Forms::Form
{
protected: virtual void WndProc(Message %m) override
{
switch (m.Msg)
{
case <mensaje 1>:
/* Procesamos el mensaje 1 */
break;
case <mensaje 2>:
/* Procesamos el mensaje 2 */
break;
default:
// Invocamos a la funcion original
Form::WndProc(m);
break;
}
}
...
// El resto de funciones del formulario
// No te olvides de agregar la cabecera
// en el .h del formulario bajo protected o public
void __fastcall TForm1::WndProc(TMessage &m)
{
switch (m.Msg)
{
case <mensaje 1>:
/* Procesamos el mensaje 1 */
break;
case <mensaje 2>:
/* Procesamos el mensaje 2 */
break;
default:
// Invocamos a la funcion original
TForm::WndProc(m);
break;
}
}
public partial class Form1 : Form
{
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case <mensaje 1>:
/* Procesamos el mensaje 1 */
break;
case <mensaje 2>:
/* Procesamos el mensaje 2 */
break;
default:
// Invocamos a la funcion original
base.WndProc(ref m);
break;
}
}
...
// El resto de funciones del formulario
Ten en cuenta que los case del switch los usaré para detectar el tipo de mensaje y el default se ejecutará para todos los mensajes que no intercepte llamando a la función por defecto (de la clase padre).
Además ten en cuenta que donde dice <mensaje 1> y <mensaje 2> colocaremos los mensajes de windows que vamos a interceptar.
Un típico mensaje de windows es el de cerrar que está definido como la constante WM_QUIT equivalente al número en hexadecimal 0x0012. Si colocamos el valor hexadecimal funcionará siempre mientras que si colocamos WM_QUIT necesitamos que esta constante esté definida por lo que es necesario incluir primero la librería windows.h.
Diferencia entre WndProc y los eventos del formulario
Normalmente, lo que siempre programamos va dentro de los eventos del formulario como por ejemplo el evento click del formulario o el evento close.
Sin embargo el WndProc está antes de dichos evento. Para que esto quede más claro te explico cual es el flujo del mensaje desde que llega a la ventana hasta que se levanta el evento del formulario.
- El mensaje llega a la ventana.
- El mensaje entra a la función WndProc.
- En el WndProc se procesa el comportamiento por defecto de dicho mensaje.
- Después de ejecutado el WndProc se invoca al evento y por lo tanto se ejecuta nuestro código.
Como puedes ver, el WndProc se llama mucho antes que al evento, inclusive se llama antes de que pase cualquier cosa por lo que si lo podemos interrumpir podemos cambiar el comportamiento por defecto.
En general para mí hay dos ventajas de utilizar el WndProc en lugar de eventos para ciertos casos:
- Podemos modificar el comportamiento por defecto ante los mensajes.
- No existen eventos para todos los mensajes que recibe nuestra ventana. Por ejemplo no hay un evento que nos permita detectar cuando alguien le hace clic a la barra de título de nuestro programa.
Ejemplos de mensajes
A continuación te voy a mostrar una serie de ejemplos de como interceptar algunos mensajes para que entiendas la potencia que nos puede dar la funcion WndProc.
El mensaje mouse down
El mensaje mouse down viene en una gran variedad de versiones. En este caso te voy a enseñar solamente dos.
| Hex | Constante | Descripción |
|---|---|---|
0x0201 |
WM_LBUTTONDOWN |
Cuando se ha hecho clic en la parte cliente de la ventana. La parte cliente es toda la parte interna de la ventana. |
0x00A1 |
WM_NCLBUTTONDOWN |
Cuando se ha hecho clic en la parte NO cliente de la ventana. La parte NO cliente incluye los bordes y la barra de título. |
En este ejemplo simplemente voy a reducir la ventana cuando hagas clic en la parte cliente y cambiar el color del formulario cuando hagas clic en la parte no cliente.
Visual Studio
Code Gear
Visual C#
protected: virtual void WndProc(Message %m) override
{
switch (m.Msg)
{
case 0x0201: // WM_LBUTTONDOWN
Width -= 10;
Height -= 10;
break;
case 0x00A1: // WM_NCLBUTTONDOWN
BackColor = Color::FromArgb(rand()%255, rand()%255, rand()%255);
break;
default:
// Invocamos a la funcion original
Form::WndProc(m);
break;
}
}
void __fastcall TForm1::WndProc(TMessage &m)
{
switch (m.Msg)
{
case 0x0201: // WM_LBUTTONDOWN
Width -= 10;
Height -= 10;
break;
case 0x00A1: // WM_NCLBUTTONDOWN
Color = (TColor)RGB(rand()%255, rand()%255, rand()%255);
break;
default:
// Invocamos a la funcion original
TForm::WndProc(m);
break;
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0x0201: // WM_LBUTTONDOWN
Width -= 10;
Height -= 10;
break;
case 0x00A1: // WM_NCLBUTTONDOWN
Random r = new Random();
BackColor = Color.FromArgb(r.Next(255), r.Next(255), r.Next(255));
break;
default:
// Invocamos a la funcion original
base.WndProc(ref m);
break;
}
}
Cuando ejecutes tu programa ahora puedes ver que cuando haces clic en la parte interna la ventana se reduce en 10 pixeles a cada lado y cuando haces clic en la barra de título o si quieres cambiar el tamaño o tratar de cerrar o maximizar o minimizar tu programa simplemente cambia de color. Para cerrar tu ventana presiona Ctrl + F4 o detén la ejecución desde tu IDE.
Que pasa si no queremos que nuestra aplicación deje de moverse, cambiar de tamaño o cerrarse, es decir, no quiero que deje de funcionar como antes simplemente quiero que ahora adicionalmente cambie de color.
La respuesta es muy simple, basta con invocar a la función original mandándole el mensaje. Mira la primera línea del case WM_NCLBUTTONDOWN en el siguiente código:
Visual Studio
Code Gear
Visual C#
protected: virtual void WndProc(Message %m) override
{
switch (m.Msg)
{
case 0x0201: // WM_LBUTTONDOWN
Width -= 10;
Height -= 10;
break;
case 0x00A1: // WM_NCLBUTTONDOWN
Form::WndProc(m);
BackColor = Color::FromArgb(rand()%255, rand()%255, rand()%255);
break;
default:
// Invocamos a la funcion original
Form::WndProc(m);
break;
}
}
void __fastcall TForm1::WndProc(TMessage &m)
{
switch (m.Msg)
{
case 0x0201: // WM_LBUTTONDOWN
Width -= 10;
Height -= 10;
break;
case 0x00A1: // WM_NCLBUTTONDOWN
TForm::WndProc(m);
Color = (TColor)RGB(rand()%255, rand()%255, rand()%255);
break;
default:
// Invocamos a la funcion original
TForm::WndProc(m);
break;
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0x0201: // WM_LBUTTONDOWN
Width -= 10;
Height -= 10;
break;
case 0x00A1: // WM_NCLBUTTONDOWN
base.WndProc(ref m);
Random r = new Random();
BackColor = Color.FromArgb(r.Next(255), r.Next(255), r.Next(255));
break;
default:
// Invocamos a la funcion original
base.WndProc(ref m);
break;
}
}
Ahora tu ventana funciona normalmente pero cambia de color cuando el clic termina. Si quieres que cambie de color antes de que el clic termine entonces mueve la línea que invoca a la función original justo antes del break para que la función se invoque después de que cambiaste el color.
El mensaje de comandos del sistema con parámetros
Cada vez que en una ventana se realiza una operación que tenga que ver con el menú de la ventana se invoca al mensaje WM_SYSCOMMAND (0x0112). Cuando me refiero al menú de la ventana me refiero a sus botones para maximizar, minimizar, cerrar, scroll horizontal y vertical, etc.
Cuando llega este mensaje necesitamos identificar con precisión que comando ha sido el invocado ya que hay varias alternativas. Para esto podemos consultar el WParam que llega con el mensaje.
En este ejemplo voy a deshabilitar las opciones Maximizar y Minimizar y voy a escribir en la barra de título el mensaje correspondiente para que el usuario sepa que hizo pero en realidad no va a pasar nada.
A continuación te describo los parámetros que voy a interceptar:
| Hex | Constante | Descripción |
|---|---|---|
0xF030 |
SC_MAXIMIZE |
Cuando se da clic sobre el ícono de maximizar. |
0xF032 |
SC_MAXIMIZE2 |
Cuando se da doble clic sobre la barra de título para maximizar la ventana. |
0xF020 |
SC_MINIMIZE |
Cuando se da clic sobre el ícono de minimizar. |
Visual Studio
Code Gear
Visual C#
protected: virtual void WndProc(Message %m) override
{
switch (m.Msg)
{
case 0x0112: // WM_SYSCOMMAND:
// Interceptamos el maximizar SC_MAXIMIZE(0xF030) y SC_MAXIMIZE2(0xF032)
if (m.WParam.ToInt32() == 0xF030 || m.WParam.ToInt32() == 0xF032)
{
this->Text = "Maximizado";
return;
}
// Interceptamos el minimizar SC_MINIMIZE(0xF020)
if (m.WParam.ToInt32() == 0xF020)
{
this->Text = "Minimizado";
return;
}
// Si es cualquier otro tipo
Form::WndProc(m);
break;
default:
// Invocamos a la funcion original
Form::WndProc(m);
break;
}
}
void __fastcall TForm1::WndProc(TMessage &m)
{
switch (m.Msg)
{
case 0x0112: // WM_SYSCOMMAND:
// Interceptamos el maximizar SC_MAXIMIZE(0xF030) y SC_MAXIMIZE2(0xF032)
if (m.WParam == 0xF030 || m.WParam == 0xF032)
{
this->Caption = "Maximizado";
return;
}
// Interceptamos el minimizar SC_MINIMIZE(0xF020)
if (m.WParam == 0xF020)
{
this->Caption = "Minimizado";
return;
}
// Si es cualquier otro tipo
TForm::WndProc(m);
break;
default:
// Invocamos a la funcion original
TForm::WndProc(m);
break;
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0x0112: // WM_SYSCOMMAND:
// Interceptamos el maximizar SC_MAXIMIZE(0xF030) y SC_MAXIMIZE2(0xF032)
if (m.WParam.ToInt32() == 0xF030 || m.WParam.ToInt32() == 0xF032)
{
this.Text = "Maximizado";
return;
}
// Interceptamos el minimizar SC_MINIMIZE(0xF020)
if (m.WParam.ToInt32() == 0xF020)
{
this.Text = "Minimizado";
return;
}
// Si es cualquier otro tipo
base.WndProc(ref m);
break;
default:
// Invocamos a la funcion original
base.WndProc(ref m);
break;
}
}
Nota: Si quieres deshabilitar todos los comandos, no solamente el maximizar y minimizar, simplemente comenta la última línea de este case.
Nota: Si usas Visual Studio prueba presionando Win + M para ver como todas las ventanas se minimizan pero la tuya se queda solamente que ahora muestra el título como Minimizado.
El mensaje cambio en la fuente de poder
El mensaje WM_POWERBROADCAST (0x0218) se le manda a todas las ventanas cada vez que hay un cambio en la fuente de poder. Uno de estos casos es cuando en una laptop cambiamos de batería a corriente.
En este caso simplemente le voy a cambiar el color al formulario cada vez que este mensaje llegue a mi aplicación.
Visual Studio
Code Gear
Visual C#
protected: virtual void WndProc(Message %m) override
{
switch (m.Msg)
{
case 0x0218: // WM_POWERBROADCAST
BackColor = Color::FromArgb(rand()%255, rand()%255, rand()%255);
Form::WndProc(m);
break;
default:
// Invocamos a la funcion original
Form::WndProc(m);
break;
}
}
void __fastcall TForm1::WndProc(TMessage &m)
{
switch (m.Msg)
{
case 0x0218: // WM_POWERBROADCAST
Color = (TColor)RGB(rand()%255, rand()%255, rand()%255);
TForm::WndProc(m);
break;
default:
// Invocamos a la funcion original
TForm::WndProc(m);
break;
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0x0218: // WM_POWERBROADCAST
Random r = new Random();
BackColor = Color.FromArgb(r.Next(255), r.Next(255), r.Next(255));
base.WndProc(ref m);
break;
default:
// Invocamos a la funcion original
base.WndProc(ref m);
break;
}
}
Para probar esto simplemente deja corriendo tu programa y conecta y desconecta el cable de corriente de tu laptop.
Mensajes de usuario
Adicionalmente a los mensajes de usuario tu puedes crear tus propios mensajes para que tus programas respondan entre sí y mandárselos con SendMessage. Tus mensajes deberían comenzar a partir del número WM_USER (0x400). Por lo general tus propios mensajes debes definirlos desde este número hacia adelante para que no interfieran con los mensajes de windows.
Lista de mensajes
No he encontrado una lista de mensajes completa en Internet, ni siquiera en la página de Microsoft pero aquí te dejo con un par de links.
Lo que si hay en Microsoft, en la parte de documentación MSDN, es una descripción de todos los mensajes de windows, simplemente busca el mensaje en base a su constante por ejemplo WM_QUIT y obtendrás toda la información que necesitas.
Resumen
En este artículo solo he colocado tres ejemplos de mensajes para no hacerlo muy largo pero que te quede claro que hay muchos mensajes más y las posibilidades son limitadas simplemente por tu creatividad así que comienza a jugar y probar con los diferentes mensajes para que encuentres cosas increibles que no creías que podías hacer.

Este truquillo con WndProc permite personalizar mucho el comportamiento de las ventanas.
Este artículo va directo a Mis Favoritos