Implementing Resizable Controls in VC++

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


Comments

  • Tracking the moment when the mouse Pointer changes to Resize bars [WM_SETCURSOR, HT_xx]

    Posted by bluredEn on 04/23/2007 05:28am

    Thanks for the article. I was looking for the IDC_SIZExx values for
    detecting the change event of the mouse cursor when it is brought to the
    edge of my resizable dialogs. Basically I need to redraw our custom titlebar
    just when the mouse cursor changes to the resize cursor. I hope the solution
    i'm trying to get to will work.
    
    This is my issue.!! if it may be of any use to anyone:- My dialog's Caption
    bar is custom drawn a bright-red clolor inside my application. I have hidden
    the Max/Min buttons from my dialog and disabled the Close (x) button on the
    top-right. My dialog Titlebar now displays my own button bitpmaps instead of
    the default buttons for the Max/Min/Close buttons. The problem is that -
    when I move my mouse pointer (cursor) to the edge of the dialog, making it
    change to a resize (cursor) pointer, the dialog's disabled Close button
    peeks out from behind the bitmap for close that I have drawn.
    
    SO NOW I PLAN, to redraw the titlebar just when the resize-cursor appears.
    
    So now I guess I can use something like:
    
        switch(uMsg)
        {
            case WM_SETCURSOR:
                CallWindowProc(pTDraw->wndOrgWndProc, hwndTarget, uMsg, wParam, lParam);
                if ((HTLEFT        == LOWORD(lParam))    ||
                    (HTRIGHT       == LOWORD(lParam))    ||
                    (HTTOP         == LOWORD(lParam))    ||
                    (HTBOTTOM      == LOWORD(lParam))    ||
                    (HTTOPLEFT     == LOWORD(lParam))    ||
                    (HTTOPRIGHT    == LOWORD(lParam))    ||
                    (HTBOTTOMLEFT  == LOWORD(lParam))    ||
                    (HTBOTTOMRIGHT == LOWORD(lParam))) {
    
                    ReDrawMyTitle(); // this will redraw
    			
                    return TRUE;
    		}
        }
    
    Now when ever the mouse pointer changes to a Resize bar, ReDrawMyTitle()
    will be called. Yupeeeee.!

    Reply
  • Bug Fix

    Posted by Legacy on 01/20/2003 12:00am

    Originally posted by: cvogt61457

    Need to fix the MaxWidth and MaxHeight

    //Change Limits
    inline bool IResizeControl::SetWidth(int iMinWidth, int iMaxWidth)
    {
    if((iMinWidth>0)&&(iMaxWidth>0)&& (iMinWidth<iMaxWidth))
    {
    m_iMinWidth = iMinWidth;
    m_iMaxWidth = iMaxWidth; // was MinWidth
    return true;
    }
    return false;
    }

    inline bool IResizeControl::SetHeight(int iMinHeight, int iMaxHeight)
    {
    if((iMinHeight>0)&&(iMaxHeight>0)&&(iMinHeight<iMaxHeight))
    {
    m_iMinHeight = iMinHeight;
    m_iMaxHeight = iMaxHeight; // was MinHeight
    return true;
    }
    return false;
    }

    Reply
  • Good solution

    Posted by Legacy on 01/19/2003 12:00am

    Originally posted by: Caprice

    The parent controls the size of own childs - very simple solution and because of it it's great. :)

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds