环境: 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); |