Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / MFC
Article

MDISnap

Rate me:
Please Sign up or sign in to vote.
5.00/5 (24 votes)
29 Nov 2004CPOL4 min read 128.1K   3.9K   47   15
Do your users a favor - add snapping edges to MDI child windows (or anywhere else).

Introduction

Although Multiple Document Interface (MDI) is less common these days, it's still a powerful choice for advanced applications. To make arranging the MDI child windows easier, MDISnap adds "magnetic edges"  to the MDI client area borders and other child windows, so arranging windows gets easier.

The best idea is to play with the sample application. Open some windows and tile them ([H] for horizontal, and [V] for vertical tile), then play around!

Update: I've added the following changes:

  • disable magnetic edges when using shift
  • magnetic edges disabled when move/size is initiated through the system menu (that's much better than the "jumping windows" I had before)
  • Holding down Ctrl, you can swap two windows
  • Brushed up the sample application a bit

The first two changes were submitted by a_zur. Thank you!

Using it

To use MDI Snapper, include this in your project:

  1. Declare a variable CMDISnapper m_mdiSnap accessible from all (e.g., in CFooApp or CMainFrame object)
  2. Adjust the snap wdith by calling m_mdiSnap.SetSnapWidth(int).
  3. For CChildFrame (and other CMDIChildWnd-derived classes you use), override WindowProc, and call m_mdiSnap.OnMessage before passing the message to the base class implementation.

Implementation Overview

struct SNAPINFO implements the logic for a single window, and holds required state variables. A typical call (e.g., in WM_SIZING / WM_MOVING) looks like this:

// ----- 1. Init -----
SNAPINFO sninf;
// old, new  position, size of catch area
sninf.Init(rectOld, rectNew, snapWidth);  

// ----- 2. provide horizontal/vertical "snap" lines -----
sninf.SnapVLine(0);                // snap to left border
sninf.SnapHLine(0);                // snap to top border
sninf.SnapVLine(clientArea.right); // snap to right border
sninf.SnapHLine(clientArea.bottom);// snap to bottom border 

// ... additional snap lines
// ... e.g. the borders of other MDI child windows

// ----- 3. finish  -----
sninf.EndSnap();
rectNew = sninf.rout; 

rectNew could then be used as corrected position of the window.

CMDISnapper implements exactly this for MDI child windows. It assumes and uses MDI client area coordinates in all places and does the appropriate conversions.

Integration in other projects / environments

SNAPINFO can be used in any Windows project and does the basic calculations. CMDISnapper uses MFC classes, but can be easily ported to e.g., ATL or Win32 API, as it does not rely on MFC specifics.

Documentation

struct SNAPINFO

  • Init(RECT const & oldRect, RECT const & newRect, DWORD snapWidth)

    Prepare calculation for adjusting a window while it is resized or moved.

  • Init(RECT const & r, DWORD snapWidth, bool moveOnly)

    Prepare calculation  for adjusting a single window, independent of changes.

  • void SnapHLine(long y)

    Specify the y coordinate of a horizontal line where one of the window edges could snap to. The line will be taken into consideration only if it's close enough to the top or bottom edge of the window.

  • void SnapVLine(long y)

    Guess what.

  • RECT & EndSnap()

    returns a reference to rect which contains the final coordinates after EndSnap was called.

class CMDISnapper

  • CMDISnapper(DWORD snapWidth = 8)

    MDISnapper constructor, at your service. If you already know what you want, you can specify a snap width.

  • void SetSnapWidth(DWORD snapWidth)
  • DWORD GetSnapWidth() const

    Set / retrieve snap width. It will be used with the next relevant message (currently, WM_SIZING and WM_MOVING) processed.

  • LRESULT OnMessage(CWnd * wnd, UINT msg, WPARAM wp, LPARAM lp)

    Call this function in the window proc of the window(s) that should snap. Pass the window, the message sent, and its parameters. Return value can be ignored. Forwards the following messages to specific handlers: WM_SIZING, WM_MOVING, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE.

  • void Sizing(CWnd * wnd, RECT & rnew)

    Called from WM_SIZING and WM_MOVING handler; retrieves the current rect, iterates through all visible sibling windows, and adjusts the new size/position rectangle specified in rnew.

Bugs, Limitations, Notes

  • Move via keyboard does not use the "magnetic" feature (but at least it's usable now).
  • In the current implementation, edges will snap to other edges even when they are "far away" (open seven windows in the sample app, tile them vertically, close the two in the middle, and play, you will see what I mean). This behavior is by design (sic!). If you want to snap only to "close" edges, you can do the following in CMDISnapper::Sizing, when you iterate the child windows:
    • Store a copy of the original rect of the sizing window, inflated by a few pixels (snapWidth comes into mind).
    • Call SnapHLine only when the y coordinate falls into this inflated rect, similar for SnapVLine.
  • Using the "old" and "new" rect in SNAPINFO is a bit useless, as the old coordinates are used only to check whether the window is sized or moved. I originally intended to snap only into the direction of the mouse move, but this resulted in much more complex calculations and stupid behavior.
  • Pressing/releasing Shift or Control requires a minimal mouse move for the display to be updated. Automatically doing so is quite expensive (it requires a keyboard hook or a timer, and Sizing would have to remember the original, unadjusted position).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Klippel
Germany Germany
Peter is tired of being called "Mr. Chen", even so certain individuals insist on it. No, he's not chinese.

Peter has seen lots of boxes you youngsters wouldn't even accept as calculators. He is proud of having visited the insides of a 16 Bit Machine.

In his spare time he ponders new ways of turning groceries into biohazards, or tries to coax South American officials to add some stamps to his passport.

Beyond these trivialities Peter works for Klippel[^], a small german company that wants to make mankind happier by selling them novel loudspeaker measurement equipment.


Where are you from?[^]



Please, if you are using one of my articles for anything, just leave me a comment. Seeing that this stuff is actually useful to someone is what keeps me posting and updating them.
Should you happen to not like it, tell me, too

Comments and Discussions

 
QuestionMDI only? Pin
mikejobu2311-Feb-10 10:10
mikejobu2311-Feb-10 10:10 
AnswerRe: MDI only? Pin
peterchen11-Feb-10 11:58
peterchen11-Feb-10 11:58 
GeneralGood Job Pin
yv310-Sep-08 22:12
yv310-Sep-08 22:12 
GeneralNice class Pin
Jaime Stuardo8-Mar-05 12:59
Jaime Stuardo8-Mar-05 12:59 
GeneralNice, indeed Pin
Jörgen Sigvardsson7-Dec-04 9:22
Jörgen Sigvardsson7-Dec-04 9:22 
GeneralNice! Pin
Ravi Bhavnani30-Nov-04 0:04
professionalRavi Bhavnani30-Nov-04 0:04 
GeneralCool Pin
Brian Delahunty29-Nov-04 22:34
Brian Delahunty29-Nov-04 22:34 
Generalapp that makes all your windows snap Pin
Ivan Heckman28-Jun-02 12:04
Ivan Heckman28-Jun-02 12:04 
GeneralRe: app that makes all your windows snap Pin
Huyong Zhao16-Aug-07 17:35
Huyong Zhao16-Aug-07 17:35 
QuestionWhat about pics? Pin
Christian Rodemeyer14-Oct-01 21:43
professionalChristian Rodemeyer14-Oct-01 21:43 
AnswerRe: What about pics? Pin
peterchen15-Oct-01 3:09
peterchen15-Oct-01 3:09 
GeneralRe: What about pics? Pin
Christian Rodemeyer15-Oct-01 8:33
professionalChristian Rodemeyer15-Oct-01 8:33 
GeneralNow it's my turn.. Pin
peterchen15-Oct-01 9:47
peterchen15-Oct-01 9:47 
GeneralRe: Now it's my turn.. Pin
Christian Rodemeyer16-Oct-01 8:21
professionalChristian Rodemeyer16-Oct-01 8:21 
GeneralRe: Now it's my turn.. Pin
peterchen16-Oct-01 10:32
peterchen16-Oct-01 10:32 

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.