Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MDISnap

0.00/5 (No votes)
29 Nov 2004 1  
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here