Al usar el estilo WS_THICKFRAME para permitir el redimensionamiento directo de ventanas laterales, se observan dos problemas principales:
- El tamaño de la ventana cambia continuamente, lo que provoca que todas las subventanas se reajusten y se refresquen de manera constante, causando parpadeos en la interfaz.
- Los bordes no destinados al movimiento también se pueden arrastrar, lo que no es deseado.
Para solucionar esto, se implementa un enfoque basado en eventos del ratón donde se captura y dibuja una región parcial del marco durante el movimiento. Este método evita la reorganización constante de la interfaz, y solo al soltar el botón del ratón se realiza el reajuste final, transmitiendo la posición al marco para la redistribución.
Además, se utiliza el contexto de dispositivo (DC) del marco para el dibujo, facilitando la conversión de coordenadas del ratón desde la pantalla al marco. Si se obtuvieran las coordenadas desde la ventana lateral, la conversión sería más compleja en áreas fuera de ella.
A continuación, se muestra un ejemplo de implementación medainte una clase personalizada.
Archivo de encabezado: SplitterLateral.h
#pragma once
#include "MarcoBase.h"
#include "UtilidadesUI.h"
class CSplitterLateral :
public CMarcoBase
{
public:
CSplitterLateral();
~CSplitterLateral();
XDECLARE_DYNAMIC(CSplitterLateral)
public:
HWND m_hControlPestanas;
HWND CrearPestanas(HWND hWndPadre);
CUtilidadMensajes* m_pUtilidadMensajes;
CUtilidadCadenas* m_pUtilidadCadenas;
CMarcoBase* m_pPrincipal;
HWND m_hMarcoPrincipal;
HWND m_hClientePrincipal;
virtual BOOL PreCrearVentana(WNDCLASSEX &cs);
virtual int AlDestruir(HWND, UINT, WPARAM, LPARAM);
int AlCrear(HWND, UINT, WPARAM, LPARAM);
int AlRedimensionar(HWND, UINT, WPARAM, LPARAM);
int AlCerrar(HWND, UINT, WPARAM, LPARAM);
int AlNotificar(HWND, UINT, WPARAM, LPARAM);
int AlTemporizador(HWND, UINT, WPARAM, LPARAM);
int AlPresionarIzq(HWND, UINT, WPARAM, LPARAM);
int AlSoltarIzq(HWND, UINT, WPARAM, LPARAM);
int AlMoverRaton(HWND, UINT, WPARAM, LPARAM);
HDC m_dc_memoria;
HBITMAP m_hBitmap_memoria;
int m_posX, m_posY;
int m_botonIzqPresionado;
XDECLARE_MAP_MENSAJES()
};
Archivo de implementación: SplitterLateral.cpp
#include "StdAfx.h"
#include "SplitterLateral.h"
#include "stdio.h"
#include "AppPrincipal.h"
XIMPLEMENT_DYNAMIC(CSplitterLateral, CMarcoBase)
XBEGIN_MAP_MENSAJES(CSplitterLateral, CMarcoBase)
XON_MENSAJE(WM_CREAR, AlCrear)
XON_MENSAJE(WM_REDIMENSIONAR, AlRedimensionar)
XON_MENSAJE(WM_CERRAR, AlCerrar)
XON_MENSAJE(WM_NOTIFICAR, AlNotificar)
XON_MENSAJE(WM_TEMPORIZADOR, AlTemporizador)
XON_MENSAJE(WM_BOTON_IZQ_PRESIONADO, AlPresionarIzq)
XON_MENSAJE(WM_BOTON_IZQ_LIBERADO, AlSoltarIzq)
XON_MENSAJE(WM_MOVIMIENTO_RATON, AlMoverRaton)
XEND_MAP_MENSAJES()
CSplitterLateral::CSplitterLateral()
{
wcscpy_s(szClaseVentana,_T("CSplitterLateral"));
wcscpy_s(szTitulo,_T("Splitter Lateral"));
m_pUtilidadMensajes = NULL;
m_botonIzqPresionado = 0;
}
CSplitterLateral::~CSplitterLateral()
{
delete m_pUtilidadMensajes;
}
HWND CSplitterLateral::CrearPestanas(HWND hWndPadre)
{
RECT rcCliente;
HWND hPestanas;
TCITEM itemPestana;
int i;
TCHAR achBuffer[4][16]={_T("Pestaña1"),_T("Pestaña2"),_T("Pestaña3"),_T("Pestaña4")};
InitCommonControls();
GetClientRect(hWndPadre, &rcCliente);
hPestanas = CreateWindow(WC_TABCONTROL, TEXT("ControlPestanas"),
WS_HIJO | WS_RECORTAR_HERMANOS | WS_VISIBLE | TCS_INFERIOR,
0, 0, 0, 0,
hWndPadre, (HMENU)ID_CONTROL_PESTANAS, m_hInstancia, NULL);
if (hPestanas == NULL)
{
return NULL;
}
HIMAGELIST hListaImagenes;
hListaImagenes = ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASCARA, 15, 15);
for(unsigned int i = 0 ; i < 15; i++)
{
HICON hIcono = LoadIcon(m_hInstancia, MAKEINTRESOURCE(IDI_ICONO_BASE + i));
ImageList_AddIcon(hListaImagenes, hIcono);
DestroyIcon(hIcono);
}
SendMessage(hPestanas, TCM_SETIMAGELIST, 0, (LPARAM)hListaImagenes);
itemPestana.mask = TCIF_TEXTO | TCIF_IMAGEN;
itemPestana.iImagen = -1;
for (i = 0; i < 3; i++)
{
itemPestana.pszText = achBuffer[i];
if (TabCtrl_InsertItem(hPestanas, i, &itemPestana) == -1)
{
DestroyWindow(hPestanas);
return NULL;
}
}
return hPestanas;
}
BOOL CSplitterLateral::PreCrearVentana(WNDCLASSEX &cs)
{
return TRUE;
}
extern HWND hVENTANA_IMPRESION;
int CSplitterLateral::AlCrear(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
long estilo;
estilo = GetWindowLong(hWnd, GWL_ESTILO);
estilo &= ~WS_MARCO_GRUESO;
SetWindowLong(hWnd, GWL_ESTILO, estilo);
m_pUtilidadMensajes = new CUtilidadMensajes();
m_pUtilidadMensajes->m_pMarcoPrincipal = m_pPrincipal;
m_pUtilidadCadenas = new CUtilidadCadenas();
m_pUtilidadCadenas->m_pMarcoPrincipal = m_pPrincipal;
m_hControlPestanas = CrearPestanas(hWnd);
m_pUtilidadMensajes->Crear(m_hInstancia, m_hControlPestanas);
m_pUtilidadCadenas->Crear(m_hInstancia, m_hControlPestanas);
hVENTANA_IMPRESION = m_pUtilidadMensajes->m_hWndEdicionMensajes;
TCITEM itemPestana;
itemPestana.mask = TCIF_TEXTO | TCIF_IMAGEN;
itemPestana.iImagen = 0;
itemPestana.pszText = _T("Mensajes");
itemPestana.lParam = (LPARAM)m_pUtilidadMensajes->m_hWnd;
TabCtrl_SetItem(m_hControlPestanas, 0, &itemPestana);
itemPestana.iImagen = 11;
itemPestana.pszText = _T("Cadenas");
itemPestana.lParam = (LPARAM)m_pUtilidadCadenas->m_hWnd;
TabCtrl_SetItem(m_hControlPestanas, 1, &itemPestana);
SetTimer(hWnd, 1, 1000, NULL);
return 0;
}
int CSplitterLateral::AlTemporizador(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
return 1;
}
int CSplitterLateral::AlRedimensionar(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
int anchoCliente, altoCliente;
anchoCliente = LOWORD(lParam);
altoCliente = HIWORD(lParam);
MoveWindow(m_hControlPestanas, 0, 6, anchoCliente, altoCliente - 6, TRUE);
RECT rt;
GetClientRect(m_hControlPestanas, &rt);
anchoCliente = rt.right - rt.left;
altoCliente = rt.bottom - rt.top;
MoveWindow(m_pUtilidadMensajes->m_hWnd, 3, 0, anchoCliente, altoCliente - 27, TRUE);
MoveWindow(m_pUtilidadCadenas->m_hWnd, 3, 0, anchoCliente, altoCliente - 27, TRUE);
return CMarcoBase::AlRedimensionar(hWnd, mensaje, wParam, lParam);
}
int CSplitterLateral::AlNotificar(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
RECT rt;
LPNMHDR lpHdr = (LPNMHDR)lParam;
if (wParam == ID_CONTROL_PESTANAS)
{
switch (lpHdr->code)
{
case TCN_CAMBIO_SELECCION:
PRINT(_T("\r\nCambio en pestañas"));
int seleccion = TabCtrl_GetCurSel(m_hControlPestanas);
switch(seleccion)
{
case 0:
PRINT(_T("\r\nPestaña 0"));
ShowWindow(m_pUtilidadMensajes->m_hWnd, SW_MOSTRAR);
ShowWindow(m_pUtilidadCadenas->m_hWnd, SW_OCULTAR);
break;
case 1:
PRINT(_T("\r\nPestaña 1"));
ShowWindow(m_pUtilidadMensajes->m_hWnd, SW_OCULTAR);
ShowWindow(m_pUtilidadCadenas->m_hWnd, SW_MOSTRAR);
break;
case 2:
PRINT(_T("\r\nPestaña 2"));
ShowWindow(m_pUtilidadMensajes->m_hWnd, SW_OCULTAR);
ShowWindow(m_pUtilidadCadenas->m_hWnd, SW_OCULTAR);
break;
}
break;
}
}
return ::DefWindowProc(hWnd, mensaje, wParam, lParam);
}
int CSplitterLateral::AlCerrar(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
return 0;
}
int CSplitterLateral::AlDestruir(HWND, UINT, WPARAM, LPARAM)
{
m_hWnd = NULL;
return 1;
}
int CSplitterLateral::AlPresionarIzq(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
HWND hWndMarco;
HDC dc_marco;
RECT rt;
POINT pt;
int anchoVentana;
hWndMarco = GetParent(hWnd);
GetWindowRect(hWnd, &rt);
anchoVentana = rt.right - rt.left;
SetCapture(hWnd);
SetCursor(LoadCursor(NULL, IDC_TAMANO_NS));
dc_marco = GetDC(hWndMarco);
m_hBitmap_memoria = CreateCompatibleBitmap(dc_marco, anchoVentana, 6);
m_dc_memoria = CreateCompatibleDC(dc_marco);
SelectObject(m_dc_memoria, m_hBitmap_memoria);
GetCursorPos(&pt);
ScreenToClient(hWndMarco, &pt);
m_posY = pt.y;
GetClientRect(hWndMarco, &rt);
BitBlt(m_dc_memoria, 0, 0, anchoVentana, 6, dc_marco, rt.left, m_posY - 3, SRCCOPY);
MoveToEx(dc_marco, rt.left, m_posY - 2, &pt);
LineTo(dc_marco, anchoVentana, m_posY - 2);
MoveToEx(dc_marco, rt.left, m_posY, &pt);
LineTo(dc_marco, anchoVentana, m_posY);
MoveToEx(dc_marco, rt.left, m_posY + 2, &pt);
LineTo(dc_marco, anchoVentana, m_posY + 2);
m_botonIzqPresionado = 1;
ReleaseDC(hWndMarco, dc_marco);
return 1;
}
int CSplitterLateral::AlMoverRaton(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
HWND hWndMarco;
HDC dc_marco;
RECT rt;
POINT pt;
int raton_y;
int anchoVentana;
hWndMarco = GetParent(hWnd);
raton_y = HIWORD(lParam);
if(raton_y < 6)
{
SetCursor(LoadCursor(NULL, IDC_TAMANO_NS));
}
if(m_botonIzqPresionado == 0)
{
return 0;
}
GetWindowRect(hWnd, &rt);
anchoVentana = rt.right - rt.left;
SetCapture(hWnd);
dc_marco = GetDC(hWndMarco);
GetClientRect(hWndMarco, &rt);
BitBlt(dc_marco, rt.left, m_posY - 3, anchoVentana, 6, m_dc_memoria, 0, 0, SRCCOPY);
GetCursorPos(&pt);
ScreenToClient(hWndMarco, &pt);
m_posY = pt.y;
GetClientRect(hWndMarco, &rt);
BitBlt(m_dc_memoria, 0, 0, anchoVentana, 6, dc_marco, rt.left, m_posY - 3, SRCCOPY);
MoveToEx(dc_marco, rt.left, m_posY - 2, &pt);
LineTo(dc_marco, anchoVentana, m_posY - 2);
MoveToEx(dc_marco, rt.left, m_posY, &pt);
LineTo(dc_marco, anchoVentana, m_posY);
MoveToEx(dc_marco, rt.left, m_posY + 2, &pt);
LineTo(dc_marco, anchoVentana, m_posY + 2);
ReleaseDC(hWndMarco, dc_marco);
return 1;
}
int CSplitterLateral::AlSoltarIzq(HWND hWnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
HWND hWndMarco;
HDC dc_marco;
RECT rt;
POINT pt;
int raton_y;
int anchoVentana;
hWndMarco = GetParent(hWnd);
GetWindowRect(hWnd, &rt);
anchoVentana = rt.right - rt.left;
dc_marco = GetDC(hWndMarco);
ReleaseCapture();
m_botonIzqPresionado = 0;
GetClientRect(hWndMarco, &rt);
BitBlt(dc_marco, rt.left, m_posY - 3, anchoVentana, 6, m_dc_memoria, 0, 0, SRCCOPY);
ReleaseDC(hWndMarco, dc_marco);
DeleteDC(m_dc_memoria);
DeleteObject(m_hBitmap_memoria);
SendMessage(hWndMarco, WM_COMANDO, ID_REDIMENSIONAR_VENTANA, (LPARAM) m_posY);
return 1;
}
Esta implementación puede encapsularse en una clase separada de splitter para una mayor modularidad, lo que simplifica la integración en marcos con múltiples peneles ajustables.