Click here to Skip to main content
15,887,135 members
Articles / Desktop Programming / Windows Forms

Evolving Windows Forms Event Handling With Loosely Coupled Methods

Rate me:
Please Sign up or sign in to vote.
4.65/5 (10 votes)
15 Jun 2011CPOL3 min read 30.9K   502   17   17
Reduce the exposure of OwnerObject members or resources by interfacing methods to child or sibling objects.

Introduction

I will present an alternative to standard event handling, subscription, and unsubscription by exposing methods through an interface. I will also present a GenericEventArgs class to speed up event argument coding.

The question, "How do I get FormB controls to affect FormA", has been asked several times lately. I began to consider a scenario where an OwnerForm would open a ChildForm; certain events in the ChildForm might need to alter data on the OwnerForm. Some solutions to this scenario suggest sending an OwnerForm reference to ChildForm. While this will work, I see this as brittle. If there is any change in the OwnerForm, no doubt you will have to at least open the code of the ChildForm to make sure nothing is broken.

By interfacing our events, we expose only references to Actions. This can be thought of as a default action should a certain event occur. Each implementation for an OwnerObject would tend to be unique, but the system will be recognizable and the added flexibility will be beneficial. Loosely coupled events will also make it easier to avoid what I call functionCreep(this).

  • functionCreep(this) - executing functions or operating on resources intimate to the OwnerObject
  • functionCreep(this) invites exposure of the OwnerObject's resources

Background

A general knowledge of event delegates and custom EventArgs is helpful.

Using the Code

The source download contains all the necessary components to compile and execute the project, including the main form and the settings form. Two classes - specifically, IEventPublisher and GenericEventArgs - are important for this process to be useful and Factory-like.

Download the source and open in Visual Studio, compile, and run.

Form1Startup.PNG

Clicking Open Settings will open the 'Settings Form'.

SettingsWindow.PNG

Enter the word 'Presto' in the textbox as shown, then click the Set Title button. The OwnerForm title changes.

Next, select a person from the combobox. When a person is selected, the OwnerForm PropertyGrid will display the person's properties.

While these are very simple examples, the project demonstrates the ability to expose methods to the ChildForm without exposing any controls or members other than what the IEventPublisher interface allows. In order to accomplish this, we must modify the ChildForm constructor to accept the IEventPublisher and do something with it:

Here is the constructor for the FormSettings window:

C#
// fields
Action<object, EventArgs> changeTitleDelegate;
Action<object, EventArgs> displayPropsDelegate;

public FormSettings ( IEventPublisher EventOwner )
{
    //  couple to events
    changeTitleDelegate = EventOwner.ChangeFormTitle;
    displayPropsDelegate = EventOwner.DisplayProperties;

    InitializeComponent ( );
    
    //  fills uxPersonSelector (ComboBox) with Person records
    fillPersonSelector ( );

    //  subscribe
    uxSetTitle.Click += new EventHandler ( uxSetTitle_Click );
    uxCloseSettings.Click += delegate { this.Close ( ); };
    uxPersonSelector.SelectedValueChanged += 
        new EventHandler ( uxPersonSelector_SelectedValueChanged );
}

Other than assigning values to changeTitleDelegate and displayPropsDelegate, this looks like a very standard constructor. IEventPublisher is the interface that exposes the two Action getters.

IEventPublisher:

C#
public interface IEventPublisher
{
    Action<object, EventArgs> ChangeFormTitle { get; }
    Action<object, EventArgs> DisplayProperties { get; }
}

The two functions uxSetTitle_Click and uxPersonSelector_SelectedValueChanged allow flexibility as to when, or if, the OwnerForm executes a 'default' method.

Here is the constructor for the FormSettings window:

C#
void uxSetTitle_Click ( object sender, EventArgs e )
{
    //  pre-process if necessary

    //  invoke delegate
    this.changeTitleDelegate ( this, 
         new GenericEventArgs<string> ( uxFormTitle.Text ) );
}

void uxPersonSelector_SelectedValueChanged ( object sender, EventArgs e )
{
    //  invoke delegate
    this.displayPropsDelegate ( this,
        new GenericEventArgs<Person> ( (Person)uxPersonSelector.SelectedValue ) );
}

Note: The Person class is included in the project source.

Looking at our OwnerForm; in the uxOpenSettings_Click2 function, we instantiate and show the SettingsForm as a dialog. When the SettingsForm is instantiated, ChangeFormTitle and DisplayProperties are assigned. When SettingsForm's events fire, the functions in the OwnerForm are executed.

IEventPublisher:

C#
//  constructor
public Form1 ( )
{
    InitializeComponent ( );
    
    uxOpenSettings.Click += new EventHandler ( this.uxOpenSettings_Click2 );
}

//  functions
private void uxOpenSettings_Click2 ( object sender, EventArgs e )
{
    using (FormSettings settings = new FormSettings ( this ))
    {
        //  show settings in Dialog mode
        settings.ShowDialog ( );
    }
}

void ChangeFormTitle ( object sender, EventArgs e )
{
    //  note the use of GenericEventArgs<T>
    var args = (GenericEventArgs<string>)e;

    //  change Form1 title
    Text = args.Value;
}

void DisplayProperties ( object sender, EventArgs e )
{
    //  note the use of GenericEventArgs<T>
    var args = (GenericEventArgs<Person>)e;

    uxPersonProperties.SelectedObject = args.Value;
}

