Este artigo descreve a implementação em C++ de uma classe de painel com abas (Tab Control) integrada como um painel lateral em uma aplicação baseada em MDI (Multiple Document Interface). A classe suporta o redimensionamento itnerativo por arrastar em uma das bordas do painel, requerendo a conversão precisa de coordenadas entre diferentes sistemas de janela.
Estrutura do Painel com Abas (CTabPanel)
A classe principal encapsula o controle de abas nativo do Windows e gerencia o ciclo de vida das páginas.
#pragma once #include "xwnd.h"
#define SIDE_TOP 0x01 #define SIDE_BOTTOM 0x02 #define SIDE_LEFT 0x04 #define SIDE_RIGHT 0x08
class CTabPanel : public CXWnd { public: CTabPanel(); ~CTabPanel();
XDECLARE_DYNCREATE(CTabPanel)
HWND m_hTabControl;
HWND m_hParentFrame;
HWND m_hClientArea;
CXWnd* m_pOwner;
HWND CreateTabControl(HWND hParent);
virtual BOOL PreCreateWindow(WNDCLASSEX& wc);
int HandleCreate(HWND, UINT, WPARAM, LPARAM);
int HandleSize(HWND, UINT, WPARAM, LPARAM);
int HandleDestroy(HWND, UINT, WPARAM, LPARAM);
int HandleNotification(HWND, UINT, WPARAM, LPARAM);
int HandleTimer(HWND, UINT, WPARAM, LPARAM);
int HandleMouseDown(HWND, UINT, WPARAM, LPARAM);
int HandleMouseUp(HWND, UINT, WPARAM, LPARAM);
int HandleMouseMovement(HWND, UINT, WPARAM, LPARAM);
HDC m_memDC;
HBITMAP m_memBitmap;
int m_cursorX, m_cursorY;
BOOL m_isDragging;
int m_dockSide;
int AddTab(const TCHAR* title, int position, int iconIndex, HWND pageWindow);
int ActivateTab(int index);
int RenderDragBar(HDC hdc, int x1, int y1, int x2, int y2);
XDECLARE_MESSAGE_MAP()
};
</div>Implementação do Painel com Abas
--------------------------------
A implementação inclui a manipulação de mensagens Windows e a lógica de desenho personalizado para a barra de arrasto.
<div class="code-block">```
#include "StdAfx.h"
#include "CTabPanel.h"
XIMPLEMENT_DYNCREATE(CTabPanel, CXWnd)
XBEGIN_MESSAGE_MAP(CTabPanel, CXWnd)
XON_MESSAGE(WM_CREATE, HandleCreate)
XON_MESSAGE(WM_SIZE, HandleSize)
XON_MESSAGE(WM_DESTROY, HandleDestroy)
XON_MESSAGE(WM_NOTIFY, HandleNotification)
XON_MESSAGE(WM_TIMER, HandleTimer)
XON_MESSAGE(WM_LBUTTONDOWN, HandleMouseDown)
XON_MESSAGE(WM_LBUTTONUP, HandleMouseUp)
XON_MESSAGE(WM_MOUSEMOVE, HandleMouseMovement)
XEND_MESSAGE_MAP()
CTabPanel::CTabPanel()
{
wcscpy_s(szWindowClass, _T("CustomTabPanel"));
wcscpy_s(szTitle, _T("Tab Panel"));
m_isDragging = FALSE;
}
CTabPanel::~CTabPanel()
{
}
HWND CTabPanel::CreateTabControl(HWND hParent)
{
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icc);
HWND hTab = CreateWindowEx(
0,
WC_TABCONTROL,
NULL,
WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | TCS_BOTTOM,
0, 0, 0, 0,
hParent,
(HMENU)IDC_TAB_CONTROL,
m_hInst,
NULL);
if (!hTab)
return NULL;
HIMAGELIST hIcons = ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, 10, 5);
for (int i = 0; i < 10; i++)
{
HICON hIcon = LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_ICON_BASE + i));
if (hIcon)
{
ImageList_AddIcon(hIcons, hIcon);
DestroyIcon(hIcon);
}
}
SendMessage(hTab, TCM_SETIMAGELIST, 0, (LPARAM)hIcons);
return hTab;
}
BOOL CTabPanel::PreCreateWindow(WNDCLASSEX& wc)
{
wc.style &= ~(CS_HREDRAW | CS_VREDRAW);
return TRUE;
}
int CTabPanel::HandleCreate(HWND hWnd, UINT, WPARAM, LPARAM)
{
LONG style = GetWindowLong(hWnd, GWL_STYLE);
style &= ~WS_THICKFRAME;
SetWindowLong(hWnd, GWL_STYLE, style);
m_hTabControl = CreateTabControl(hWnd);
return 0;
}
int CTabPanel::AddTab(const TCHAR* title, int position, int iconIndex, HWND pageWindow)
{
TCITEM tci = {0};
tci.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
tci.iImage = iconIndex;
tci.pszText = (LPTSTR)title;
tci.lParam = (LPARAM)pageWindow;
if (TabCtrl_InsertItem(m_hTabControl, position, &tci) == -1)
return -1;
RECT tabRect;
TabCtrl_GetItemRect(m_hTabControl, 0, &tabRect);
TabCtrl_AdjustRect(m_hTabControl, FALSE, &tabRect);
MoveWindow(pageWindow,
tabRect.left, tabRect.top,
tabRect.right - tabRect.left,
tabRect.bottom - tabRect.top,
TRUE);
return 0;
}
int CTabPanel::ActivateTab(int index)
{
int count = TabCtrl_GetItemCount(m_hTabControl);
if (count <= 0 || index < 0 || index >= count)
return -1;
TCITEM tci = {0};
tci.mask = TCIF_PARAM;
for (int i = 0; i < count; i++)
{
if (TabCtrl_GetItem(m_hTabControl, i, &tci))
{
ShowWindow((HWND)tci.lParam, (i == index) ? SW_SHOW : SW_HIDE);
}
}
TabCtrl_SetCurSel(m_hTabControl, index);
return 0;
}
int CTabPanel::HandleSize(HWND hWnd, UINT, WPARAM, LPARAM)
{
RECT clientRect;
GetClientRect(hWnd, &clientRect);
int offset = (m_dockSide != 0) ? 6 : 0;
switch (m_dockSide)
{
case SIDE_TOP:
MoveWindow(m_hTabControl, 0, 0, clientRect.right, clientRect.bottom - offset, TRUE);
break;
case SIDE_BOTTOM:
MoveWindow(m_hTabControl, 0, offset, clientRect.right, clientRect.bottom - offset, TRUE);
break;
case SIDE_LEFT:
MoveWindow(m_hTabControl, 0, 0, clientRect.right - offset, clientRect.bottom, TRUE);
break;
case SIDE_RIGHT:
MoveWindow(m_hTabControl, offset, 0, clientRect.right - offset, clientRect.bottom, TRUE);
break;
default:
MoveWindow(m_hTabControl, 0, 0, clientRect.right, clientRect.bottom, TRUE);
break;
}
// Atualizar posição das páginas
int activeTab = TabCtrl_GetCurSel(m_hTabControl);
if (activeTab < 0)
return 0;
RECT displayArea;
GetClientRect(m_hTabControl, &displayArea);
TabCtrl_AdjustRect(m_hTabControl, FALSE, &displayArea);
int pageCount = TabCtrl_GetItemCount(m_hTabControl);
for (int i = 0; i < pageCount; i++)
{
TCITEM tci = {0};
tci.mask = TCIF_PARAM;
if (TabCtrl_GetItem(m_hTabControl, i, &tci))
{
HWND hPage = (HWND)tci.lParam;
MoveWindow(hPage,
displayArea.left, displayArea.top,
displayArea.right - displayArea.left,
displayArea.bottom - displayArea.top,
TRUE);
}
}
return 0;
}
int CTabPanel::HandleNotification(HWND hWnd, UINT, WPARAM wParam, LPARAM lParam)
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->idFrom != IDC_TAB_CONTROL)
return 0;
switch (pNMHDR->code)
{
case TCN_SELCHANGING:
{
int curSel = TabCtrl_GetCurSel(m_hTabControl);
TCITEM tci = {0};
tci.mask = TCIF_PARAM;
if (TabCtrl_GetItem(m_hTabControl, curSel, &tci))
ShowWindow((HWND)tci.lParam, SW_HIDE);
}
break;
case TCN_SELCHANGE:
{
int newSel = TabCtrl_GetCurSel(m_hTabControl);
TCITEM tci = {0};
tci.mask = TCIF_PARAM;
if (TabCtrl_GetItem(m_hTabControl, newSel, &tci))
ShowWindow((HWND)tci.lParam, SW_SHOW);
}
break;
}
return DefWindowProc(hWnd, WM_NOTIFY, wParam, lParam);
}
int CTabPanel::RenderDragBar(HDC hdc, int x1, int y1, int x2, int y2)
{
HPEN hPen = CreatePen(PS_DOT, 1, RGB(0, 0, 0));
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
MoveToEx(hdc, x1, y1, NULL);
LineTo(hdc, x2, y1);
LineTo(hdc, x2, y2);
LineTo(hdc, x1, y2);
LineTo(hdc, x1, y1);
// Linha central vertical
MoveToEx(hdc, (x1 + x2) / 2, y1, NULL);
LineTo(hdc, (x1 + x2) / 2, y2);
// Linha central horizontal
MoveToEx(hdc, x1, (y1 + y2) / 2, NULL);
LineTo(hdc, x2, (y1 + y2) / 2);
SelectObject(hdc, hOldPen);
DeleteObject(hPen);
return 0;
}
int CTabPanel::HandleMouseDown(HWND hWnd, UINT, WPARAM, LPARAM)
{
SetCapture(hWnd);
m_isDragging = TRUE;
POINT cursorPos;
GetCursorPos(&cursorPos);
ScreenToClient(hWnd, &cursorPos);
m_cursorX = cursorPos.x;
m_cursorY = cursorPos.y;
HDC hdc = GetDC(hWnd);
switch (m_dockSide)
{
case SIDE_TOP:
case SIDE_BOTTOM:
{
RECT windowRect;
GetWindowRect(hWnd, &windowRect);
int width = windowRect.right - windowRect.left;
HBITMAP hBmp = CreateCompatibleBitmap(hdc, width, 6);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, hBmp);
BitBlt(memDC, 0, 0, width, 6, hdc, 0, cursorPos.y - 3, SRCCOPY);
RenderDragBar(hdc, 0, cursorPos.y - 2, width, cursorPos.y + 2);
SelectObject(memDC, oldBmp);
DeleteDC(memDC);
DeleteObject(hBmp);
}
break;
case SIDE_LEFT:
case SIDE_RIGHT:
{
RECT windowRect;
GetWindowRect(hWnd, &windowRect);
int height = windowRect.bottom - windowRect.top;
HBITMAP hBmp = CreateCompatibleBitmap(hdc, 6, height);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, hBmp);
BitBlt(memDC, 0, 0, 6, height, hdc, cursorPos.x - 3, 0, SRCCOPY);
RenderDragBar(hdc, cursorPos.x - 2, 0, cursorPos.x + 2, height);
SelectObject(memDC, oldBmp);
DeleteDC(memDC);
DeleteObject(hBmp);
}
break;
}
ReleaseDC(hWnd, hdc);
return 0;
}
int CTabPanel::HandleMouseMovement(HWND hWnd, UINT, WPARAM, LPARAM)
{
if (!m_isDragging)
{
POINT cursor;
GetCursorPos(&cursor);
ScreenToClient(hWnd, &cursor);
RECT client;
GetClientRect(hWnd, &client);
BOOL showCursor = FALSE;
switch (m_dockSide)
{
case SIDE_TOP:
showCursor = (client.bottom - cursor.y) < 6;
break;
case SIDE_BOTTOM:
showCursor = cursor.y < 6;
break;
case SIDE_LEFT:
showCursor = (client.right - cursor.x) < 6;
break;
case SIDE_RIGHT:
showCursor = cursor.x < 6;
break;
}
if (showCursor)
SetCursor(LoadCursor(NULL, (m_dockSide <= SIDE_BOTTOM) ? IDC_SIZENS : IDC_SIZEWE));
return 0;
}
SetCapture(hWnd);
POINT newPos;
GetCursorPos(&newPos);
ScreenToClient(hWnd, &newPos);
HDC hdc = GetDC(hWnd);
RECT clientRect;
GetClientRect(hWnd, &clientRect);
// Restaurar área anterior
switch (m_dockSide)
{
case SIDE_TOP:
case SIDE_BOTTOM:
{
int width = clientRect.right;
BitBlt(hdc, 0, m_cursorY - 3, width, 6, hdc, 0, m_cursorY - 3, SRCCOPY);
}
break;
case SIDE_LEFT:
case SIDE_RIGHT:
{
int height = clientRect.bottom;
BitBlt(hdc, m_cursorX - 3, 0, 6, height, hdc, m_cursorX - 3, 0, SRCCOPY);
}
break;
}
m_cursorX = newPos.x;
m_cursorY = newPos.y;
// Desenhar nova posição
switch (m_dockSide)
{
case SIDE_TOP:
case SIDE_BOTTOM:
RenderDragBar(hdc, 0, m_cursorY - 2, clientRect.right, m_cursorY + 2);
break;
case SIDE_LEFT:
case SIDE_RIGHT:
RenderDragBar(hdc, m_cursorX - 2, 0, m_cursorX + 2, clientRect.bottom);
break;
}
ReleaseDC(hWnd, hdc);
return 0;
}
int CTabPanel::HandleMouseUp(HWND hWnd, UINT, WPARAM, LPARAM)
{
ReleaseCapture();
m_isDragging = FALSE;
POINT finalPos;
GetCursorPos(&finalPos);
ScreenToClient(hWnd, &finalPos);
RECT windowRect;
GetWindowRect(hWnd, &windowRect);
HWND hParent = GetParent(hWnd);
RECT parentClient;
GetClientRect(hParent, &parentClient);
POINT topLeft = {windowRect.left, windowRect.top};
ScreenToClient(hParent, &topLeft);
switch (m_dockSide)
{
case SIDE_BOTTOM:
MoveWindow(hWnd, topLeft.x, finalPos.y,
windowRect.right - windowRect.left,
windowRect.bottom - finalPos.y, TRUE);
break;
case SIDE_LEFT:
MoveWindow(hWnd, topLeft.x, topLeft.y,
finalPos.x - topLeft.x,
windowRect.bottom - windowRect.top, TRUE);
break;
case SIDE_RIGHT:
MoveWindow(hWnd, finalPos.x, topLeft.y,
windowRect.right - finalPos.x,
windowRect.bottom - windowRect.top, TRUE);
break;
}
SendMessage(hParent, WM_COMMAND, ID_REALIGN_WINDOWS, 0);
return 0;
}
Exemplo de como criar e configurar múltiplos painéis de abas dentro de uma aplicação MDI.
class CMainFrame : public CXMDIFrame { public: CMainFrame(); ~CMainFrame();
CTabPanel* m_pLeftPanel;
CTabPanel* m_pRightPanel;
CTabPanel* m_pBottomPanel;
int m_leftWidth;
int m_rightWidth;
int m_bottomHeight;
int HandleCreate(HWND, UINT, WPARAM, LPARAM);
int HandleSize(HWND, UINT, WPARAM, LPARAM);
int RealignChildWindows(HWND hWnd);
// ... outras declarações
};
int CMainFrame::HandleCreate(HWND hWnd, UINT, WPARAM, LPARAM) { // Configuração da área cliente MDI CLIENTCREATESTRUCT ccs; ccs.hWindowMenu = GetSubMenu(GetMenu(hWnd), 2); ccs.idFirstChild = 100;
m_hMDIClient = CreateWindow(
_T("MDICLIENT"),
NULL,
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
0, 0, 0, 0,
hWnd,
NULL,
m_hInst,
(LPSTR)&ccs);
// Criar painéis
m_pLeftPanel = new CTabPanel();
m_pLeftPanel->m_dockSide = SIDE_LEFT;
m_pLeftPanel->m_pOwner = this;
m_pLeftPanel->Create(m_hInst, hWnd);
m_leftWidth = 200;
m_pRightPanel = new CTabPanel();
m_pRightPanel->m_dockSide = SIDE_RIGHT;
m_pRightPanel->m_pOwner = this;
m_pRightPanel->Create(m_hInst, hWnd);
m_rightWidth = 200;
m_pBottomPanel = new CTabPanel();
m_pBottomPanel->m_dockSide = SIDE_BOTTOM;
m_pBottomPanel->m_pOwner = this;
m_pBottomPanel->Create(m_hInst, hWnd);
m_bottomHeight = 150;
// Adicionar páginas de exemplo
// ... código para criar janelas de conteúdo
RealignChildWindows(hWnd);
return 0;
}
int CMainFrame::HandleSize(HWND hWnd, UINT, WPARAM, LPARAM) { return RealignChildWindows(hWnd); }
int CMainFrame::RealignChildWindows(HWND hWnd) { RECT clientArea; GetClientRect(hWnd, &clientArea);
// Espaço reservado para barras de ferramentas e status
int toolbarHeight = 30;
int statusBarHeight = 25;
clientArea.top += toolbarHeight;
clientArea.bottom -= statusBarHeight;
// Calcular áreas para painéis laterais
int mainAreaLeft = clientArea.left + m_leftWidth;
int mainAreaRight = clientArea.right - m_rightWidth;
int mainAreaBottom = clientArea.bottom - m_bottomHeight;
// Reposicionar painéis
MoveWindow(m_pLeftPanel->m_hWnd,
clientArea.left, clientArea.top,
m_leftWidth, clientArea.bottom - clientArea.top, TRUE);
MoveWindow(m_pRightPanel->m_hWnd,
mainAreaRight, clientArea.top,
m_rightWidth, clientArea.bottom - clientArea.top, TRUE);
MoveWindow(m_pBottomPanel->m_hWnd,
clientArea.left, mainAreaBottom,
clientArea.right - clientArea.left, m_bottomHeight, TRUE);
// Reposicionar área cliente MDI
MoveWindow(m_hMDIClient,
mainAreaLeft, clientArea.top,
mainAreaRight - mainAreaLeft,
mainAreaBottom - clientArea.top, TRUE);
return 0;
}
</div>A estrutura apresentada permite a criação de interfaces de usuário flexíveis com múltiplos painéis acopláveis. A implementação cuidadosa da conversão de coordenadas e o gerenciamento do estado de arrasto garantem uma experiência de usuário fluida durante o redimensionamento dos painéis.