封装amento em C++ de um painel Tab para um framework MDI com suporte a redimensionamento por arrastar

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.

Tags: C++ Win32 MDI TabControl

Publicado em 6-1 15:34 por Thomas