domingo, 6 de febrero de 2011

Creando un Boton personalizado

Aburrido de que los botones siempre luzcan iguales me puse a investigar un poco... Mi idea era que el boton luzca de una manera cuando el cursor del mouse esta posicionado sobre el botón y de otra manera cuando el cursor este fuera del botón. Mirando la infinidad de mensajes que tiene el windows, di con WM_MOUSEHOVER y WM_MOUSELEAVE, ¡exactamente lo que estaba buscando!, pero no todo es color de rosas, por defecto los botones en MFC tienen pocos menajes, y justamente estos dos que necesito ¡no están! así que luego de investigar 3 minutos (gracias google) di con cómo hacer para que el botón capture estos mensajes.

Como dije antes la idea es que el botón se vea de una forma cuando el cursor esta arriba él y de otra cuando el cursor esta fuera, para ello le pasamos dos HBITMAP que previamente cargamos a la función Crear() . Como se van a mostrar imágenes no es necesario pasarle texto ni los estilos, estos los predefino yo.

Para lograr que el botón tenga los mensajes WM_MOUSEHOVER y WM_MOUSELEAVE es necesario:
  • Agregar ON_WM_MOUSEMOVE() al mapa de mensajes
  • Llamar a la función TrackMouseEvent() en la funcion OnMouseMove(). 
  • Agregar ON_MESSAGE(WM_MOUSEHOVER,OnMouseHover) y ON_MESSAGE(WM_MOUSELEAVE,OnMouseLeave) al mapa de mensajes.
  • Implementar las funciones OnMouseLeave() y OnMouseHover().
  • nada más, por ahora...
Bien la parte "complicada" ya está, ahora ¿cómo joraca hago que muestre una imagen cuando el mouse esta sobre el botón y que muestre otra imagen cuando sale?, bien esta es la forma que se me ocurrió a mi, quizás existan formas mas "elegantes" de hacerlo

#define MIBOTON
#ifdef MIBOTON

class CMiBoton : public CButton{
private:
HBITMAP m_MouseSobre, m_MouseFuera;
TRACKMOUSEEVENT m_tme;
public:
void Crear(HBITMAP Sobre,HBITMAP Fuera ,CRect &r, CWnd *VentanaPadre, UINT nID){
this->Create(NULL, WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_BITMAP, r,VentanaPadre, nID);
m_tme.cbSize = sizeof(m_tme);
m_tme.hwndTrack = m_hWnd;
m_tme.dwFlags = TME_LEAVE|TME_HOVER;
m_tme.dwHoverTime = 1;
m_MouseSobre = Sobre;
m_MouseFuera = Fuera;
this->SetBitmap(m_MouseFuera);
}
protected:
void OnMouseMove(UINT nFlag, CPoint Point){
TrackMouseEvent(&m_tme);
}
LRESULT OnMouseHover(WPARAM wparam, LPARAM lparam){
this->SetBitmap(m_MouseSobre);
return 0;
}
LRESULT OnMouseLeave(WPARAM wparam, LPARAM lparam){
this->SetBitmap(m_MouseFuera);
return 0;
}
DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMiBoton, CButton)
ON_WM_MOUSEMOVE()
ON_MESSAGE(WM_MOUSEHOVER,OnMouseHover)
ON_MESSAGE(WM_MOUSELEAVE,OnMouseLeave)
END_MESSAGE_MAP()
#endif

lunes, 27 de diciembre de 2010

Lección 2: El Message Map...

O al menos parte de él, esto es lo poco que pude aprender hasta el momento.
Resulta que como en el API de windows tenemos un bucle de mensajes aca tenemos un Mapa de Mensajes, son en esencia lo mismo -eso creo- aunque poseen algunas diferencias.
En primer lugar existen unas macro como DECLARE_MESAGE_MAP(), BEGIN_MESSAGE_MAP(...) y END_MESSAGE_MAP() necesarias para poder manejar todo el tema de los eventos.
  • DECLARE_MESAGE_MAP()debe declararse al final de nuestra clase que hereda de CFrameWnd(en el tutorial sería CMyFrame).
  •  BEGIN_MESSAGE_MAP(...) recive dos parámetros, en el primero se debe mandar CMyFrame y en el segundo la clase padre de CMyFrame, es decir CFrameWnd y es la encargada de iniciar la definición del mapa de mensajes.
  • Por último, tenemos END_MESSAGE_MAP(), no tiene parámetros, y es la encargada de terminar la definición del mapa de mensajes.
