Implementing Resizable Controls in VC++

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Environment: VC6, MFC

To implement an interactively resizable control (using the mouse), you generally need to treat the following Windows messages:

  • WM_SETCURSOR—to set the appropriate mouse cursor for the different regions of the control’s border. The following mouse cursors are used:
    • IDC_SIZEWE—when the mouse is on the left or right side
    • IDC_SIZENS—when the mouse is on the top or bottom side
    • IDC_SIZENWSE—when the mouse is on the top left or bottom right corner
    • IDC_SIZENESW—when the mouse is on the top right or bottom left corner
    • IDC_ARROW—when the mouse is on the control but not on the border)
  • WM_MOUSEMOVE—to resize the control with the mouse movement.
  • WM_NCLBUTTONDOWN—to start the resizing action.
  • WM_LBUTTONUP—to end the resizing action.

During the code development phase I detected some variables and actions that are general for all the resizable controls and for this reason I decided to move them in a general interface, IResizeControl, from which all the resizable controls are to inherit. Technically, IResizeControl is not an interface because it doesn’t have any pure virtual functions, but I still consider it an interface because it doesn’t make any sense to declare IResizeControl objects. In fact, I am preventing the creation of IResizeControl objects by declaring the IResizeControl’s constructor and destructor as protected. The IResizeControl class declaration is given below:


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;
};

The member functions EnableNorth(), EnableWest(), EnableSouth(), and EnableEast() are used to alter the set of resizable borders after construction (initially the set of resizable borders is decided at construction). If two adjacent borders are in the set of resizable borders, the corner in between is also active for mouse resizing; for example, if the member variables m_bN and m_bW are both true, the NW (top left) corner can be caught with the mouse and both borders can be resized at the same time.

The member functions SetWidth() and SetHeight() are used to change the minimal and maximum size limits of the control after construction (initially the size limits are decided at construction). You cannot interactively resize the control beyond these limits.

The user message UWM_CONTROLRESIZE is posted to the parent window each time the control is resized, but only if the bNotify flag is set true (this flag can be set only in the constructor). The UWM_CONTROLRESIZE message can be used by the parent window (which can be a dialog box, for example) to take some specific actions, for example, to resize other control accordingly.

The direction, size limits, and notification member variables can be decided from the constructor. All of them, excepting the notification flag, can be changed later.

The virtual member function FindPosition() is used to find the current mouse position. It decides whether the current position of the mouse cursor is inside or outside the argument rectangle, on the borders, or on the corners. The return values are from the enumeration:


enum { POSDEF=0, POSN=1, POSNW=2, POSW=3, POSSW=4, POSS=5,
POSSE=6, POSE=7, POSNE=8 };

The POSDEF value is used when the mouse is not on the rectangle’s border; the other values are self-explanatory. The current position on the control is also maintained by the m_iPosition member variable. This function can be overridden in derived classes if needed.

The virtual members function NewDimensions() is used to determine the new control’s dimensions when the mouse is moved. The rPt argument transmits the current mouse position. The roRect rectangle argument transmits the current control’s dimensions. The arguments riLeft, riTop, riWidth, and riHeight return the new control’s dimensions and the rbResize flag is informing back whether the control has to be resized. This function can be overridden in derived classes if needed.

The cursor handles sm_hWE, sm_hNS, sm_hNWSE, sm_hNESW, and sm_hDEF are keeping some preloaded mouse cursor (sm_hDEF is for the default arrow cursor, the other are self-explanatory).

The tracking flag m_bTrack is set to true only during the resizing operation.

All the implemented resizable Windows controls are deriving from the IResizeControl interface. I give as a code example only the resizable button, CResizeButton class, the other (CResizeEdit, CResizeListBox) being similar. CResizeButton inherits from both CButton and IResizeControl:

class CResizeButton : public CButton, public IResizeControl

As explained before, the messages WM_MOUSEMOVE, WM_SETCURSOR, WM_LBUTTONUP, and WM_NCLBUTTONDOWN have to be treated by each resizable control:


// 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()

One peculiarity for the button control is that the static edge style, WS_EX_STATICEDGE, has to be set for the WM_NCLBUTTONDOWN message to work properly (it can be easily set from the Resource Editor). The implementation of the message handlers is given below:


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;
}

How to Use

  1. Copy the files: ResizeControl.h, ResizeListBox.h, ResizeButton.h, ResizeEdit.h, ResizeControl.cpp, ResizeListBox.cpp, ResizeButton.cpp, and ResizeEdit.cpp into your project.
  2. Replace everywhere in these files the line

    #include “TestDlg.h”

    with your application’s header.

  3. Create the controls from the Resource Editor.
  4. Include the header files in your class header, where appropriate:

  5. #include “ResizeListBox.h”
    #include “ResizeButton.h”
    #include “ResizeEdit.h”

  6. Declare the member variable controls in your class declaration, where appropriate:

  7. ResizeListBox m_oResizeListBox;
    CResizeButton m_oResizeButton;
    CResizeEdit m_oResizeEdit;

  8. Subclass the controls in the appropriate initialization function; for example, if you use the controls in a Dialog Box, the appropriate place is the OnInitDialog() function:

  9. BOOL CTestDlg::OnInitDialog()
    //…
    m_oResizeListBox.SubclassDlgItem(IDC_LIST1, this);
    m_oResizeButton.SubclassDlgItem(IDC_BUTTON1, this);
    m_oResizeEdit.SubclassDlgItem(IDC_EDIT1, this);

Downloads


Download demo project – 20 Kb


Download source – 8 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read