Download demo project - 140 KB
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 HWND
s 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:
if (message == WM_MOVE)
{
LRESULT lResult = CExtPSBase::WindowProc(message, wParam, lParam);
NotifyUnsnapped();
return lResult;
} if ((message == WM_MOVING) ||
(message == WM_SIZING))
{
LRESULT lResult = CExtPSBase::WindowProc(message, wParam, lParam);
MoveSnappers(wParam, lParam);
return lResult;
} 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;
}
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>
.
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.
m_pTestWSDialog = new CTestWSDialog();
m_pTestWSDialog->Create(CTestWSDialog::IDD, this);
m_pTestWSDialog->SetSnapOffset(20);
m_pTestWSDialog->ShowWindow(SW_SHOW);
AddSnapper((CWnd*)m_pTestWSDialog);
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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.