Geeks With Blogs
Willem's... {rue if I mellow}

MFC provides a rather neat way of updating the state of window elements in an MFC CWnd (or derived) object during application idle-time. Typically, this is done by setting the element ID and member function in the ON_UPDATE_COMMAND_UI macro, and then declaring the function in the class header file:


    afx_msg void OnUpdateMyCommand(CCmdUI* pCmdUI);

Then you implement the handler as follows:


    void CMyClass::OnUpdateMyCommand(CCmdUI* pCmdUI)
    {
      if(IsMyCommandAvailable())
        pCmdUI->Enable(TRUE);
    }

This provides a mechanism to update the state of the UI element by passing TRUE or FALSE to the Enable function. The effect is that a toolbar button and a menu item with the same element IDs can be simultaneously updated with one state management function. Similarly, command execution can be routed through a similar mechanism, so that clicking the menu or the corresponding tool button results in a single (and the same) function being executed.

In the standard .NET System.Windows.Forms world, this functionality is not available out the box. In essence, the menu and button elements are managed separately, and require separate handlers for execution (the Click event), and no 'OnUpdate..' type handlers are defined. For moderately complex  forms, separate handlers result in many handler methods which become difficult to manage, particularly as the handlers are added to the ended of the code module in a non-structured way.

This problem can be answered in a few ways. The links below are useful resources, and provide very flexible but ultimately over-complex solutions and implementation-patterns for many applications:

The following approach provides a mechanism (read: class) to provide something similar to the command and update handlers in MFC. The Command class implemented provides the code to link together a ToolStripMenuItem menu item for use on a MenuStrip object, with a ToolStripMenuItem popup menu item for use on a ContextMenuStrip, and with a ToolStripButton toolbar button on a ToolStrip object. Furthermore, instead of linking the UI elements together with macros and element IDs, this implementation relies on correctly named elements set at design-time, which are then reflected and resolved at runtime, and linked together for the lifetime of the Command class instantiated to manage these elements together.

A Command object is instantiated and added to the static list of commands to be managed:


    Command.Commands.Add(new Command(this, "CommandA"));

The this parameter is the command target, and is normally the Form object which contains the UI elements.

The Command class expects the "CommandA" implementation to be defined in the form code in the format On{Command}:


    private void OnCommandA(object sender, EventArgs e)
    {
      // Implementation of Command A.
      ...
    }

Similarly, the "CommandA" UI update implementation is defined in the form code in the format On{Command}UpdateUI:


    private void OnCommandAUpdateUI(object sender, EventArgs e)
    {
      Command command = (Command)sender;
      if(this.UseDefaultUpdateUIMethod)
      {
        command.DefaultUpdateUI(this.IsCommand_A_Available);
      }
      else
      {
        command.Button.Enabled = this.IsCommand_A_Available;
        command.MenuItem.Enabled = this.IsCommand_A_Available;
        command.PopupMenuItem.Enabled = this.IsCommand_A_Available;
      }
    }

Both the UseDefaultUpdateUIMethod and IsCommand_A_Available properties are provided by the Form object and provide the state of the form elements. Obviously, the appropriate state properties or methods have to be provided by the application programmer in a real application.

The Command.DefaultUpdateUI method provides the same functionality as that coded in the else section. The method is presented as above only to illustrate the various properties of the Command object.

In order to create and hookup delegate's for the button and menu Click events, as well as the Command UpdateEventHandler event, the Form class is reflected and the UI elements are found from their names:

  • the ToolStripButton button is found by searching for the name {Command}Button. For the above example the actual name would be "CommandAButton",

  • the ToolStripMenuItem menu item is found from {Command}MenuItem, or "CommandAMenuItem" in the example, and

  • the ToolStripMenuItem  popup menu item is {Command}PopupMenuItem, or "CommandAPopupMenuItem" in the example.

Command provides a method to find the UI button element:


    private ToolStripButton FindButton(string buttonName)
    {
      FieldInfo buttonInfo = Target.GetType().GetField(
        buttonName,
        BindingFlags.IgnoreCase 
        | BindingFlags.Instance
        | BindingFlags.NonPublic
        | BindingFlags.Public);
      if (buttonInfo != null)
      {
        return buttonInfo.GetValue(Target) as ToolStripButton;
      }
      return null;
    }

where Target is the Form object used to construct the Command object. Similarly, the menu items can also be found.

