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

Colocando controles normales dentro de una ventana

 

Autor: RFOG (MVP de Visual C++)
Publicado: 27/Abr/2010 (21/Abr/2010)
Actualizado: 27/Abr/2010

Añadir controles normales dentro de una ventana como si estuviéramos en un cuadro de diálogo.




 

Introducción:

En esta primera parte vamos a ver cómo podemos añadir controles normales dentro de una ventana como si estuviéramos en un cuadro de diálogo, pero con la ventaja de que se trata de una ventana normal y corriente y por tanto se puede redimensionar y usar como tal (por ejemplo como una vista dentro del modelo Documento/Vista).

La pega es que hay que hacerlo al viejo estilo, es decir, a mano.

En detalle

Bueno, vamos allá. Lo primero de todo es tener una ventana, algo que dicho así a bote pronto puede sonar bastante evidente, no lo es, porque ¿cómo creamos una ventana desde cero en MFC?

Pues sencillo, nos vamos al proyecto en el Explorador de Soluciones, hacemos clic con el botón derecho y elegimos añadir nueva clase. Entonces se nos abre el asistente (sí, los de C++ también tenemos de eso), como sale en la imagen y seleccionamos “Clase MFC”.

MFC05_01

 

Cuando la añadamos llegaremos a la siguiente pantalla, en la que pondremos en el nombre de la clase el que queramos darle y elegiremos la clase base, que podrá ser CWnd, CPropertyPage,  CDockablePane o cualquier otra clase que tenga funciones de ventana.

MFC05_02

 

Ahora ya tenemos nuestra clase de ventana, y es cuestión de irle añadiendo funcionalidad. Podemos usar el Class Wizard, pero lo vamos a hacer a mano.

Usualmente en una ventana se suele sobreescribir el método virtual PreCreateWindow () y también se debe sobreescribir el también virtual Create() (equivalente al OnCreate() de un cuadro de diálogo). En el primero podremos cambiar el estilo de la ventana antes de que sea creada y en el segundo tendremos que añadir la funcionalidad extra.

Para el ejemplo que os quiero demostrar, PreCreateWindow() toma esta forma:

BOOL MiVentana::PreCreateWindow(CREATESTRUCT& cs) 
{
    if (!CWnd::PreCreateWindow(cs))
        return FALSE;

    cs.dwExStyle |= WS_EX_CLIENTEDGE;
    cs.style &= ~WS_BORDER;
    cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, 
        ::LoadCursor(NULL, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), NULL);

    return TRUE;
}

El método Create() por defecto tiene este aspecto:

BOOL MiVentana::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, 
    const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)
{
    CWnd::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext);
    
    return TRUE;
}

Como vemos estamos llamando al Create() de la clase padre (por cierto, Visual C++ 2010 añade la palabra reservada __super para llamar al método miembro de la clase padre).

Hasta ahora tenemos una ventana normal y corriente pero sin borde (se lo hemos quitado en PreCreateWindow() ).

Para terminar de añadirle el esqueleto, tenemos que capturar el mensaje WM_SIZE y sobreescribir un nuevo método miembro  llamado AdjustLayout(). Este último método contendrá el código que redimensionará los controles hijos de la ventana de forma uniforme, y lo hacemos así porque es un método que también tiene MFC y usa a veces y porque tenemos que llamarlo en varios sitios.

Si habéis seguido los pasos explicados, tendréis un fichero cabecera parecido a este:

class MiVentana: public CWnd
{
    DECLARE_DYNAMIC(MiVentana)

public:
    MiVentana();
    virtual ~ MiVentana();
    virtual BOOL Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, 
        const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext = NULL);

// Overrides
protected:
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    DECLARE_MESSAGE_MAP()
private:
    CListBox m_items;
    void AdjustLayout();
};

Y habremos añadido al mapa de mensajes la macro correspondiente:

BEGIN_MESSAGE_MAP(MiVentana, CWnd)
	ON_WM_SIZE()
END_MESSAGE_MAP()

También tendremos dos nuevos métodos:

void MiVentana::OnSize(UINT nType, int cx, int cy)
{
    CWnd::OnSize(nType, cx, cy);

    AdjustLayout();
}
void MiVentana::AdjustLayout()
{
    if (GetSafeHwnd() == NULL)
    {
        return;
    }
}

Ahora ya podremos ir añadiendo el código que nos hace falta para que todo funcione. Si os fijáis en el fichero cabecera, hemos puesto en la sección privada un dato miembro llamado m_items que es un control de lista y que vamos a embeber dentro de la ventana. Por lo tanto, para que el control se cree a la vez que nuestra ventana, tenemos que modificar el método Create() y añadir la creación del cuadro de lista. También llamaremos a AdjustLayout() para reorganizar el tema:

BOOL MiVentana::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle,
    const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)
{
    CWnd::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext);
    
    CRect rectDummy;
    rectDummy.SetRectEmpty();

    const DWORD dwViewStyle = WS_CHILD | WS_VISIBLE | WS_BORDER;
    if(!m_items.Create(dwViewStyle,rectDummy,this,8))
    {
        TRACE0("Failed to create times control\n");
        return -1;      // fail to create
    }
    
    AdjustLayout();

    return TRUE;
}

El hecho de pasar un RECT todo a cero nos ayudará a redimensionar el Listbox de forma adecuada en AdjustLayout().

Y finalmente tenemos que implementar dicho método:

void MiVentana::AdjustLayout()
{
    if (GetSafeHwnd() == NULL)
    {
        return;
    }

    CRect rectClient;
    GetOwner()->GetClientRect(rectClient);

    //TODO: Adjust child windows
    SetWindowPos(&wndTop, rectClient.left, rectClient.top, rectClient.Width(), rectClient.Height(),
                 SWP_SHOWWINDOW);

    if(m_items.m_hWnd!=0)
        m_items.SetWindowPos(NULL, rectClient.left, rectClient.top, rectClient.Width()/2, rectClient.Height(), 
                             SWP_NOACTIVATE | SWP_NOZORDER);
}

Si os fijáis, primero obtenemos el rectángulo cliente de la ventana padre (que es donde en la siguiente entrada encastraremos esta ventana). Dicho rectángulo podría ser cualquier que nos conviniera, incluso valores sacados de un archivo de configuración que nos habría guardado el tamaño de dicha ventana.

Primero redimensionamos la propia ventana a dicho tamaño cliente, y luego redimensionamos el cuadro de lista a la mitad del tamaño de la ventana. De este modo, cada vez que cambiemos el tamaño de la misma, nuestro ListBox ocupará, siempre, su mitad izquierda.

Conclusión

De este modo podremos añadir los controles que queramos, y de hecho es la forma que tiene el Feature Pack de hacerlo con ventanas mucho más complejas. Para que os hagáis una idea, todo el sistema de encastración de ventanas del propio Visual Studio funciona así, y en la entrada siguiente (o en la posterior), veremos cómo hacerlo.

 


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




La fecha/hora en el servidor es: 23/11/2024 12:27:56

La fecha actual GMT (UTC) es: 

©Guillermo 'guille' Som, 1996-2024