Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Part II: Web & Window Form Unification: Synchronous And Asynchronous Event Handling For Controls Created At Runtime

0.00/5 (No votes)
13 Jul 2002 2  
This is the second part of a multipart article on unifying web and window form application development. This part demonstrates synchronous and asynchronous event handling of GUI control events, abstracting the implementation of web/window forms to be technology independant.

Introduction

This is the second part of a multipart article on unifying web and Windows form application development. In the first part, I demonstrated a mechanism for unifying Web Form and Windows Form controls. In Part II, I will demonstrate an interface for events that abstracts web and window form event differences and synchronous/asynchronous event consumers.

A programmer typically implements GUI control event handlers as methods in the web or window form class that contains the control, or possibly as a method in a class derived from a GUI control. A similar mechanism can be used even for runtime created controls. However, this requires that the form or page be created at the time of implementation so that the appropriate event handlers can be implemented as methods to the form/page.

In my dynamic GUI generation tool, the form/page is dynamically instantiated (or an equivalent generic template). This requires an interface for associating the event handlers with the GUI control event, because the event handlers cannot be associated with a dynamically created form/page.

For example, a Window Form is created dynamically with:

DynWindowForm mainForm=new DynWindowForm();
mainForm.Init(new System.Windows.Forms.Form());

Granted, the Web Forms version is not quite as dynamic, as it utilizes an existing label on the main form to create child controls, but the concept is the same:

DynWebForm mainForm=new DynWebForm();
mainForm.Init(Label1);

To support event in a dynamic form/page framework, events consumers are declared as static methods and are registered into an event collection with a key/value pair where the key is a unique textual name and the value is the System.EventHandler delegate. A control is associated with a particular event by name, therefore allowing control-event associations to be handled in the resource script.

Benefits

This has the benefit of enforcing a design technique that I feel is better than the classical method of handling the GUI control event in the parent form or the sub-classed control. For example, I've seen many cases where complex functionality was included in the event handler (pseudo code follows:)

public class Abutton : Button
{
?
void OnClick(object sender, EventArgs e)
{
    /*
    ** Now we do something important
    */
}
}

In many large applications, I have found programmers performing a copy/paste for event handlers that perform similar, if not the same, functionality. This is redundant and makes it harder to modify and maintain the application. Instead, I prefer that all event handlers be separated out from the strict GUI implementation. This also creates a clean separation between the presentation and the "business logic", or functionality, of the application. For example, I think it is better design to implement the above example as:

public class Abutton : Button
{
...
    void OnClick(object sender, EventArgs e)
    {
        BusinessLogic.DoSomethingImportant();
    }
}

public class BusinessLogic
{
    static void DoSomethingImportant(void)
    {
        ...
    }
}

This might seem like overkill for simple events, especially those that end up merely changing the state of some other GUI element, and I agree. However, I find that it is a good idea to do things consistently as to create a good habit.

