Introduction
I started to write this class because I'm often in need to popup additional dialogs around the main one. Often these dialogs can give some trouble to the user; for example, he must move/close them one by one... A solution that could give the application a more solid aspect and that could make the management of the various windows easier could be, to dock all dialogs side by side (like Winamp does, for example).
Background
First of all: when I talk about parent dialog I mean the main dialog and the children are dialogs that can dock to the parent. I know that this isn't exactly the meaning that MS gives to these words but I can't think anything better ;) The class code is really simple. All my work was focused on two points: "the magnetic field" management that enables dialogs to dock, and avoid flickering when windows are forced to dock (i.e. you are inside the field ;)). The first point wasn't a real problem: my solution was to build around the parent dialog four rectangles (one for each side); if the center point of the correspondent child window's edge is inside the rectangle the child is docked.
Here is a snapshot of the code:
m_pMagParentDlg->GetWindowRect(rectParent);
rectRight = CRect(rectParent.right-rHight,rectParent.top-rWidth,
rectParent.right+rHight,rectParent.bottom+rWidth);
curPl = CPoint(pRect->left,
pRect->top+(pRect->bottom-pRect->top)/2);
if ((m_dwMagType == DKDLG_RIGHT || m_dwMagType == DKDLG_ANY) &&
rectRight.PtInRect(curPl))
{
m_pMagParentDlg->DockMagneticDialog(this,DKDLG_RIGHT);
nEdge = DKDLG_RIGHT;
m_bDocked = TRUE;
}
I spent some time to find a solution to the second problem (avoid flickering). I wanted to reproduce the same effect of Winamp dialogs: childrent that go inside the "magnetic field" of the parent will be pushed to dock. The code behind this effect is simple (a MoveWindow
is enough...). The real problem is: where to put the code? Using the ON_WM_MOVING
message is bad because this message is cast after the actual window movement so the resulting effect is really ugly (more than a simple flicker!). After some research, I found the right message to catch: ON_WM_WINDOWPOSCHANGING
.
The prototype of this function is:
void CMagDialog::OnWindowPosChanging(WINDOWPOS* lpwndpos)
where lpwndpos
is a structure that contain the coordinates to which the window will be moved. Filtering these coordinates according to my need is the trick.
void CMagDialog::OnWindowPosChanging(WINDOWPOS* lpwndpos)
{
if ((m_bDocked) &
(m_pMagParentDlg != NULL) &&
(m_bDisablePosFix == FALSE))
{
CRect tmpRect,rectParent;
GetWindowRect(tmpRect);
m_pMagParentDlg->GetWindowRect(rectParent);
if (nEdge == DKDLG_RIGHT)
{
tmpRect.MoveToXY(rectParent.right,lpwndpos->y);
}
}
lpwndpos->x = tmpRect.left;
lpwndpos->y = tmpRect.top;
lpwndpos->cx = tmpRect.Width();
lpwndpos->cy = tmpRect.Height();
CDialog::OnWindowPosChanging(lpwndpos);
}
Using the code
To use magnetic dialogs in your project, simply derive both the parent dialog and the children (see the Background section about the definition of parent and children) from the CMagDialog
class. The children must be all modeless dialogs and should be defined inside the parent dialog class (the same thing happens when you are using dockable windows in a DOC/View architecture). Then you must call AddMagneticDialog
and EnableMagnetic
methods in the parent dialog for each child.
Let's say that m_MyMagDlg
is a modeless dialog declared in theparent dialog class. The OnInitDialog
will look like:
BOOL CCMagTestDlg::OnInitDialog()
{
AddMagneticDialog(&m_MyMagDlg,TRUE,DKDLG_RIGHT);
m_MyMagDlg.EnableMagnetic(DKDLG_ANY,this);
m_MyMagDlg.Create(IDD_DIALOG_LIBPREVIEW,this);
return TRUE;
}
As you can see in the demo, the class also remembers the relative position between the children and the parent so, if you close one child and move around the parent, when you will show the child again, it will placed in the same relative position that it had before.
Overview
These are the definitions of the two public methods of CMagDialog
:
void AddMagneticDialog(CMagDialog* pDialog,
BOOL bDocked = FALSE,DWORD dwMagWhere =0);
PDialog
- to child dialog.
bDocked
- if true
, the child window popup is already docked.
dwMagWhere
- where the dialog docks; can have the following values:
DKDLG_ANY
DKDLG_LEFT
DKDLG_RIGHT
DKDLG_TOP
DKDLG_BOTTOM
void EnableMagnetic(DWORD dwMagType,CMagDialog* pMagParentDlg);
dwMagType
- where the dialog can dock; values are the same as dwMagWhere
described previously.
pMagParentDlg
- pointer to the parent dialog window.
Remarks:
dwMagWhere
refers to the parent's window side. So DKDLG_LEFT
means that children will docked to the left side of the parent window.
History
2005/11/07
License
This program/code is free and provided "as is" without any expressed or implied warranty. Use at your own risk!