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.files.wordpress.com/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; }