Click here to Skip to main content
15,883,883 members
Articles / Programming Languages / C#
Article

SnapFormExtender - a magnet for your MDI child forms

Rate me:
Please Sign up or sign in to vote.
4.61/5 (14 votes)
4 Mar 20043 min read 71.2K   3K   37   10
An extender provider that draws MDI child forms to another form's edges while moving or resizing
Figure 1

Introduction

Developing user interface we always wish to make it nice and neat. Of course, MDI application with accurately aligned child forms looks better but it's wastefulness to write heavy-weighting code for such trifles. That's why I decided to write a small and simple component that attracts a child form to the closest border while a user moves or resize it. Starting this work, I've set a few conditions for myself:

  • It should be an IExtenderProvider component in order to place it on the parent's form and forget about it
  • It shouldn't be a platform-dependent solution. I've refused to use Win32 API (though, I've regretted later)
  • To try to avoid overriding of WndProc because this code may interfere with already written one

Implementation

At first, I've created IExtenderProvider component:

C#
public class SnapFormExtender : Component, IExtenderProvider, ISupportInitialize

ISupportInitialize contains two methods: BeginInit() and EndInit(). I used the last one in order to hook event handler to the parent's form when initialization is completed. Then I've written CanExtend(object component) method that allows providing extender properties to the parent MDI form:

C#
public bool CanExtend( object component )
{
   if( component is Form )
   {
      Form form = ( Form )component;
      return form.IsMdiContainer;
   }
   return false ;
}

Next, I've added three properties to the component:

  • Enabled - enable/disable to align child forms
  • Form - parent MDI form
  • Distance - maximum distance in pixels at which a child form will be attracted to a closest border

Every time a child form is activated, two event handlers will be added to the form:

C#
private void parentForm_MdiChildActivate(object sender, EventArgs e)
{
   if( parentForm.ActiveMdiChild != null )
   {
      Form child = parentForm.ActiveMdiChild;
      child.Move   -= new EventHandler(child_Move);
      child.Resize -= new EventHandler(child_Resize);
      if( enableSnap )
      {
         child.Move   += new EventHandler(child_Move);
         child.Resize += new EventHandler(child_Resize);
      }
   }
}

Now we can control size and movement of the child form. Using our event handlers we intercept Control.Move and Control.Resize events. The last thing to do is to compare distance between our active child edge (formEdge1) and rest edges with the same orientation (see Figure 2).

Figure 2

If we find a closest edge at a range of the snap distance (property Distance), we move (or resize) our active form. So, the closest edges get the same coordinate and our forms become aligned. Pretty easily, isn't!

... and add it to your taste

Compile and run our test application SnapFormExtenderTest. If you create your own application using this component, you should add it to your VS toolbox:

  • Right-click on the "Windows Forms" tab in the Toolbox
  • Choose "Add/Remove Items..."
  • On the ".NET Framework Components", press the "Browse..." button and find a SnapFormExtender.dll

That will add a "SnapFormExtender" icon to the Windows Forms tab. Drag this icon to your MDI parent form. Select the snapFormExtender object on the Design View and set its Form property to your MDI parent form:

Figure 3

If you open InitializeComponent() source code, you will see something like this:

C#
this.snapFormExtender = new SnapFormExtender.SnapFormExtender(this.components);
((System.ComponentModel.ISupportInitialize)(this.snapFormExtender)).BeginInit();
this.snapFormExtender.Form = this;
((System.ComponentModel.ISupportInitialize)(this.snapFormExtender)).EndInit();
By default snap distance is 4 pixels. But you can change it at any time:
C#
snapFormExtender.Distance = 6;     // Set snap distance to 6 pixels.
snapFormExtender.Enabled = false;  // Or disable it at all

I have added "Snap Distance" menu to the test - try to play with different values.

"I watch you move, so smooth..."

If you have started the test application, you've possibly noticed some funny thing. When you are trying to resize a child form pulling an edge that is already stuck to another border, the child form behaves like a caterpillar. While displacement is less than the snap distance, the border stays on place but the form changes its size. Very funny. But maybe you don't like it. Well, then just cut the SnapOnResize() out or decrease the snap distance.

But seriously, the reason of that behavior is missing WM_SIZING, WM_MOVING, WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE events and corresponding event handlers for the Control component. Without it we cannot catch the moment when a user starts resizing. That's why I said I regret that rejected Win32 API. Next idea was to use PreFilterMessage(ref Message m). But after a few tests it's turned out that this filter doesn't monitor all necessary messages. Overriding WndProc or setting event hook with SetWindowsHookEx() I would make the code rather awkward and difficult to build into new projects.

Anyway, I think this component has a reasonable balance of simplicity in use and functionality.

History

  • March 4th, 2004 - First release.

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


Written By
Latvia Latvia
Jevgenij lives in Riga, Latvia. He started his programmer's career in 1983 developing software for radio equipment CAD systems. Created computer graphics for TV. Developed Internet credit card processing systems for banks.
Now he is System Analyst in Accenture.

Comments and Discussions

 
GeneralThank you Pin
El Chubb25-Jun-09 18:25
El Chubb25-Jun-09 18:25 
GeneralRe: Thank you Pin
El Chubb25-Jun-09 18:38
El Chubb25-Jun-09 18:38 
QuestionLicense? Pin
Gilvinit26-Feb-09 0:59
Gilvinit26-Feb-09 0:59 
AnswerRe: License? Pin
Jevgenij Pankov26-Feb-09 6:36
Jevgenij Pankov26-Feb-09 6:36 
GeneralRe: License? Pin
Gilvinit26-Feb-09 8:54
Gilvinit26-Feb-09 8:54 
GeneralVery adaptable Pin
GravitySpec16-Jan-07 12:06
GravitySpec16-Jan-07 12:06 
GeneralSimilar ... Pin
Corneliu Tusnea26-Apr-04 19:21
Corneliu Tusnea26-Apr-04 19:21 
Hi,

Have a look at my component http://www.codeproject.com/csharp/StickyWindows.asp[^].
It uses the Win32 API to take care of the resizing.
I've also overcome your "caterpillar" problem. Maybe you have the time to make it work for MDI windows also.

Good luck,
Tutu.
GeneralRe: Similar ... Pin
lecobf17-Jan-06 5:12
lecobf17-Jan-06 5:12 
GeneralRe: Similar ... Pin
glacialfury20-Jul-06 11:33
glacialfury20-Jul-06 11:33 
GeneralQuite Helpful Pin
surgeproof12-Mar-04 7:43
surgeproof12-Mar-04 7:43 

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.