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

Creating an Extensible User Interface with .NET, Part 2

, 15 Dec 2002 CDDL
Rate this:
Please Sign up or sign in to vote.
Accessing application level user interface elements from plug-in components.

Introduction

In Part 1, I described an architecture that allows you to create user interface plug-ins that can be loaded at runtime. In this document, I will describe methods for accessing application level user interface elements from the plug-ins. Please see Creating an Extensible User Interface with .NET, Part 1 before reading this article.

Overview

Some user interface elements naturally belong to the shell and should not reside in the plug-in. By convention, a status bar should run the entire width of the application window. Putting a status bar inside a plug-in would look odd and out of place. The main menu is another UI element that belongs to the shell application, but should be accessible from a plug-in. The following two examples show methods you can use to provide access to these two user interface elements.

Adding a StatusBar to the shell.

In this first example we will add a status bar to the shell and give the plug-ins a reference to one of the status bar's panels. We also include a helper function for placing text into the status bar panel.

Base class changes.

First we will add the needed code to the plug-in base class located in PlugIn.cs.

  • Add a local variable to hold the StatusBarPanel.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// Local storage for StatusPanel
    /// <span class="code-SummaryComment"></summary>
    </span>
    private System.Windows.Forms.StatusBarPanel _StatusPanel;
  • Next add a property to set and get the StatusBarPanel.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// StatusPanel property
    /// <span class="code-SummaryComment"></summary>
    </span>
    public System.Windows.Forms.StatusBarPanel StatusPanel
    {
        get
        {
            return _StatusPanel;
        }
        set
        {
            _StatusPanel = value;
        }
    }
  • And finally add a helper function that a plug-in can call to set the text in the StatusBarPanel. Note that we check that the _StatsPanel has been set before assigning the text.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// Helper function to set StatusPanel text.
    /// <span class="code-SummaryComment"></summary>
    </span>
    /// <span class="code-SummaryComment"><param name="Text">String to display</param>
    </span>
    protected void ShowStatus(string Text)
    {
        if(_StatusPanel != null)
        {
            _StatusPanel.Text = Text;
        }
    }

Shell application changes

Now we can modify the shell application.

  • Add a StatusBar control to the form.
  • Add a StatusBarPanel to the StatusBar.
  • Within AddPlugIn set the plug-in’s StatusPanel property to the StatusBarPanel you just added.
/// <span class="code-SummaryComment"><summary>
</span>
/// Load and add a plug-in to the panel1 control
/// Also set the list box to navigate between plug-ins.
/// <span class="code-SummaryComment"></summary>
</span>
/// <span class="code-SummaryComment"><param name="Location">The name or path of the file 
</span>
///      that contains the manifest of the assembly.<span class="code-SummaryComment"></param>
</span>
/// <span class="code-SummaryComment"><param name="ControlName">The name of the type to locate.</param>
</span>
private void AddPlugIn(string Location, string ControlName)
{
    Assembly ControlLib;
    PlugIn NewPlugIn;
    // Load the assembly.
    ControlLib = Assembly.LoadFrom(Location);

    // Now create the plugin.
    NewPlugIn = (PlugIn)ControlLib.CreateInstance(ControlName);
    NewPlugIn.Location = new System.Drawing.Point(0, 0);
    NewPlugIn.Dock = DockStyle.Fill;
    NewPlugIn.Visible = false;
    // Add it to the panel, note that its Visible property is false.
    panel1.Controls.Add(NewPlugIn);
    // Set up the ClickHandler
    NewPlugIn.Clicked += new PlugInLib.ClickDelegate(PlugIn_Clicked);
    NewPlugIn.SetMenu += new PlugInLib.SetMenuDelegate(PlugIn_SetMenu);

    NewPlugIn.StatusPanel = statusBarPanel1; //<--- 

    // Add the plugin to the listBox, listBox will use ToString to
    // get the text to display.
    listBox1.Items.Add(NewPlugIn);

}

Plug-in modifications

  • Finally add code to your plug-in to show its status: Here we just display "Over the Label" when the mouse is hovering above label1.
    private void label1_MouseEnter(object sender, System.EventArgs e)
    {
        ShowStatus("Over the Label");
    }
    
    private void label1_MouseLeave(object sender, System.EventArgs e)
    {
        ShowStatus("");
    }

