Visual – MFC – Splitter Control – Dialog Based- C++

Publicado: 27 de abril de 2014 em C/C++

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.

Splitter Control

Splitter Control

 

Visual Studio

Vamos utilizar a classe CSplitterControl que é Open Source que você encontra logo abaixo:

  1. Crie um projeto MFC Dialog Based.
  2. Coloque na tela dois componentes Picture Control e nomeie um como IDC_IMAGEM e outro como IDC_SPLITTER1.
  3. Coloque um componente Edit Control e crie uma variável chamada m_texto;
  4. No componente IDC_IMAGEM mude sua propriedade Type para Bitmap.
  5. No componente IDC_SPLITTER1 mude sua propriedade Visible para False.
  6. No componente Edit Control mude sua propriedade MultiLines para True.
  7. Disponha os componentes como na imagem abaixo:

    Splitter Control - Design

    Splitter Control – Design

  8. Adicione a classe CSplitterControl em seu projeto.
  9. 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
  10. 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;
}
Publicidade

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s