环境: VC6, MFC 为了实现用鼠标改变控件的大小,通常你需要处理下面一些窗口消息: WM_SETCURSOR—为控件上不同的区域设置不同的鼠标形状,有以下鼠标供选择。 IDC_SIZEWE—当鼠标在左或右边界时的形状 IDC_SIZENS—当鼠标在上或下边界时的形状 IDC_SIZENWS
环境: VC6, MFC
为了实现用鼠标改变控件的大小,通常你需要处理下面一些窗口消息:
WM_SETCURSOR—为控件上不同的区域设置不同的鼠标形状,有以下鼠标供选择。
IDC_SIZEWE—当鼠标在左或右边界时的形状
IDC_SIZENS—当鼠标在上或下边界时的形状
IDC_SIZENWSE—当鼠标在左上角或右下角时的形状
IDC_SIZENESW—当鼠标在右上角或左下角时的形状
IDC_ARROW—当鼠标在控件上但是不在控件边框时的形状
WM_MOUSEMOVE—在鼠标移动时改变控件的大小
WM_NCLBUTTONDOWN—开始控件改变大小的动作
WM_LBUTTONUP—结束控件改变大小的动作
在设计代码的过程中,我发现对于所有的控件改变大小来说,有一些变量或功能是通用的,基于此点,我设计了一个接口-IResizeControl,所有的需要改变大小的控件都可以从这个接口派生。技术上来说,IResizeControl不是一个接口,因为它没有任何的纯虚函数,但是我依然把它当成一个接口,因为在声明IResizeControl实例时,没有作任何的动作。下面是IResizeControl类的声明:
class IResizeControl
{
public:
//Enabling Flags
void EnableNorth(bool bN=true);
void EnableWest(bool bW=true);
void EnableSouth(bool bS=true);
void EnableEast(bool bE=true);
//Change Limits
bool SetWidth(int iMinWidth, int iMaxWidth);
bool SetHeight(int iMinHeight, int iMaxHeight);
//Resize Message
static const UINT UWM_CONTROLRESIZE;
protected:
//Constructor and Destructor declared protected
prevents
//creation of IResizeControl objects
//CONSTRUCTOR
IResizeControl(bool bN, bool bW, bool bS, bool bE,
int iMinWidth, int iMaxWidth,
int iMinHeight, int iMaxHeight, bool bNotify);
//DESTRUCTOR
virtual ~IResizeControl();
//Find the current Mouse Position
virtual int FindPosition(POINT const& rPt, CRect const& roRect);
//Determine the new Dimensions
virtual void NewDimensions(POINT const& rPt,
CRect const& roRect,
int& riLeft, int& riTop,
int& riWidth, int&
riHeight, bool& rbResize);
//Mouse Cursor Positions
enum { POSDEF=0, POSN=1, POSNW=2, POSW=3, POSSW=4, POSS=5,
POSSE=6, POSE=7, POSNE=8 };
//Mouse Cursors
static HCURSOR sm_hWE, sm_hNS, sm_hNWSE, sm_hNESW, sm_hDEF;
//Enabling Flags
bool m_bN, m_bW, m_bS, m_bE;
//Tracking Flag
bool m_bTrack;
//Notification Flag
bool m_bNotify;
//Position
int m_iPosition;
//Limits
int m_iMinWidth, m_iMaxWidth, m_iMinHeight, m_iMaxHeight;
};
成员函数EnableNorth(), EnableWest(), EnableSouth()和EnableEast()在构造函数结束后使用,作用是设置可改变大小的边框。如果设置了两个边框都可以改变大小,那么位于这两个边框的角也可以被鼠标选中,用来改变控件的大小。例如,成员变量m_bN和m_bW为真,那么NW(左上角)也可以被鼠标选中来改变控件的大小。
成员函数SetWidth()和SetHeight(),用来设定控件大小的范围,改变控件的大小不能超出这个范围。
当bNotify(此值在构造函数中被设置)为真时,用户消息UWM_CONTROLRESIZE当控件被改变大小时被发送给父窗口。用户消息UWM_CONTROLRESIZE也可以使用在父窗口中(比如一个对话框),去实现一些特殊的功能。
上面说的方向,大小限制和用户消息可以在构造函数中设置,除了用户消息标记,其他的设置都可以在其他地方更改。
虚成员函数FindPosition()用来找到当前鼠标移动的位置。使用这个函数可以判断当前的鼠标在控件内还是外,或是在边框上还是角上.返回值如下:
enum { POSDEF=0, POSN=1, POSNW=2, POSW=3, POSSW=4, POSS=5, POSSE=6, POSE=7, POSNE=8 };
POSDEF变量使用在鼠标不在控件边框时的状态,其他值是自说明的,鼠标当前的位置也由m_iPosition成员变量决定,如果需要的话,可以重载这个函数。
虚成员函数NewDimensions()用来得到控件移动后新的尺寸,rPt参数传递当前的鼠标位置。roRect参数传递当前的控件尺寸。riLeft, riTop, riWidth和riHeight返回新的控件尺寸,rbResize标签当控件完成改变大小时返回。如果需要,可以重载这个函数。
sm_hWE, sm_hNS, sm_hNWSE, sm_hNESW和sm_hDEF是预加载的鼠标句柄(sm_hDEF是默认的箭头鼠标,其他自说明)。
当改变大小时m_bTrack标记被设置为真。
所有的改变控件大小的实现都派生于IResizeControl接口。我只给了一个改变按钮大小的例子-CResizeButton类, 其他的控件(CResizeEdit, CResizeListBox)与其类似。CResizeButton从CButton和IResizeControl继承:
class CResizeButton : public CButton, public IResizeControl
前面已经说明过,WM_MOUSEMOVE, WM_SETCURSOR, WM_LBUTTONUP和WM_NCLBUTTONDOWN消息被用于每一个大小的改变的控件:
// ResizeButton.h :
header file
//...
//In class declaration
//{{AFX_MSG(CResizeButton)
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnNcLButtonDown(UINT nHitTest, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
// ResizeButton.cpp : implementation file
//...
BEGIN_MESSAGE_MAP(CResizeButton, CButton)
//{{AFX_MSG_MAP(CResizeButton)
ON_WM_MOUSEMOVE()
ON_WM_SETCURSOR()
ON_WM_LBUTTONUP()
ON_WM_NCLBUTTONDOWN()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
对于按钮的static edge风格来说,有一个特别的消息,WS_EX_STATICEDGE为了食WM_NCLBUTTONDOWN消息能够正常的工作,这个消息必须被设置。实现代码如下:
void
CResizeButton::OnMouseMove(UINT nFlags, CPoint point)
{
if(true == m_bTrack)
{
CRect oRect;
GetWindowRect(&oRect);
//Transform from screen coordinates to parent client
//coordinates
GetParent()->ScreenToClient(&oRect);
ClientToScreen(&point);
GetParent()->ScreenToClient(&point);
//Determine the new Dimensions
int iLeft, iTop, iWidth, iHeight;
bool bResize;
NewDimensions(point, oRect, iLeft, iTop, iWidth, iHeight,
bResize);
if(true == bResize)
{
SetWindowPos(NULL, iLeft, iTop, iWidth, iHeight,
SWP_NOZORDER);
//Notify the parent about size change
if(true == m_bNotify)
GetParent()->PostMessage(UWM_CONTROLRESIZE,
GetDlgCtrlID());
}
}
CButton::OnMouseMove(nFlags, point);
}
void CResizeButton::OnNcLButtonDown(UINT nHitTest, CPoint point)
{
SetCapture();
m_bTrack = true;
CButton::OnNcLButtonDown(nHitTest, point);
}
void CResizeButton::OnLButtonUp(UINT nFlags, CPoint point)
{
if(true == m_bTrack)
{
ReleaseCapture();
m_bTrack = false;
}
CButton::OnLButtonUp(nFlags, point);
}
BOOL CResizeButton::OnSetCursor(CWnd* pWnd, UINT nHitTest,
UINT message)
{
if(HTBORDER == nHitTest)
{
//Is on Border, find out where
CRect oRect;
GetWindowRect(&oRect);
POINT pt = GetCurrentMessage()->pt;
m_iPosition = FindPosition(pt, oRect);
switch(m_iPosition)
{
case POSN:
case POSS:
::SetCursor(sm_hNS);
break;
case POSE:
case POSW:
::SetCursor(sm_hWE);
break;
case POSNW:
case POSSE:
::SetCursor(sm_hNWSE);
break;
case POSNE:
case POSSW:
::SetCursor(sm_hNESW);
break;
}
}
else
::SetCursor(sm_hDEF);
//Message handled
return TRUE;
}
怎样使用这个接口
1.把ResizeControl.h, ResizeListBox.h, ResizeButton.h, ResizeEdit.h, ResizeControl.cpp, ResizeListBox.cpp, ResizeButton.cpp和ResizeEdit.cpp文件加入你的工程。
2.在以上的每一个文件中加入你的程序头文件的声明:
#include "TestDlg.h"
3.用资源编辑器创建控件。
4.在适当的地方,加入接口头文件的声明:
#include "ResizeListBox.h"
#include "ResizeButton.h"
#include "ResizeEdit.h"
5.在适当的地方,声明控件的成员变量:
ResizeListBox m_oResizeListBox;
CResizeButton m_oResizeButton;
CResizeEdit m_oResizeEdit;
6.在适当的初始化函数中子类化控件,例如,如果你要在对话框中使用这个控件,那么你应当在OnInitDialog()函数中子类化控件:
BOOL CTestDlg::OnInitDialog()
//...
m_oResizeListBox.SubclassDlgItem(IDC_LIST1, this);
m_oResizeButton.SubclassDlgItem(IDC_BUTTON1, this);
m_oResizeEdit.SubclassDlgItem(IDC_EDIT1, this);
|