Em C++ MFC não existe um componente pronto para fazer o trabalho de redimensionamento de painéis, mas existe uma classe que encapsula a API do Windows que faz exatamente este trabalho e esta classe é chamada de CSplitterWnd, a classe fornece a funcionalidade de uma janela de divisão, que é uma janela que contém vários painéis. Um objeto CSplitterWnd geralmente é incorporado em um CFrameWnd pai ou um objeto filho CMDIChildWnd, isto significa que esta classe utiliza um tipo de aplicação MDI e não uma aplicação baseada em diálogos.
É possível criar um workaround que utiliza um componente estático Picture Control para criar um Splitter que redimensiona os componentes e não painéis como faz a classe CSplitterWnd. Existe vários componentes prontos na internet para fazer isto, mas nós vamos utilizar uma classe de código aberto para fazer este trabalho.
Visual Studio
Vamos utilizar a classe CSplitterControl que é Open Source que você encontra logo abaixo:
- Crie um projeto MFC Dialog Based.
- Coloque na tela dois componentes Picture Control e nomeie um como IDC_IMAGEM e outro como IDC_SPLITTER1.
- Coloque um componente Edit Control e crie uma variável chamada m_texto;
- No componente IDC_IMAGEM mude sua propriedade Type para Bitmap.
- No componente IDC_SPLITTER1 mude sua propriedade Visible para False.
- No componente Edit Control mude sua propriedade MultiLines para True.
- Disponha os componentes como na imagem abaixo:
- Adicione a classe CSplitterControl em seu projeto.
- Efetue o Download da imagem e a transforme em BMP, você pode usar o Paint Brush do Windows: https://desenvolvimentoaberto.org/wp-content/uploads/2014/04/balonismo.jpg
- Copie os trechos marcados do código abaixo para seu código gerado automaticamente.
Exemplo:
Neste exemplo utilizamos uma classe Open Source Splitter Control para criar um Splitter à partir de um componente Picture Control que controla o tamanho dos componentes entre eles controlando seu tamanho baseado no valor do Splitter.
Obs: Lembre-se que os exemplos abaixo utilizam classes e as classes em C++ são divididas em dois arquivos.
C++
Porgrama – Classe meusplittercpp.h
// código gerado automaticamente
// meusplittercppDlg.h : header file
//
#pragma once
#include "afxwin.h"
#include "SplitterControl.h"
class CmeusplittercppDlg : public CDialogEx
{
public:
CmeusplittercppDlg(CWnd* pParent = NULL);
enum { IDD = IDD_MEUSPLITTERCPP_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
// Desenvolvimento Aberto
virtual LRESULT DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam);
protected:
HICON m_hIcon;
// Desenvolvimento Aberto
void Redimensiona(int delta);
void Append(CString texto);
CSplitterControl m_wndSplitter1;
// ******
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
CEdit m_texto;
};
Porgrama – Classe meusplittercpp.cpp
// meusplittercppDlg.cpp : implementation file
//
#include "stdafx.h"
#include "meusplittercpp.h"
#include "meusplittercppDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CmeusplittercppDlg dialog
CmeusplittercppDlg::CmeusplittercppDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CmeusplittercppDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CmeusplittercppDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT1, m_texto);
}
BEGIN_MESSAGE_MAP(CmeusplittercppDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
// ON_WM_CREATE()
END_MESSAGE_MAP()
// CmeusplittercppDlg message handlers
BOOL CmeusplittercppDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
// Desenvolvimento Aberto
// Inicializa componentes
// Adiciona texto
Append(L"O balonismo é um esporte aeronáutico praticado com um balão de ar quente.\n");
Append(L"Possui adeptos em todo o mundo. No Brasil, o esporte começou a se popularizar\n");
Append(L"a partir dos anos 90.\n");
Append(L"O balão é considerado uma aeronave assim como avião, helicópteros e outros.\n");
Append(L"Por esta razão o balão deve ter uma matricula (prefixo) registrado junto\n");
Append(L"à ANAC, seu piloto deve possuir uma licença (brevê) específico para a pratica\n");
Append(L"do balonismo também emitido pela ANAC.");
// Cria componente Splitter Control baseado no componente Picture Control
CRect rc;
CWnd* pWnd;
pWnd = GetDlgItem(IDC_SPLITTER1);
pWnd->GetWindowRect(rc);
ScreenToClient(rc);
m_wndSplitter1.Create(WS_CHILD | WS_VISIBLE, rc, this, IDC_SPLITTER1);
m_wndSplitter1.SetRange(50, 50, -1);
// Carrega imagem
CStatic * foto;
CString imagem;
foto = (CStatic *)GetDlgItem(IDC_IMAGEM);
imagem = L"C:/Desenvolvimento Aberto/balonismo.bmp";
HBITMAP pic = (HBITMAP)LoadImage(NULL, imagem, IMAGE_BITMAP,370, 260, LR_LOADFROMFILE);
foto->SetBitmap(pic);
// ***
return TRUE;
}
void CmeusplittercppDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
HCURSOR CmeusplittercppDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
// Desenvolvimento Aberto
// Nosso código começa aqui
LRESULT CmeusplittercppDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_NOTIFY)
{
if (wParam == IDC_SPLITTER1)
{
SPC_NMHDR* pHdr = (SPC_NMHDR*)lParam;
Redimensiona(pHdr->delta);
}
}
return CDialog::DefWindowProc(message, wParam, lParam);
}
void CmeusplittercppDlg::Redimensiona(int delta)
{
CSplitterControl::ChangeWidth(GetDlgItem(IDC_IMAGEM), delta);
CSplitterControl::ChangeWidth(&m_texto, -delta, CW_RIGHTALIGN);
Invalidate();
UpdateWindow();
}
void CmeusplittercppDlg::Append(CString texto)
{
int tam1 = m_texto.GetWindowTextLengthW();
m_texto.SetSel(tam1, tam1);
m_texto.ReplaceSel(texto);
}
Splitter Control – Componente Open Source
Porgrama – Classe CSplitterControl.h
/**************CSplitterControl interface***********
* Class name :CSplitterControl
* Purpose: Implement splitter control for dialog
* or any other windows.
* Author: Nguyen Huy Hung, Vietnamese student.
* Date: May 29th 2002.
* Note: You can use it for any purposes. Feel free
* to change, modify, but please do not
* remove this.
* No warranty of any risks.
* (:-)
*/
#if !defined(AFX_SPLITTERCONTROL_H__FEBBA242_B2C9_4403_B68D_E519D645CB15__INCLUDED_)
#define AFX_SPLITTERCONTROL_H__FEBBA242_B2C9_4403_B68D_E519D645CB15__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// SplitterControl.h : header file
//
/////////////////////////////////////////////////////////////////////////////
// CSplitterControl window
#define SPN_SIZED WM_USER + 1
#define CW_LEFTALIGN 1
#define CW_RIGHTALIGN 2
#define CW_TOPALIGN 3
#define CW_BOTTOMALIGN 4
#define SPS_VERTICAL 1
#define SPS_HORIZONTAL 2
typedef struct SPC_NMHDR
{
NMHDR hdr;
int delta;
} SPC_NMHDR;
class CSplitterControl : public CStatic
{
// Construction
public:
CSplitterControl();
// Attributes
public:
protected:
BOOL m_bIsPressed;
int m_nType;
int m_nX, m_nY;
int m_nMin, m_nMax;
int m_nSavePos; // Save point on the lbutton down
// message
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSplitterControl)
//}}AFX_VIRTUAL
// Implementation
public:
static void ChangePos(CWnd* pWnd, int dx, int dy);
static void ChangeWidth(CWnd* pWnd, int dx, DWORD dwFlag = CW_LEFTALIGN);
static void ChangeHeight(CWnd* pWnd, int dy, DWORD dwFlag = CW_TOPALIGN);
public:
void SetRange(int nMin, int nMax);
void SetRange(int nSubtraction, int nAddition, int nRoot);
int GetStyle();
int SetStyle(int nStyle = SPS_VERTICAL);
void Create(DWORD dwStyle, const CRect& rect, CWnd* pParent, UINT nID);
virtual ~CSplitterControl();
// Generated message map functions
protected:
virtual void DrawLine(CDC* pDC, int x, int y);
void MoveWindowTo(CPoint pt);
//{{AFX_MSG(CSplitterControl)
afx_msg void OnPaint();
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_SPLITTERCONTROL_H__FEBBA242_B2C9_4403_B68D_E519D645CB15__INCLUDED_)
Porgrama – Classe CSplitterControl.cpp
// SplitterControl.cpp : implementation file
//
#include "stdafx.h"
#include "SplitterControl.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CSplitterControl
// hCursor1 is for vertiacal one
// and hCursor2 is for horizontal one
static HCURSOR SplitterControl_hCursor1 = NULL;
static HCURSOR SplitterControl_hCursor2 = NULL;
CSplitterControl::CSplitterControl()
{
// Mouse is pressed down or not ?
m_bIsPressed = FALSE;
// Min and Max range of the splitter.
m_nMin = m_nMax = -1;
}
CSplitterControl::~CSplitterControl()
{
}
BEGIN_MESSAGE_MAP(CSplitterControl, CStatic)
//{{AFX_MSG_MAP(CSplitterControl)
ON_WM_PAINT()
ON_WM_MOUSEMOVE()
ON_WM_SETCURSOR()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CSplitterControl message handlers
/****************************************************
* Create(DWORD dwStyle, const CRect& rect, CWnd* pParent, nID)
* Use this function instead of the CStatic::Create function
* Parameters: No need to explain (see MSDN (:-) )
*
****************************************************/
void CSplitterControl::Create(DWORD dwStyle, const CRect &rect, CWnd *pParent, UINT nID)
{
CRect rc = rect;
dwStyle |= SS_NOTIFY;
// Determine default type base on it's size.
m_nType = (rc.Width() < rc.Height())?
SPS_VERTICAL:
SPS_HORIZONTAL;
if (m_nType == SPS_VERTICAL)
rc.right = rc.left + 5;
else // SPS_HORIZONTAL
rc.bottom = rc.top + 5;
CStatic::Create(L"", dwStyle, rc, pParent, nID);
if (!SplitterControl_hCursor1)
{
SplitterControl_hCursor1 = AfxGetApp()->LoadStandardCursor(IDC_SIZEWE);
SplitterControl_hCursor2 = AfxGetApp()->LoadStandardCursor(IDC_SIZENS);
}
// force the splitter not to be splited.
SetRange(0, 0, -1);
}
// Set style for splitter control
// nStyle = SPS_VERTICAL or SPS_HORIZONTAL
int CSplitterControl::SetStyle(int nStyle)
{
int m_nOldStyle = m_nType;
m_nType = nStyle;
return m_nOldStyle;
}
int CSplitterControl::GetStyle()
{
return m_nType;
}
void CSplitterControl::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect rcClient;
GetClientRect(rcClient);
CBrush br, *pOB;
CPen pen, *pOP;
dc.Draw3dRect(rcClient, GetSysColor(COLOR_BTNHIGHLIGHT), GetSysColor(COLOR_BTNSHADOW));
rcClient.DeflateRect(1,1,1,1);
pen.CreatePen(0, 1, RGB(200, 200, 200));
br.CreateSolidBrush(RGB(200, 220, 220));
pOB = dc.SelectObject(&br);
pOP = dc.SelectObject(&pen);
dc.Rectangle(rcClient);
// Restore pen and brush
DeleteObject(dc.SelectObject(pOB));
DeleteObject(dc.SelectObject(pOP));
}
void CSplitterControl::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bIsPressed)
{
CWindowDC dc(NULL);
DrawLine(&dc, m_nX, m_nY);
CPoint pt = point;
ClientToScreen(&pt);
GetParent()->ScreenToClient(&pt);
if (pt.x < m_nMin)
pt.x = m_nMin;
if (pt.y < m_nMin)
pt.y = m_nMin;
if (pt.x > m_nMax)
pt.x = m_nMax;
if (pt.y > m_nMax)
pt.y = m_nMax;
GetParent()->ClientToScreen(&pt);
m_nX = pt.x;
m_nY = pt.y;
DrawLine(&dc, m_nX, m_nY);
}
CStatic::OnMouseMove(nFlags, point);
}
BOOL CSplitterControl::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
if (nHitTest == HTCLIENT)
{
(m_nType == SPS_VERTICAL)?(::SetCursor(SplitterControl_hCursor1))
:(::SetCursor(SplitterControl_hCursor2));
return 0;
}
else
return CStatic::OnSetCursor(pWnd, nHitTest, message);
}
void CSplitterControl::OnLButtonDown(UINT nFlags, CPoint point)
{
CStatic::OnLButtonDown(nFlags, point);
m_bIsPressed = TRUE;
SetCapture();
CRect rcWnd;
GetWindowRect(rcWnd);
if (m_nType == SPS_VERTICAL)
m_nX = rcWnd.left + rcWnd.Width() / 2;
else
m_nY = rcWnd.top + rcWnd.Height() / 2;
if (m_nType == SPS_VERTICAL)
m_nSavePos = m_nX;
else
m_nSavePos = m_nY;
CWindowDC dc(NULL);
DrawLine(&dc, m_nX, m_nY);
}
void CSplitterControl::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_bIsPressed)
{
ClientToScreen(&point);
CWindowDC dc(NULL);
DrawLine(&dc, m_nX, m_nY);
CPoint pt(m_nX, m_nY);
m_bIsPressed = FALSE;
CWnd *pOwner = GetOwner();
if (pOwner && IsWindow(pOwner->m_hWnd))
{
CRect rc;
int delta;
pOwner->GetClientRect(rc);
pOwner->ScreenToClient(&pt);
MoveWindowTo(pt);
if (m_nType == SPS_VERTICAL)
delta = m_nX - m_nSavePos;
else
delta = m_nY - m_nSavePos;
SPC_NMHDR nmsp;
nmsp.hdr.hwndFrom = m_hWnd;
nmsp.hdr.idFrom = GetDlgCtrlID();
nmsp.hdr.code = SPN_SIZED;
nmsp.delta = delta;
pOwner->SendMessage(WM_NOTIFY, nmsp.hdr.idFrom, (LPARAM)&nmsp);
}
}
CStatic::OnLButtonUp(nFlags, point);
ReleaseCapture();
}
void CSplitterControl::DrawLine(CDC* pDC, int x, int y)
{
int nRop = pDC->SetROP2(R2_NOTXORPEN);
CRect rcWnd;
int d = 1;
GetWindowRect(rcWnd);
CPen pen;
pen.CreatePen(0, 1, RGB(200, 200, 200));
CPen *pOP = pDC->SelectObject(&pen);
if (m_nType == SPS_VERTICAL)
{
pDC->MoveTo(m_nX - d, rcWnd.top);
pDC->LineTo(m_nX - d, rcWnd.bottom);
pDC->MoveTo(m_nX + d, rcWnd.top);
pDC->LineTo(m_nX + d, rcWnd.bottom);
}
else // m_nType == SPS_HORIZONTAL
{
pDC->MoveTo(rcWnd.left, m_nY - d);
pDC->LineTo(rcWnd.right, m_nY - d);
pDC->MoveTo(rcWnd.left, m_nY + d);
pDC->LineTo(rcWnd.right, m_nY + d);
}
pDC->SetROP2(nRop);
pDC->SelectObject(pOP);
}
void CSplitterControl::MoveWindowTo(CPoint pt)
{
CRect rc;
GetWindowRect(rc);
CWnd* pParent;
pParent = GetParent();
if (!pParent || !::IsWindow(pParent->m_hWnd))
return;
pParent->ScreenToClient(rc);
if (m_nType == SPS_VERTICAL)
{
int nMidX = (rc.left + rc.right) / 2;
int dx = pt.x - nMidX;
rc.OffsetRect(dx, 0);
}
else
{
int nMidY = (rc.top + rc.bottom) / 2;
int dy = pt.y - nMidY;
rc.OffsetRect(0, dy);
}
MoveWindow(rc);
}
void CSplitterControl::ChangeWidth(CWnd *pWnd, int dx, DWORD dwFlag)
{
CWnd* pParent = pWnd->GetParent();
if (pParent && ::IsWindow(pParent->m_hWnd))
{
CRect rcWnd;
pWnd->GetWindowRect(rcWnd);
pParent->ScreenToClient(rcWnd);
if (dwFlag == CW_LEFTALIGN)
rcWnd.right += dx;
else if (dwFlag == CW_RIGHTALIGN)
rcWnd.left -= dx;
pWnd->MoveWindow(rcWnd);
}
}
void CSplitterControl::ChangeHeight(CWnd *pWnd, int dy, DWORD dwFlag)
{
CWnd* pParent = pWnd->GetParent();
if (pParent && ::IsWindow(pParent->m_hWnd))
{
CRect rcWnd;
pWnd->GetWindowRect(rcWnd);
pParent->ScreenToClient(rcWnd);
if (dwFlag == CW_TOPALIGN)
rcWnd.bottom += dy;
else if (dwFlag == CW_BOTTOMALIGN)
rcWnd.top -= dy;
pWnd->MoveWindow(rcWnd);
}
}
void CSplitterControl::ChangePos(CWnd* pWnd, int dx, int dy)
{
CWnd* pParent = pWnd->GetParent();
if (pParent && ::IsWindow(pParent->m_hWnd))
{
CRect rcWnd;
pWnd->GetWindowRect(rcWnd);
pParent->ScreenToClient(rcWnd);
rcWnd.OffsetRect(-dx, dy);
pWnd->MoveWindow(rcWnd);
}
}
void CSplitterControl::SetRange(int nMin, int nMax)
{
m_nMin = nMin;
m_nMax = nMax;
}
// Set splitter range from (nRoot - nSubtraction) to (nRoot + nAddition)
// If (nRoot < 0)
// nRoot = <current position of the splitter>
void CSplitterControl::SetRange(int nSubtraction, int nAddition, int nRoot)
{
if (nRoot < 0)
{
CRect rcWnd;
GetWindowRect(rcWnd);
if (m_nType == SPS_VERTICAL)
nRoot = rcWnd.left + rcWnd.Width() / 2;
else // if m_nType == SPS_HORIZONTAL
nRoot = rcWnd.top + rcWnd.Height() / 2;
}
m_nMin = nRoot - nSubtraction;
m_nMax = nRoot + nAddition;
}



