65.9K
CodeProject is changing. Read more.
Home

SnapFormExtender - a magnet for your MDI child forms

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.61/5 (13 votes)

Mar 5, 2004

3 min read

viewsIcon

72027

downloadIcon

3055

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:

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:

   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:

   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:

   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:
   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.