Click here to Skip to main content
Click here to Skip to main content

Using Extension Methods to Create Form Extensions

, 5 Feb 2009 Ms-PL
Rate this:
Please Sign up or sign in to vote.
This article demonstrates the use of the Extension Methods language feature of C# 3.0.

JonasButt Windows Forms Extensions

Introduction

The provided source code demonstrates the use of the Extension Methods language feature of C# 3.0.

I consider myself to be a junior .NET (C#) developer. Not long ago, I started programming small to midsize Windows Forms applications using the .NET framework and Visual Studio. I recently upgraded to .NET 3.5 and Visual Studio 2008, and I'm now particularly interested in the C# language and its (recently added) features.

On one programming project, I needed to create a form containing some controls. The form had to support resizing, and I noticed my controls remained at the same location when the form was resized. I played around with the Dock property and the Anchor property of the control, and this solved the problem in some cases. I also tried using the FlowLayoutPanel and TableLayoutPanel, but I wasn't very satisfied with the results. I decided I had do the moving of the controls myself, so I wrote this code:

#region Resizing Support Methods

private Size previousSize = Size;

private void theForm_ResizeBegin(object sender, EventArgs e)
{
    previousSize = Size;
}

private void theForm_SizeChanged(object sender, EventArgs e)
{
    previousSize = Size;
}

private void theForm_Resize(object sender, EventArgs e)
{
    int widthDifference = Size.Width - previousSize.Width;
    int heightDifference = Size.Height - previousSize.Height;

    MoveControl(findButton, widthDifference, 0);
    MoveControl(aboutButton, widthDifference, heightDifference);
    MoveControl(helpLabel, 0, heightDifference);
    MoveControl(saveToFileButton, widthDifference, heightDifference);
}

private static void MoveControl(Control control, int x, int y)
{
    control.Location = new Point(control.Location.X + x, control.Location.Y + y);
}

#endregion

This works fine, but this code was duplicated for each form class in the application. Furthermore, I figured that this code was not supposed to be in the form class itself, but separated in some generic way to improve reuse. Using two Extension Methods, I was able to dramatically simplify code in the form class itself. In the section "Using the code" below, you can see how easy it is to add this support for any form.

Below is the class diagram for the form extension code.

JonasButt Windows Forms Extensions Class Diagram

Background

The following text is extracted from the official C# 3.0 language specification. It is Copyright of Microsoft Corporation 1999-2007. All Rights Reserved. Tip: download the full C# 3.0 specification here.

When the first parameter of a method includes the this modifier, that method is said to be an Extension Method. Extension Methods can only be declared in non-generic, non-nested static classes. The first parameter of an Extension Method can have no modifiers other than this, and the parameter type cannot be a pointer type. The following is an example of a static class that declares two Extension Methods:

public static class Extensions
{
    public static int ToInt32(this string s) {
        return Int32.Parse(s);
    }
    public static T[] Slice<t />(this T[] source, int index, int count) {
        if (index < 0 || count < 0 || source.Length – index < count)
            throw new ArgumentException();
        T[] result = new T[count];
        Array.Copy(source, index, result, 0, count);
        return result;
    }
}

An Extension Method is a regular static method. In addition, where its enclosing static class is in scope, an Extension Method can be invoked using the instance method invocation syntax (§7.5.5.2), using the receiver expression as the first argument. The following program uses the Extension Methods declared above:

static class Program
{
    static void Main() {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in strings.Slice(1, 2)) {
            Console.WriteLine(s.ToInt32());
        }
    }
}

The Slice method is available on the string[], and the ToInt32 method is available on string, because they have been declared as Extension Methods. The meaning of the program is the same as the following, using ordinary static method calls:

static class Program
{
    static void Main() {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in Extensions.Slice(strings, 1, 2)) {
            Console.WriteLine(Extensions.ToInt32(s));
        }
    }
}

Using the Code

To use the extension for a given form, you need to call the EnableResizeSupport() Extension Method in the constructor of that form. Next, for each control in your form you want the extension operate on, call the AddMovableControl() Extension Method. This method expects a reference to the control and the type of the move behaviour the control should have. These types, encapsulated in an enum named MovableControlType, are:

  • Both: the control moves both horizontally and vertically when the form is resized;
  • Vertical: the control moves vertically when the form is resized;
  • Horizontal: the control moves horizontally when the form is resized.
