SnapFormExtender - a magnet for your MDI child forms






4.61/5 (13 votes)
Mar 5, 2004
3 min read

72027

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 formsForm
- parent MDI formDistance
- 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.