Click here to Skip to main content
15,860,861 members
Articles / Desktop Programming / MFC

MFC Snapping Windows

Rate me:
Please Sign up or sign in to vote.
4.68/5 (17 votes)
27 May 2014GPL34 min read 44.2K   2.6K   56   7
Easily create windows that snap to each other.

Download demo project - 140 KB

Sample Image

Introduction

The classes provided are the result of a desire to have an easy to use set of objects to allow windows to snap/dock to one another. There are some very good options available, most of which appear on this site. However, they all had an issue that kept bothering me, you had to derive a class from that base class. At that point, you are stuck with whatever the type of object from which that class was derived from. So, out of this comes a set of template classes that are designed to work together to allow different types of windows to snap/dock to each other.

Background

As with other snapping/docking windows, there is a parent child relationship between the windows you will be creating. This is a 1 to many relationship, and the classes have been designed as such. Any parent window (the window be docked to) can have multiple children (windows that are docked), and each child will have only one parent. The classes therefore are designed in a parent/child relationship, where the child maintains a map of child HWNDs and the child maintains a parent HWND. This leads to an interesting configuration, since these are template classes, a child of one window can also be the parent of its own set of children windows.

The biggest issue I had when deciding to create these classes was getting the window objects to have an idea of what is happening between them, but still be generic and easy to use. What I ended up using were a few user defined messages that are posted between the windows. Whenever the parent window is moved, the child receives a notification message. It is then up to the child window to move itself where it needs to be. It is also important to note, that only children that are docked are notified. How is this accomplished? The parent holds a map of children, and when a child is docked, the child posts a message to the parent at which the map is updated to with the dock status.

Here is the parent code that does the move notification and handles the update notification:

C++
if (message == WM_MOVE)
{
    LRESULT lResult = CExtPSBase::WindowProc(message, wParam, lParam);
    NotifyUnsnapped();
    return lResult;
} // WM_MOVE
if ((message == WM_MOVING) ||
    (message == WM_SIZING))
{
    LRESULT lResult = CExtPSBase::WindowProc(message, wParam, lParam);
    MoveSnappers(wParam, lParam);
    return lResult;
} // WM_MOVING
if (message == WM_DOCK_STATUS)
{
    HWND _hwnd = (HWND)wParam;
    BOOL _bIsDocked = (BOOL)LOWORD(lParam);
    UINT _nDockSide = (UINT)HIWORD(lParam);
    UpdateDockStatus(_hwnd, _bIsDocked, _nDockSide);
    return 0;
} // WM_DOCK_STATUS

The function NotifyUnsnapped posts a message to all undocked windows that it has moved, and the child will then check its position relative to the parent window. If the child is within the snapping offset, it will snap itself to the parent, and notify the parent that it has docked.

MoveSnappers is the function that notifies windows that are docked that the window is moving, at which point, the children will then move themselves along with the parent.

UpdateDockStatus simply finds the window handle in the map, and updates the entry with the dock status and the side the window is docked to.

The majority of the code in the children simply check window positions. If the window is within the dock offset, it simply posts a message back to the parent.

These clases are also designed to clean up after themselves. When the child window receives either WM_DESTROY or WM_NCDESTROY it will notify the parent to remove its handle from the map. The parent also checks the map for invalid handles, and removes them. The parent also cleans up the map object when it is destroyed as well.

Using the code

Using the code is rather simple. First, include CExtWS.h in any file where you plan on using either of the classes. Then when you create a CDialog object (or really any object that will receive WM_MOVE or WM_MOVING messages), simply add the template CExtPS<CDialog>.

C++
class CTestDialog : public CExtPS <CDialogEx>

At this point, the dialog now has all the functionality to allow windows to dock to it.

When you create a window that you want to dock to other windows, add the template CExtCS<> to your class and it can be docked.

Once you have done this, you would simply create a modeless dialog like you would normally, and then use the AddSnapper function to add it to the map.

C++
// m_pTestWSDialog is defined in the header as CTestWSDialog * member
m_pTestWSDialog = new CTestWSDialog();
m_pTestWSDialog->Create(CTestWSDialog::IDD, this);
// Change the snap offset if you like, default is 10 pixels in either direction
// This line will change it to 20 pixels in either direction
m_pTestWSDialog->SetSnapOffset(20);
m_pTestWSDialog->ShowWindow(SW_SHOW);
AddSnapper((CWnd*)m_pTestWSDialog);
// If you would like to open the window already snapped you could do this:
m_pTestWSDialog->DockNow(DOCK_RIGHT);

Points of Interest

The child window will not snap while you are dragging it. I find that a window that jumps when you are dragging is annoying. So the child will snap after you stop dragging if it is within the snap offset. It will however snap automatically if the parent window is being dragged.

History

05/27/2014

  • Changes for new versions of Visual Studio: If you are not supporting Windows XP, GetWindowRect reports the correct window sizes for Aero Glass if WINVER >= 6. Add to the C/C++ preprocessor directives for all configurations for the project _MSC_PLATFORM_TOOLSET=$(PlatformToolsetVersion)
  • In CExtWS.h, function UpdateWindowPos was updated to offset the window ONLY if the Platform Toolset set in Visual Studio targets OS versions before Vista.

10/30/2012

  • Now checks for Aero Glass, if enabled gets the PaddedBorderWidth from registry to set Aero Offset for snapping
  • Intercepts WM_SETTINGCHANGE and rechecks compositing and PaddedBorderWidth
  • Automatically adjusts snapoffset value by paddedborderwidth when glass enabled

10/24/2012

  • Windows now move when not showing window contents while dragging.
  • Now checks for Aero Glass and offsets window to prevent overlap (customizable)
  • Sample app now uses a list structure to allow for multple snapping windows without crashing

06/12/2012

  • First release.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Systems / Hardware Administrator
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMy Vote of 5 Pin
Manikandan103-Jun-14 20:31
professionalManikandan103-Jun-14 20:31 
GeneralRe: My Vote of 5 Pin
Vachaun224-Jun-14 1:44
Vachaun224-Jun-14 1:44 
QuestionYour codes r impressive! Pin
Sun-Mi Kang25-May-14 14:15
Sun-Mi Kang25-May-14 14:15 
GeneralMy vote of 5 Pin
SemiEleven21-Apr-13 17:56
SemiEleven21-Apr-13 17:56 
GeneralMy vote of 5 Pin
magicpapacy19-Nov-12 16:51
magicpapacy19-Nov-12 16:51 
BugProgram crashes with 3 windows. Pin
DuckRoll15-Jul-12 21:47
DuckRoll15-Jul-12 21:47 
1. Run the Release version.
2. Press "Snapper". Snap new window (2nd) to the main one.
3. Move clued windows (arbitrary).
4. Press "Snapper" again. 3rd window pops up. Don't snap window to main one.
5. Repeat step 3 (newest window won't move).
6. Press OK in 3rd window.
7. Press OK in 2nd window. Program crashes.

BTW, I don't absolutely sure that steps 3 and 6 are necessary.
GeneralRe: Program crashes with 3 windows. Pin
Vachaun2216-Jul-12 1:53
Vachaun2216-Jul-12 1:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.