Ya con estos puntos resumidos pasemos a un ejemplo bien sencillo:

#define _AFXDLL
#include < afxwin.h >

class CMyFrame;
class CMyApp;

class CMyFrame : public CFrameWnd{
friend class CMyApp;
public:
   CMyFrame(){Create(NULL, "Juampi");}
   void OnLButtonDown (UINT nFlags, CPoint Point);
   void OnRButtonDown (UINT nFlags, CPoint Point);
   DECLARE_MESSAGE_MAP() //sin ; ni nada, asi nomas como esta
};

BEGIN_MESSAGE_MAP(CMyFrame, CFrameWnd)
   ON_WM_LBUTTONDOWN()
   ON_WM_RBUTTONDOWN()
END_MESSAGE_MAP()

void CMyFrame::OnLButtonDown(UINT nFlags, CPoint Point){
/*Aca metemos el código de lo que queremos que pase cuando apretamos el boton izquierdo del mouse sobre nuestra ventana*/
MessageBox("Izquierdo");
}

void CMyFrame::OnRButtonDown(UINT nFlags, CPoint Point){
/*Aca metemos el código de lo que queremos que pase cuando apretamos el boton derecho del mouse sobre nuestra ventana*/
MessageBox("Derecho");
}

class CMyApp: public CWinApp{
private:
   CMyFrame *MyFrame;
public:
   BOOL InitInstance(){
      MyFrame = new CMyFrame;
      m_ pMainWnd = MyFrame;
      m_pMainWnd->ShowWindow(1)
      return true;
   }
};

CMyApp MyApp;

Si mi lógica no me falla, esto tendría que hacer que cuando apretemos el boton izquierdo sobre la ventana nos aparezca un mensaje que dice "Izquierdo" y si apretamos el derecho, el mensaje dirá "Derecho"

domingo, 26 de diciembre de 2010

Lección 1: Creando una Ventana con MFC

Antes que nada y a modo de introducción voy a aclarar algunas cositas: 
  • Voy a prendiendo mientras escribo este blog.
  • Todos los ejemplos son probados en Visual C++ 2010 Profesional 
  • Nada más
Ahora si, nos metemos con las MFC o Microsoft Found-no-se-que classes
La "Microsoft Foundation Classes" es una librería que "envuelve" porciones de la API de windows en clases de c++(según la wikipedia)

MFC nos provee de dos clases bastante importantes, CWinApp y CFrameWnd. CWinApp es la que posee las funcionalidades a nivel aplicación y CFrameWnd es la encargada de manejar la GUI. Ambas heredan de CCmdTarget, clase encargada de manejar el mapa de mensajes que a su ves hereda de CObject, "The principal base class for the Microsoft Foundation Class Library." según la microsoft.

 CWinApp posee una función(operación, método, como quieran llamarla) virtual ("sobre-escribible") muy importante llamada InitInstance(), encargada de la inicialización de la instancia, y un miembro m_pMainWin que es un puntero a la ventana que vamos a programar.

Ya sabiendo todo esto, empecemos con un pequeño ejemplo, generamos un nuevo proyecto "WIN32" desde el Visual Studio 2010, y seleccionamos la opción "proyecto vacío", luego creamos un archivo de codigo fuente "MFC.cpp" y copiamos lo que esta aca abajo

#define _AFXDLL
#include < afxwin.h >

class CMyFrame;
class CMyApp;

class CMyFrame:public CFrameWnd{
    friend class CMyApp;
    CMyFrame(){
      Create(NULL,"Juampi");
    }
};

class CMyApp:public CWinApp{
private:
    CMyFrame *MyFrame;
public:

    BOOL InitInstance(){
     MyFrame = new CMyFrame();
     m_pMainWnd = MyFrame;
     m_pMainWnd->ShowWindow(1);
     return true;
   }
};

CMyApp MyApp;

Seguramente va a saltar un error de compilación, para solucionarlo vamos a "Proyecto->Propiedades de ...->Propiedades de configuración->General->Juego de Caracteres y lo ponemos en "multibyte"
Ahora si podemos ver que aparece una ventana sin nada adentro y que de título dice "Juampi"