Adding menus to the shell

It is very desirable for each plug-in to provide its own set of menus. The menus should only appear when the plug-in is visible. Doing this is a little more complicated than the previous example. The overriding goal of this design is that the plug-in should not need to manage its own menus. When the plug-in is not visible, then its menus should be automatically removed from the shell's main menu.

In this example, the plug-in will define its menus and place a reference to them in the base class. The base class will then call a function in the shell to install or delete the menus. The base class will use its own visibility property to determine if the menus should be installed or deleted.

Base class changes.

Again we will start by modifying the plug-in base class.

  • Add a declaration for a SetMenuDelegate. A delegate declaration defines a reference type that can be used to encapsulate a method with a specific signature. Here it defines a function that will be supplied by the shell application to add and delete items to its main menu.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// A delegate type for installing and removing menus.
    /// <span class="code-SummaryComment"></summary>
    </span>
    public delegate void SetMenuDelegate(bool Install, 
              System.Windows.Forms.MenuItem[] MenuItems);
  • Declare an instance of the SetMenuDelegate delegate. This is where the shell application will place a reference to the function that will add and delete menu items.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// SetMenu
    /// <span class="code-SummaryComment"><summary>
    </span>
    public SetMenuDelegate SetMenu;
  • Since we are going to let the base class manage the menus, we need a place to store them.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// Local storage for MenuItems
    /// <span class="code-SummaryComment"><summary>
    </span>
    private System.Windows.Forms.MenuItem[] _MenuItems;
  • And we need a property to set them.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// MenuItems property
    /// <span class="code-SummaryComment"><summary>
    </span>
    public System.Windows.Forms.MenuItem[] MenuItems
    {
        get
        {
            return _MenuItems;
        }
        set
        {
            _MenuItems = value;
        }
    }
  • Override the OnVisibleChanged function.

    This is where all the magic happens. When the plug-in's visibility changes, the base class will call the shell and let it add or delete the menus. After we have called SetMenu, we call base.OnVisibleChanged(e) so that normal processing continues. If we didn't do this, the plug-in would never receive "VisibileChanged" events.

    /// <span class="code-SummaryComment"><summary>
    </span>
    /// Show/Hide our menus.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// <span class="code-SummaryComment"><param name="e">event data</param>
    </span>
    protected override void OnVisibleChanged(System.EventArgs e)
    {
        if((SetMenu != null) && (_MenuItems != null))
        {
            SetMenu(this.Visible, _MenuItems);
        }
        base.OnVisibleChanged(e);
    }

    If your shell application shows multiple plug-ins simultaneously, you could override OnEnter and OnLeave to show the menus only when the plug-in has the focus.

Shell application changes

Next we modify the shell application to handle calls through the SetMenuDelegate.

  • Add a MainMenu control to the form and add any menus required for the shell application.
  • Add a function to add and delete menus. This function must match the signature of the SetMenuDelegate in the plug-in base class.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// Adds or Deletes MenuItems to the applications main menu.
    /// <span class="code-SummaryComment"></summary>
    </span>
    /// <span class="code-SummaryComment"><param name="Install">True - Add the menus<br />
    </span>
    /// False - Delete the menus<span class="code-SummaryComment"></param>
    </span>
    /// <span class="code-SummaryComment"><param name="MenuItems">Array of MenuItems 
    </span>
    ///                                  to Add or Delete<span class="code-SummaryComment"></param>
    </span>
    private void PlugIn_SetMenu(bool Install, 
          System.Windows.Forms.MenuItem[] MenuItems)
    {
        if(Install == true)
        {
            this.mainMenu1.MenuItems.AddRange(MenuItems);
        }
        else
        {
            foreach(MenuItem m in MenuItems)
            {
                this.mainMenu1.MenuItems.Remove(m);
            }
        }
    }
  • In AddPlugIn, set the plug-in’s SetMenu property to the PlugIn_SetMenu function.
    /// <span class="code-SummaryComment"><summary>
    </span>
    /// Load and add a plug-in to the panel1 control
    /// Also set the list box to navigate between plug-ins.
    /// <span class="code-SummaryComment"></summary>
    </span>
    /// <span class="code-SummaryComment"><param name="Location">The name or path 
    </span>
    ///  of the file that contains the manifest of the assembly.<span class="code-SummaryComment"></param>
    </span>
    /// <span class="code-SummaryComment"><param name="ControlName">The name of the type to locate.</param>
    </span>
    private void AddPlugIn(string Location, string ControlName)
    {
        Assembly ControlLib;
        PlugIn NewPlugIn;
        // Load the assembly.
        ControlLib = Assembly.LoadFrom(Location);
    
        // Now create the plugin.
        NewPlugIn = (PlugIn)ControlLib.CreateInstance(ControlName);
        NewPlugIn.Location = new System.Drawing.Point(0, 0);
        NewPlugIn.Dock = DockStyle.Fill;
        NewPlugIn.Visible = false;
        // Add it to the panel, note that its Visible property is false.
        panel1.Controls.Add(NewPlugIn);
        // Set up the ClickHandler
        NewPlugIn.Clicked += new 
             PlugInLib.ClickDelegate(PlugIn_Clicked);
        
        NewPlugIn.SetMenu += new 
             PlugInLib.SetMenuDelegate(PlugIn_SetMenu); //<---
    
        NewPlugIn.StatusPanel = statusBarPanel1;
        // Add the plugin to the listBox, listBox will use ToString to
        // get the text to display.
        listBox1.Items.Add(NewPlugIn);
    }

