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

Creating an Extensible User Interface with .NET, Part 2

0.00/5 (No votes)
15 Dec 2002 2  
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.
    /// <summary>
    
    /// Local storage for StatusPanel
    
    /// </summary>
    
    private System.Windows.Forms.StatusBarPanel _StatusPanel;
  • Next add a property to set and get the StatusBarPanel.
    /// <summary>
    
    /// StatusPanel property
    
    /// </summary>
    
    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.
    /// <summary>
    
    /// Helper function to set StatusPanel text.
    
    /// </summary>
    
    /// <param name="Text">String to display</param>
    
    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.
/// <summary>

/// Load and add a plug-in to the panel1 control

/// Also set the list box to navigate between plug-ins.

/// </summary>

/// <param name="Location">The name or path of the file 

///      that contains the manifest of the assembly.</param>

/// <param name="ControlName">The name of the type to locate.</param>

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.
    /// <summary>
    
    /// A delegate type for installing and removing menus.
    
    /// </summary>
    
    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.
    /// <summary>
    
    /// SetMenu
    
    /// <summary>
    
    public SetMenuDelegate SetMenu;
  • Since we are going to let the base class manage the menus, we need a place to store them.
    /// <summary>
    
    /// Local storage for MenuItems
    
    /// <summary>
    
    private System.Windows.Forms.MenuItem[] _MenuItems;
  • And we need a property to set them.
    /// <summary>
    
    /// MenuItems property
    
    /// <summary>
    
    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.

    /// <summary>
    
    /// Show/Hide our menus.
    
    /// <summary>
    
    /// <param name="e">event data</param>
    
    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.
    /// <summary>
    
    /// Adds or Deletes MenuItems to the applications main menu.
    
    /// </summary>
    
    /// <param name="Install">True - Add the menus<br />
    
    /// False - Delete the menus</param>
    
    /// <param name="MenuItems">Array of MenuItems 
    
    ///                                  to Add or Delete</param>
    
    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.
    /// <summary>
    
    /// Load and add a plug-in to the panel1 control
    
    /// Also set the list box to navigate between plug-ins.
    
    /// </summary>
    
    /// <param name="Location">The name or path 
    
    ///  of the file that contains the manifest of the assembly.</param>
    
    /// <param name="ControlName">The name of the type to locate.</param>
    
    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 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