public Form1()
{
    InitializeComponent();
    this.EnableResizeSupport();
    this.AddMovableControl(secondFormButton, MovableControlType.Both);
    this.AddMovableControl(exitButton, MovableControlType.Both);
}

Points of Interest

When first writing the code, the thought arose that the extension would probably work fine for one form, but would have unexpected results when used in multiple forms at the same time. This problem was due to the lack of state of the static class implementing the Extension Methods. This problem was overcome by adding a state using a Dictionary containing the references to the forms, the controls added by that form, and the previous size of the form. Each form is identified by its handle, which is the key for the Dictionary.

Critics might not be in favor of this solution (adding state to a static class), but in this case, it works out pretty neat. I'm open to receive constructive criticism regarding this implementation detail.

internal class FormResizeExtensionState : Dictionary<string, >
{
}

internal class FormResizeExtensionStateEntry
{
    public Size PreviousSize
    {
        get;
        set;
    }

    public Form Form
    {
        get;
       set;
   }

    public MovableControls MovableControls
    {
       get;
       set;
   }

}


private static FormResizeExtensionState state = new FormResizeExtensionState();

public static void EnableResizeSupport(this Form form)
{
    string key = form.Handle.ToString();
    if (!state.ContainsKey(key)) {
        state.Add(
            form.Handle.ToString(),
            new FormResizeExtensionStateEntry 
            {
                Form = form,
                MovableControls = new MovableControls(),
               PreviousSize = form.Size
            }
        );
        form.ResizeBegin += new EventHandler(form_ResizeBegin);
        form.SizeChanged += new EventHandler(form_SizeChanged);
        form.Resize += new EventHandler(form_Resize);
    }
}

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Jonas Butt
Software Developer Avanade
Netherlands Netherlands

I am Jonas Butt, a software developer living in the The Netherlands. Currently I'm busy obtaining my bachelors degree in Business Administration / Computer Science at the Hogeschool van Amsterdam.
www.international.hva.nl

I'm currently working on my bachelors thesis at Avanade, a international technology consulting firm in the form of a joint venture between Microsoft and Accenture. My thesis research is about domain specific languages (DSL) for vertical domains and in a broader context it is about model driven development, software factories and code generation.
www.avanade.com/nl

In the past year I have also worked as a full time Java web developer at Deloitte Consulting in the service line Technology Integration. The project I worked on involved developing a critical business application using mainly open source technologies such as Spring, Hibernate, Acegi, Eclipse etc.
www.deloitte.nl

Please feel to contact me for questions, feedback or just for fun. I also have a few CodePlex projects! www.codeplex.com/UserAccount/UserProfile.aspx?UserName=JonasButt


Comments and Discussions

 
GeneralHm ... Pinmemberjvdb25086-Feb-09 10:12 
Rant.... PinmemberJohn_Wesley5-Feb-09 14:41 
GeneralLet's see if I'm understanding this right Pinmembersupercat95-Feb-09 13:54 
GeneralRe: Let's see if I'm understanding this right PinmemberPIEBALDconsult5-Feb-09 16:21 
GeneralRe: Let's see if I'm understanding this right PinmemberMoim Hossain5-Feb-09 22:12 
GeneralRe: Let's see if I'm understanding this right PinmemberPIEBALDconsult6-Feb-09 5:00 
GeneralRe: Let's see if I'm understanding this right Pinmembersupercat96-Feb-09 7:39 
GeneralRe: Let's see if I'm understanding this right PinmemberPIEBALDconsult6-Feb-09 8:14 
GeneralRe: Let's see if I'm understanding this right Pinmembersupercat96-Feb-09 10:20 
GeneralRe: Let's see if I'm understanding this right PinmemberPIEBALDconsult6-Feb-09 10:43 
GeneralUmmm... PinmemberPIEBALDconsult5-Feb-09 10:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411023.1 | Last Updated 5 Feb 2009
Article Copyright 2009 by Jonas Butt
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid