MFC中,常需要在对话框中添加一个分割条,拖动分割条时动态改变各个控件的大小和位置,本文通过从CStatic控件派生一个新类的方式实现这一功能,效果如下:
一 实现对话框分割条所需的几个函数
1 获取与设定窗口样式
- DWORD CWnd::GetStyle ( ) const
该函数可以获取对话框当前的窗口样式,注意这个窗口样式包含窗口通用的样式以及控件独有的样式。 - LONG GetWindowLong( HWND hWnd, int nIndex)
这个函数可以获取指定窗口的属性,当然也包括窗口的样式 - LONG SetWindowLong( HWND hWnd, int nIndex, LONG dwNewLong);
这个函数可以获取指定窗口的属性,当然包括窗口的样式。
2 捕获鼠标消息
- HWND GetCapture(VOID);
该函数用于查看当前捕获鼠标的窗口句柄。每次只能有一个窗口能够捕获鼠标;当窗口捕获鼠标输入时,无论鼠标是否在窗口边界以内,窗口均可以接收到鼠标输入。如果没有窗口捕获鼠标,这个函数返回空。(The GetCapture function retrieves a handle to the window (if any) that has captured the mouse. Only one window at a time can capture the mouse; this window receives mouse input whether or not the cursor is within its borders. ) - CWnd* CWnd::SetCapture ( );
该函数使后续的所有鼠标输入都发送到当前的窗口,无论鼠标位置在哪。当该窗口不需要捕获所有的鼠标输入时,程序应当调用ReleaseCapture ,这样其他窗口就能够接受到鼠标输入了。(Causes all subsequent mouse input to be sent to the current CWnd object regardless of the position of the cursor. When CWnd no longer requires all mouse input, the application should call the ReleaseCapture function so that other windows can receive mouse input. )
3 限定光标移动范围
- BOOL ClipCursor( const RECT *lpRect);
该函数限定光标在屏幕上的一个矩形区域内活动。如果光标落在这个矩形区域以外,系统自动调整光标位置,使它重新落在矩形区域以内。参数指示矩形区域的范围,如果为NULL,取消范围限制,光标可以自由活动。 另外,光标是共享资源。对一个限定光标运动范围的程序,如果需要当将光标控制权移交给其他程序,那么必须调用ClipCursor。 (The ClipCursor function confines the cursor to a rectangular area on the screen. If a subsequent cursor position (set by the SetCursorPos function or the mouse) lies outside the rectangle, the system automatically adjusts the position to keep the cursor inside the rectangular area. The cursor is a shared resource. If an application confines the cursor, it must release the cursor by using ClipCursor before relinquishing control to another application. )
二 对话框分割条的实现类
从CWnd派生一个类,作为分割条的实现类。其头文件为:
#pragma once
class CVertSplitter :public CStatic
{
DECLARE_DYNAMIC(CVertSplitter)
public:
CVertSplitter();
~CVertSplitter();
void SetMinWidth(int left, int right);
BOOL AttachCtrlAsLeftPane(DWORD idCtrl);
BOOL AttachCtrlAsRightPane(DWORD idCtrl);
BOOL DetachAllPanes();
void AdjustLayout();
protected:
BOOL GetMouseClipRect(LPRECT rcClip, 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 void OnMouseMove(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
private:
CRect m_rcOrgRect;
CRect m_rcOldRect;
CWnd* m_pParent;
CPoint m_pPointStart;
int m_iLeftMin, m_iRightMin;
CDWordArray m_idLeft, m_idRight;
};
其具体的实现文件为如下。这里特别注意OnLButtonDown中捕获鼠标输入的函数,以及OnLButtonUp中解除鼠标捕获的函数。上述操作的作用:当用户在分割条按下鼠标左键后,分割条开始捕获光标的移动操作,直到鼠标左键弹起。
#include "pch.h"
#include "CVertSplitter.h"
IMPLEMENT_DYNAMIC(CVertSplitter, CStatic)
CVertSplitter::CVertSplitter()
:CStatic()
, m_pParent(NULL)
, m_iLeftMin(10)
, m_iRightMin(10)
{}
CVertSplitter::~CVertSplitter()
{}
void CVertSplitter::SetMinWidth(int left, int right)
{
m_iLeftMin = left;
m_iRightMin = right;
}
BOOL CVertSplitter::AttachCtrlAsLeftPane(DWORD idCtrl)
{
m_idLeft.Add(idCtrl);
return TRUE;
}
BOOL CVertSplitter::AttachCtrlAsRightPane(DWORD idCtrl)
{
m_idRight.Add(idCtrl);
return TRUE;
}
BOOL CVertSplitter::DetachAllPanes()
{
m_idLeft.RemoveAll();
m_idRight.RemoveAll();
return TRUE;
}
void CVertSplitter::AdjustLayout()
{
CWnd* pane;
RECT rcBar, rcPane;
GetWindowRect(&rcBar);
m_pParent->ScreenToClient(&rcBar);
int i;
DWORD id;
for (i = 0; i < m_idLeft.GetSize(); i++) {
id = m_idLeft.GetAt(i);
pane = m_pParent->GetDlgItem(id);
pane->GetWindowRect(&rcPane);
m_pParent->ScreenToClient(&rcPane);
rcPane.right = rcBar.left - 1;
pane->MoveWindow(&rcPane, FALSE);
}
for (i = 0; i < m_idRight.GetSize(); i++)
{
id = m_idRight.GetAt(i);
pane = m_pParent->GetDlgItem(id);
pane->GetWindowRect(&rcPane);
m_pParent->ScreenToClient(&rcPane);
rcPane.left = rcBar.right + 1;
pane->MoveWindow(&rcPane, FALSE);
}
m_pParent->Invalidate();
}
BEGIN_MESSAGE_MAP(CVertSplitter, CStatic)
ON_WM_SETCURSOR()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()
BOOL CVertSplitter::GetMouseClipRect(LPRECT rcClip, CPoint point)
{
RECT rcOrg, rcTarget, rcParent, rcPane;
DWORD id;
GetWindowRect(&rcOrg);
m_pParent->GetClientRect(&rcParent);
m_pParent->ClientToScreen(&rcParent);
rcTarget = rcOrg;
rcTarget.left = rcParent.left + m_iLeftMin;
for (int i = 0; i < m_idLeft.GetSize(); i++) {
id = m_idLeft.GetAt(i);
m_pParent->GetDlgItem(id)->GetWindowRect(&rcPane);
if (rcTarget.left < rcPane.left + m_iLeftMin) {
rcTarget.left = rcPane.left + m_iLeftMin;
}
}
rcTarget.right = rcParent.right - m_iRightMin;
for (int i = 0; i < m_idRight.GetSize(); i++)
{
id = m_idRight.GetAt(i);
m_pParent->GetDlgItem(id)->GetWindowRect(&rcPane);
if (rcTarget.right > rcPane.right - m_iRightMin) {
rcTarget.right = rcPane.right - m_iRightMin;
}
}
if (rcTarget.left >= rcTarget.right) {
TRACE(_T("No room to drag the x-splitter bar"));
return FALSE;
}
rcClip->left = rcTarget.left + point.x;
rcClip->right = rcTarget.right - (rcOrg.right - rcOrg.left - point.x) + 1;
rcClip->top = rcOrg.top;
rcClip->bottom = rcOrg.bottom;
return TRUE;
}
BOOL CVertSplitter::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
::SetCursor(::LoadCursor(NULL, IDC_SIZEWE));
return TRUE;
}
void CVertSplitter::OnLButtonDown(UINT nFlags, CPoint point)
{
if (::GetCapture() != NULL) return;
m_pParent = GetParent();
if (!m_pParent) return;
CRect rcMouseClip;
if (!GetMouseClipRect(rcMouseClip, point)) return;
::ClipCursor(&rcMouseClip);
m_pPointStart = point;
SetCapture();
GetWindowRect(m_rcOrgRect);
m_pParent->ScreenToClient(m_rcOrgRect);
CDC* pDrawDC = NULL;
pDrawDC = m_pParent->GetDC();
pDrawDC->DrawDragRect(m_rcOrgRect, CSize(1, 1), NULL, CSize(1, 1));
m_rcOldRect = m_rcOrgRect;
m_pParent->ReleaseDC(pDrawDC);
}
void CVertSplitter::OnMouseMove(UINT nFlags, CPoint point)
{
if (GetCapture() == this)
{
CDC* pDrawDC = NULL;
pDrawDC = m_pParent->GetDC();
CRect rcCur = m_rcOrgRect;
long xDiff = 0, yDiff = 0;
xDiff = point.x - m_pPointStart.x;
yDiff = point.y - m_pPointStart.y;
rcCur.OffsetRect(xDiff, 0);
pDrawDC->DrawDragRect(rcCur, CSize(1, 1), &m_rcOldRect, CSize(1, 1));
m_rcOldRect = rcCur;
m_pParent->ReleaseDC(pDrawDC);
}
}
void CVertSplitter::OnLButtonUp(UINT nFlags, CPoint point)
{
if (GetCapture() == this)
{
CDC* pDrawDC = NULL;
pDrawDC = m_pParent->GetDC();
pDrawDC->DrawDragRect(CRect(0, 0, 0, 0), CSize(1, 1), m_rcOldRect, CSize(1, 1));
m_pParent->ReleaseDC(pDrawDC);
::ReleaseCapture();
MoveWindow(m_rcOldRect);
AdjustLayout();
}
::ClipCursor(NULL);
}
三 分割条类的使用
在对话框资源编辑器中,拖入一个picture控件,ID设为IDC_PIC_SPLITTER,type属性可选为Etched Vert,Notify属性必须为True.
在对话框类的头文件中,加入
private:
CVertSplitter m_wndXSplitter;
在对话框类的DoDataExchange函数中,加入
void CSplitterWndDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_PIC_SPLITTER, m_wndXSplitter);
}
在对话框类的OnInitDialog函数中,加入
BOOL CSplitterWndDlg::OnInitDialog()
{
m_wndXSplitter.AttachCtrlAsLeftPane(IDC_BTN);
m_wndXSplitter.AttachCtrlAsLeftPane(IDC_EDT);
m_wndXSplitter.AttachCtrlAsLeftPane(IDC_TRE);
m_wndXSplitter.AttachCtrlAsRightPane(IDC_PIC_PREVIEW);
}
即可实现文初效果。
|