Once the UI element objects are found, their event handler delegates can be set (or removed) by creating new delegate instances from the required method:


    private void CreateEventHandler(
      object target,
      object component,
      string eventName,
      string handlerName,
      bool add)
    {
      MethodInfo methodInfo = target.GetType().GetMethod(
        handlerName,
        BindingFlags.IgnoreCase
        | BindingFlags.Instance
        | BindingFlags.NonPublic
        | BindingFlags.Public);
      if (methodInfo != null)
      {
        EventInfo eventInfo = component.GetType().GetEvent(
          eventName,
          BindingFlags.IgnoreCase
          | BindingFlags.Instance
          | BindingFlags.Public);
        Delegate eventDelegate = Delegate.CreateDelegate(
          typeof(EventHandler), target, methodInfo);
        if (add)
        {
          eventInfo.AddEventHandler(component, eventDelegate);
        }
        else
        {
          eventInfo.RemoveEventHandler(component, eventDelegate);
        }
      }
    }

Note that the methods to be used as handlers are required to have the same signature as that required by the EventHandler:


    void MyEventHandler(object sender, EventArgs e) { ... }

Note also that the searches for variable fields, methods and events are conducted ignoring the case of the names, such that 'myEventHandler' and 'MyEventHandler' will both refer to the same construct.

Command provides a UI update method which will call the UI update handler if defined by the Form as per the requirement above:


    public event EventHandler UpdateEventHandler;
    public void Update()
    {
      if (UpdateEventHandler != null)
      {
        UpdateEventHandler(this, EventArgs.Empty);
      }
    }

Finally, all the Command's are updated as follows:


    public static void UpdateCommands()
    {
      foreach (Command command in Commands)
      {
        command.Update();
      }
    }

So,  you ask, how do I get this working in my application?

  1. First, provide a reference to the Command class by adding the C# file to your Windows.Forms application project, or reference it as a class library.

  2. Add the required UI elements to the form and name them appropriately.

  3. Add the required implementation handler and UI update handler to the code and name them appropriately.

  4. In the Form constructor after the InitializeComponent method add the following line for each Command (in the example the command is named "CommandA":

    
        Command.Commands.Add(new Command(this, "CommandA"));
  5. Add an update method to the form which is called by the Application when it is idle. This method then updates all the Command object in use:

    
        public void IdleUpdate()
        {
          Command.UpdateCommands();
        }
  6. Replace the Program class in Program.cs (.NET 2.0+), as follows:

      
      static class Program
      {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        //static void Main()
        //{
        //    Application.EnableVisualStyles();
        //    Application.SetCompatibleTextRenderingDefault(false);
        //    Application.Run(new Form1());
        //}
        static void Main()
        {
          Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
          Application.EnableVisualStyles();
          Application.SetCompatibleTextRenderingDefault(false);
          form1 = new Form1();
          Application.Idle += OnApplicationIdle;
          Application.Run(form1);
        }
        static Form1 form1;
        static EventHandler OnApplicationIdle = new EventHandler(Application_Idle);
        static void Application_Idle(object sender, EventArgs e)
        {
          form1.IdleUpdate();
        }
        static void Application_ApplicationExit(object sender, EventArgs e)
        {
          Application.Idle -= OnApplicationIdle;
        }
      }

The C# source-code for the Command class and a sample forms application is provided in a Visual Studio 2005 .NET 2.0 solution and can be downloaded from the link below:

Posted on Tuesday, June 5, 2007 12:53 PM .NET Adventures | Back to top


Comments on this post: Command UI Updating Windows Forms in C#

# re: Command UI Updating Windows Forms in C#
Requesting Gravatar...
I've been working with MFC for ages. so I'm really missing this pattern in WIndows Forms.
But wouldn't it make sense to make it more general that this pattern can be used for buttons, labels, textboxes and other controls too?
Left by Markus Vogel on Oct 19, 2010 3:25 AM

# re: Command UI Updating Windows Forms in C#
Requesting Gravatar...
Markus, this update pattern is now fully implemented in WPF/Silverlight - suggest you look at those development environments for future work.
Left by Willem on Oct 19, 2010 4:17 AM

# re: Command UI Updating Windows Forms in C#
Requesting Gravatar...
as "old" mfc user what do you say about C# and WPF4. Would you recommand it to be used for an custom graphic editor or would you still use C++ / MFC V10 (plus CLR) for applications like powerfull graphic editors with graphic and rich object list and rich ui?
Left by Markus Vogel on Oct 19, 2010 8:50 AM

Your comment:
 (will show your gravatar)
 


Copyright © Willem Fourie | Powered by: GeeksWithBlogs.net | Join free