If implemented correctly, the event handler can be invoked in other ways as well, besides by GUI control events. This improves the modularity of the code. Also, if the framework (as I will illustrate mine does) supports asynchronous events (as does C#), the programmer can choose whether the event is handled in the main application thread or as a worker thread.

A side effect to this style of event management is that event consumers are usually declared as static methods. This is actually preferable, especially in Web Forms applications, which are stateless, and an object instance is not preserved between page loads.

The Event Collection Class

The event collection class is a thin wrapper around the SortedList class:

using System;
using System.Collections;

namespace EventManager
{
    public class EventCollection
    {
        public static void Add(string name, EventHandler e)
        {
            eventList[name]=e;
        }

        public static EventHandler Get(string name)
        {
            if (eventList.Contains(name))
            {
                return (System.EventHandler)eventList[name];
            }
            else
            {
                return null;
            }
        }

        protected static SortedList eventList=new SortedList();
    }
}

The SortedList class would be sufficient by itself, but I prefer to create a wrapper around generic containers so that I can add additional functionality later. Also, I could have derived the EventManager class from SortedList. I do not usually like doing this because I want to restrict the application?s access to the container. This way, the exception handling is reduced if the wrapper class is more involved in the management of the data.

Invoking The Event

The event invocation is handled by the base class of all the dynamic controls, DynControl:

protected virtual IAsyncResult InvokeEvent(string name)
{
    string eventName=(string)ctrlEvents[name];
    if (eventName[0]=='*')
    {
        // asynchronous

        eventName=eventName.Remove(0, 1);
        System.EventHandler eh=
                 (System.EventHandler)EventManager.EventCollection.Get(eventName);
        IAsyncResult result=eh.BeginInvoke(this, null, null, null);
        return result;
        // result.AsyncWaitHandle.WaitOne();

    }
    else
    {
        // synchronous

        System.EventHandler eh=(System.EventHandler)EventManager.
                  EventCollection.Get(eventName);
        eh(this, null);
        return null;
    }
}

The InvokeEvent method determines whether the event should be invoked synchronously or asynchronously (as discussed below). What is elegant about this is that the same method is used regardless of whether the event is triggered by a Web Form or a Windows Form. The implementation abstracts the specifics of both technologies so that the application doesn?t have to concern itself with this issue. (Of course, there are complexities to this that we will get into in later parts).

Tying Events In To The Control Creation Script

In Part I, I introduced an approach that creates GUI?s essentially from a "resource script" (albeit hard-coded for now, but that will change in Part IV, when database interfaces is introduced). As part of the form/page initialization, it is possible to associate event consumers with the desired GUI control at the time that the page/form is loaded. For example, for each dynamic control instantiated, an event could be created as follows (more pseudo code):

GetControl("btnClearForm").OnClick+=EventHandler(BusinessLogic.ClearTheForm);

(any required downcasting is left out of this pseudo code)

This implementation tightly couples the consumer with the control, and we violate the design rule I stated above, which is to attempt to disassociate the business logic of the application from the GUI presentation. Instead, the EventManager class is used, as described above. Using the EventManager mechanism, event consumers can be referenced by name, not by function. This decouples the event generator from the consumer. Event consumers are registered using:

EventManager.EventCollection.Add("ClearForm", 
                                      new System.EventHandler(AppEvents.ClearForm));

And the event consumer is coupled to the control itself as part of the ControlInfo that is passed to the dynamic control during creation. For example, the ControlInfo structure is initialized as follows:

ctrlInfo[2]=new ControlInfo(
    "imageButton",
    new Point(100, 30),
    new Size(20, 20),
    "btnClearForm",
    "c:\\projects.net\\AutoFormPrototype\\csform\\icons\\clearForm.ico",
    "",
    "",
    "onClick=ClearForm");

The above code associates the "onClick" event with a synchronous event consumer, AppEvents.ClearForm. An asynchronous event consumer is specified by placing an asterisk in front of the consumer name:

... "onClick=*ClearForm" ...

Synchronous And Asynchronous Events

This feature comes with a price: each of our dynamic controls must implement an internal event handler. Using the button control as an example:

Web Form:

public class DynWebButton : DynWebControl
{
    public override void Create(ControlInfo ci)
    {
        System.Web.UI.WebControls.Button ctrl=
                  new System.Web.UI.WebControls.Button();
        base.ctrl=ctrl;
        ctrl.Text=ci.text;
        ParseEvents(ci);
        if (ctrlEvents.Contains("onClick"))
        {
            ctrl.Click+=new EventHandler(OnClick);
        }
        base.Create(ci);
    }

    private void OnClick(object sender, EventArgs e)
    {
        InvokeEvent("onClick");
    }
}

Windows Forms:

public class DynWindowControl : DynControl
{
    public override System.Windows.Forms.Control GetWindowCtrl() {return ctrl;}
    public override System.Web.UI.Control GetWebCtrl() {return null;}

    public virtual void Create(ControlInfo ci)
    {
        ctrl.Location=ci.pos;
        ctrl.Size=ci.size;
        ctrl.Text=ci.text;
        ctrl.Name=ci.name;
        ParseEvents(ci);
        if (ctrlEvents.Contains("onClick"))
        {
            ctrl.Click+=new EventHandler(OnClick);
        }
    }

    private void OnClick(object sender, EventArgs e)
    {
        InvokeEvent("onClick");
    }
}

Web Forms Events vs. Window Forms Events

In Windows Forms, the Click event is a member of the base class, System.Windows.Forms.Control for all controls. Furthermore it takes the same parameters regardless of the control. This allows us to implement the hander in the DynWindowControl base class. Unfortunately, this is not true for web controls, the base class for web controls has no Click event, and furthermore, different controls produce different parameters. This means that the event handlers must be coded specifically for each web control. While annoying, once the work is done the programmer performing the application coding can ignore the complexities of web events. For example, the ImageButton web control requires its own unique event consumer:

if (ctrlEvents.Contains("onClick"))
{
    ctrl.Click+=new System.Web.UI.ImageClickEventHandler(OnClick);
}

private void OnClick(object sender, System.Web.UI.ImageClickEventArgs e)
{
    InvokeEvent("onClick");
}

Note however the loss of certain information, for example, X/Y coordinate information. This issue will be addressed in the Part III: Interfacing To And Abstracting Control Data.

Conclusion

This article builds on the framework described in Part I, which demonstrated unifying Web Form and Window Form control instantiation and rendering. Part II demonstrates how to abstract Web Form and Windows Form events so that an application can be written without concern to the underlying technology. The issues of accessing control properties has not been addressed in this article (therefore, no pretty pictures at the beginning of the article either!). Part III will discuss this issue and introduce the concept of a DataContainer for managing the interface between the business logic event handlers and the presentation system.

The code distributed with this article demonstrates that the event handlers actually work. A message is displayed in the Windows Forms application, and the programmer can set a breakpoint in the Web Forms application to verify that clicking on the pencil image button actually invokes the event.

The Zip File

The zip file contains the Windows Forms demo and DynForm library, which are installed starting in the projects.net directory. The Web Forms project, AspNetForm, unzips into the aspNetForm directory. I?d unzip everything into a temp directory, preserving path, and then copy the code into the appropriate directories of your choice.

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