#region IEventPublisher Members

Action<object, EventArgs> IEventPublisher.ChangeFormTitle
{
    get { return ChangeFormTitle; }
}

Action<object, EventArgs> IEventPublisher.DisplayProperties
{
    get { return DisplayProperties; }
}

#endregion

The GenericEventArgs class is included in the project source. It is a very simple form of EventArgs subclass that passes a strongly typed argument. Its use and implementation should be self-explanatory.

Points of Interest

  • No owner resources available to child window.
  • No owner controls exposed to child window.
  • No static methods to call. This tends to force the class design to consider static variables.
  • No worry about subscribing or unsubscribing to events external to the forms themselves.
  • The GenericEventArgs T object is a bonus.

This was an experiment to find a logical way to allow execution of methods across classes without exposing the OwnerObject any more than necessary. I don't like the idea of passing control references around to child or sibling objects. This easily opens the door to memory leaks or open event subscriptions. I believe my approach to be clever and thought provoking. This was very much a learning experience and I welcome all thoughts, comments, and concerns.

History

  • [6/12/2011] Initial publishing, spelling and grammatical edits.
  • [6/14/2011] Re-submitted the [source] zip file. Thank you for letting me know there was a problem.
  • [6/15/2011] Submitted new [source] ... again. Edited comments in FormSettings.Designer.cs Dispose( ) method.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Business Analyst
United States United States
Born and raised in Texas. I have been involved in programming since the early days of the Apple II - AppleSoft Basic and assembly were the two languages back then. Since then, I have watched technology evolve; I have watched the languages evolve. Over the last 5 years, I have been programming in .Net, specifically C#.

I am currently employed as a software engineer in San Diego, CA.

Comments and Discussions

 
QuestionSecond Part - final status is undetermined Pin
IAbstract2-Jul-11 5:49
IAbstract2-Jul-11 5:49 
AnswerRe: Second Part - final status is undetermined Pin
Pete O'Hanlon14-Jul-11 23:48
mvePete O'Hanlon14-Jul-11 23:48 
Generalfascinating, thanks ! Pin
BillWoodruff16-Jun-11 7:58
professionalBillWoodruff16-Jun-11 7:58 
GeneralRe: fascinating, thanks ! Pin
IAbstract16-Jun-11 12:53
IAbstract16-Jun-11 12:53 
GeneralVery interesting. Pin
Pete O'Hanlon15-Jun-11 4:32
mvePete O'Hanlon15-Jun-11 4:32 
GeneralRe: Very interesting. Pin
IAbstract15-Jun-11 7:14
IAbstract15-Jun-11 7:14 
GeneralEventPool [was Re: Very interesting.] Pin
IAbstract16-Jun-11 12:52
IAbstract16-Jun-11 12:52 
GeneralRe: EventPool [was Re: Very interesting.] Pin
Pete O'Hanlon18-Jun-11 9:42
mvePete O'Hanlon18-Jun-11 9:42 
GeneralMy vote of 4 Pin
Brian Pendleton14-Jun-11 12:22
Brian Pendleton14-Jun-11 12:22 
GeneralRe: My vote of 4 Pin
IAbstract14-Jun-11 12:36
IAbstract14-Jun-11 12:36 
GeneralRe: My vote of 4 Pin
Brian Pendleton14-Jun-11 12:48
Brian Pendleton14-Jun-11 12:48 
GeneralRe: My vote of 4 Pin
IAbstract14-Jun-11 12:47
IAbstract14-Jun-11 12:47 
GeneralRe: My vote of 4 Pin
Brian Pendleton14-Jun-11 12:55
Brian Pendleton14-Jun-11 12:55 
GeneralRe: My vote of 4 Pin
IAbstract15-Jun-11 2:34
IAbstract15-Jun-11 2:34 
GeneralThe 'using' statement Pin
IAbstract15-Jun-11 2:37
IAbstract15-Jun-11 2:37 
Question[My vote of 2] Event Publication and Subscription Pin
Sakshi Smriti13-Jun-11 17:58
Sakshi Smriti13-Jun-11 17:58 
AnswerRe: [My vote of 2] Event Publication and Subscription Pin
IAbstract14-Jun-11 4:22
IAbstract14-Jun-11 4:22 
The example I provide is a cautiously simplified relationship. I provide an alternative that prevents forms (specifically the 'code-behind') from becoming entangled and spaghetti-like. It is certainly your prerogative to publish and raise events how you wish; no argument there Big Grin | :-D . By the way, this article is now planned to be the first part in a series on evolving our event handling (next part should be published in a few weeks.)

Remember, each time you subscribe to an event, you should unsubscribe. The method I propose minimizes event subscriptions. In my opinion, events should only be raised when it is the most practical solution as there is some added overhead in raising an event versus calling the next method in a chain (my solution will allow chaining Actions).

In addition, when FormA depends on FormB (through events subscriptions, etc.), you have a brittle situation. A change in FormB involving a published event, or modified control behavior, will require following an event chain to ensure nothing is broken. In a very large project, following those event chains can become difficult.

Again, if your project is only a couple of forms with only a few published events between them, this alternative may not be for you; although, I would still suggest that exposing Actions via an interface is a safe practice. If you have a project with massive forms, then this solution might be a good fit.

Thank you for your comments!
David

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.