Adding menus to the plug-in.

You cannot add a MainMenu to a UserControl. You will need to code your menus by hand. Due to all the overloaded MenuItem constructors, this is quite easy to do.

  • First declare your top-level menus.
    private System.Windows.Forms.MenuItem FileMenu;
    private System.Windows.Forms.MenuItem EditMenu;
  • In the constructor for the plug-in, you can now create your complete menus.

    Here we create a "File" menu with 2 sub-items, "New" and "Save" and an "Edit" menu with "Cut", "Copy" and "Paste". Then we bundle the top-level menus into an array and assign it to the MenuItems property.

    // Create a "File" menu
    FileMenu = new MenuItem("File",new MenuItem[]
            {
            new MenuItem("New",new EventHandler(menuNew_Click)),
            new MenuItem("Save",new EventHandler(menuSave_Click))
            });
    
    // Create an "Edit" menu
    EditMenu = new MenuItem("Edit",new MenuItem[]
            {
            new MenuItem("Cut",new EventHandler(menuEdit_Click)),
            new MenuItem("Copy",new EventHandler(menuEdit_Click)),
            new MenuItem("Paste",new EventHandler(menuEdit_Click))
            });
    
    // Set the MenuItems
    this.MenuItems = new System.Windows.Forms.MenuItem[]
            {
            FileMenu,
            EditMenu,
            };

    Now when this plug-in becomes visible, its menus will be added to the application's menus. The events generated when a menu selection is made will fire directly to event handlers in the plug-in.

Conclusion

With the addition of these two methods of communicating with the shell application, you should have a complete framework for creating an application with an extensible user interface.

Notes

In the ExtensibleUI2_demo.zip file, config.xml file is located in both the Release and Debug directories of the "Shell" application. These files contain the absolute pathname to the OurControls.dll including a drive letter. You will need to modify these paths for your local system.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)

Share

About the Author

Keith Jacquemin
Web Developer
United States United States
I am a senior software developer located in Phoenix, AZ. I have a wide range of experience using Microsoft development technologies and have clung to the bleeding edge of their developments tools since Windows 3.1. I am now lacerating myself on .Net.
 
I am currently available for any contract or permanent assignments.

Comments and Discussions

 
QuestionCan it be used to Web Application PinmemberMHSrinivasan3-Oct-08 0:59 
AnswerRe: Can it be used to Web Application PinmemberKeith Jacquemin3-Oct-08 3:45 
GeneralNice work PinmemberJoeIsTheMan30-Jan-07 12:37 
Generalgood stuff Pinmemberwilliam keeling16-Jan-04 18:46 
GeneralRe: good stuff PinmemberHusein29-Jan-04 13:32 
GeneralMerging Menus PineditorHeath Stewart29-May-03 21:47 

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 | Mobile
Web03 | 2.8.141022.2 | Last Updated 16 Dec 2002
Article Copyright 2002 by Keith Jacquemin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid