Creating an Extensible User Interface with .N...

来源:百度文库 编辑:神马文学网 时间:2024/04/28 02:19:50

3,672,687 members and growing!   9,452 now online.txjchen |My Settings |My Bookmarks |My Articles |Sign out
HomeMFC/C++C#ASP.NET.NETVB.NETAll Topics  Help!ArticlesMessage BoardsStoreFrontLounge
All Topics,C#,.NET >>Design and Architecture >>GUI
Creating an Extensible User Interface with .NET, Part 1
ByKeith Jacquemin.
An architecture for extending the User Interface of a program via plug-in components.
C#
Windows, .NET (.NET 1.0)
Win32, VS (VS.NET2002)
Dev
Posted: 18 Nov 2002
Updated: 16 Dec 2002
Views: 104,069
Announcements
$10,000 worth of prizes to be wonMonthly Competition

Search   Articles Authors   Advanced Search
Sitemap |Add to IE Search
PrintBroken Article?BookmarkDiscussSend to a friend
37 votes for this article.
Popularity: 6.96. Rating: 4.44 out of 5.
You are signed up for one or morenewsletters but unfortunately we are unable to send you emails. Please clickhere to have an email sent that will allow us to confirm your email address.
Download demo project - 104 Kb
Introduction
Many times it can be desirable to extend or enhance the User Interface (UI) of a program after it has been deployed. Usually this means redeploying the entire application. This document describes a “plug in” architecture that allows for the UI to be extended at any time. An example of a program with an extensible UI is the Microsoft Management Console (MMC) and its associated snap-ins.
Overview
There is one main requirement that your program must meet before this architecture can be considered.
There should be NO interaction between UI plug-ins. This does not mean they cannot share a common data structure or business objects, but each UI plug-in should not try to make direct calls to other plug-ins.
In this architecture all UI elements are contained in a set of plug-ins based upon the System.Windows.Forms.UserControl class. All plug-ins are described in a configuration file, and loaded at runtime. Extending the UI is accomplished by creating a new plug-in and adding the appropriate entries to the config file.
Some of the advantages of this architecture are:
Independent development of different UI elements. For example, if you are developing a Personal Information Manager (PIM), one person could be working on the "Appointments/Calender" UI, while another works on the "Contacts" UI. Application control. You could restrict the functionality of the application based upon the user‘s name or the user‘s roll or purchased options. You can add new UI elements at any time. In the PIM example above, you could add a "Diary" UI after the application has been distributed.
The architecture consists of 3 parts:
A “shell” application that handles the loading and navigation between the plug-ins. A base class that supplies all communications between the “shell” and the plug-ins. The individual UI plug-ins themselves.
The shell
At startup the shell application reads a configuration file to get the names and locations for each UI plug-in. It then uses reflection to load each plug-in. In the screen shots below, the Shell application contains a ListBox used to navigate between plug-ins, and a Panel in which the plug-ins are loaded.
Here is an example of a “shell” with 2 plug-ins loaded. The ListBox on the left is used to select between each plug-in, while the panel on the right will display the plug-in when it is made visible.

Clicking on "PlugIn1" made the plug-in visible.

And then clicking on "PlugIn Number 2" makes it visible in the panel.

The Tabbed Shell application shows another way of navigation:
Here is the Tabbed Shell after it has been loaded.

And here it is after "PlugIn Number 2" has been selected.

How the Shell finds the plug-ins.
The plug-ins to be loaded at runtime are listed in an XML file named config.xml.

In the form load event, the config.xml file is loaded into a DataSet via ReadXml. Then it iterates each DataRow calling AddPlugin with the “location” and “name” of the plug-in.
private void Form1_Load(object sender, System.EventArgs e) { DataSet ds = new DataSet(); ds.ReadXml("Config.xml"); foreach(DataRow dr in ds.Tables["Plug-In"].Rows) { AddPlugIn(dr["Location"].ToString(), dr["Name"].ToString()); } } Two examples of the AddPlugIn code.
The AddPlugIn loads the assembly containing the plug-in and creates an instance of it. It also adds the plug-in to the ListBox. When a new item in the list box is selected, we need to hide the current plug-in, and show the new selection.
Collapse
// Load and add a plug-in to the panel1 control // Also set the list box to navigate between plugins. 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.ClickHandler(Control_Clicked); // Add the plugin to the listBox, listBox will use ToString to // get the text to display. listBox1.Items.Add(NewPlugIn); } private PlugIn CurrentPlugIn; // When a new item in the listBox is selected, // hide the current plugin and show the new. private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) { if(CurrentPlugIn!=null) { CurrentPlugIn.Visible = false; } CurrentPlugIn = (PlugIn)listBox1.SelectedItem; CurrentPlugIn.Visible = true; }
The AddPlugIn for the Tabbed Shell application is only slightly different. The Tabbed Shell application needs no navigation code because it is handled by the TabControl.
Collapse
// Load and add a plug-in to the TabControl1 control 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 = true; // Create a new TabPage. TabPage newPage = new TabPage(); // Set the text on the tabPage with the PlugIn Caption. newPage.Text = NewPlugIn.Caption; // Add the PlugIn to the TabPage. newPage.Controls.Add(NewPlugIn); // Add the page to the tabControl. tabControl1.TabPages.Add(newPage); // Set up the ClickHandler NewPlugIn.Clicked += new PlugInLib.ClickHandler(Control_Clicked); } The PlugIn base blass
The PlugIn base class is based upon the System.Windows.Forms.UserControl class and extends it to provide predefined events, methods, and properties that each plug-in can use to communicate with the shell applications. In this example a Clicked event, a Caption property, and a TestFunction method are predefined. In addition, ToString is overridden to return the Caption instead of the object name.
Collapse
using System; using System.Windows.Forms; namespace PlugInLib { /// /// A delegate type for hooking up notifications. /// public delegate void ClickHandler(object sender, EventArgs e); /// /// Summary description for PlugIn. /// public class PlugIn : System.Windows.Forms.UserControl { // The following provides "Clicked" event back to the container. public event ClickHandler Clicked; protected void DoClick(EventArgs e) { if (Clicked != null) Clicked(this, e); } // Provide a "Caption" that the container can display. protected string m_Caption = "PlugIn"; public string Caption { get { return m_Caption; } set { m_Caption = value; } } public override string ToString() { return m_Caption; } // Provide a method "TestFunction" that the container can call. public virtual void TestFunction() { } } } Creating a UI plug-in.
Use Visual Studio to create a new “Windows Control Library”. Add a reference to the PlugInLib that contains the plug-in base class. Change the name of the user control from UserControl1 to something more descriptive. Add the using directive for the PlugInLib. Change the base class for the user control from System.Windows.Forms.UserControl to PlugIn. Connect any events that you want to send to the shell applications. Add the necessary overrides for calls from the shell to the plug-in. Construct your UI as you would for any other UserControl.
Collapse
using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Data; using System.Windows.Forms; using PlugInLib; // <---Add using for the plug-In base class namespace OurControls { /// /// Summary description for PlugIn3. /// public class PlugIn3 : PlugIn // <---Change base class to PlugIn { /// /// Required designer variable. /// private System.ComponentModel.Container components = null; public PlugIn3() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // TODO: Add any initialization after the InitForm call } /// /// Clean up any resources being used. /// protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Component Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { // // PlugIn3 // this.Caption = "PlugIn 3"; this.Name = "PlugIn3"; this.Click += new System.EventHandler(this.PlugIn3_Click); } #endregion // Override Base class to receive call from the shell. public override void TestFunction() { Console.WriteLine("TestFunction called by the shell."); } // Send clicks to the shell, just because we can. private void PlugIn3_Click(object sender, System.EventArgs e) { DoClick(e); } } } Conclusions
With this architecture, the interaction between the plug-ins and the shell should be well defined and limited. In the PlugIn base class example shown above, the only real interaction between the shell and plug-in is the Caption property. Another possible interaction would be for the shell to load a common data structure that is passed to each plug-in when it is loaded.
You can add new plug-ins at any time, simply by creating a new plug-in and adding the appropriate entries to the config.xml file.
Notes
In the demo zip available in the downloads section, config.xml file is located in both the Release and Debug directories of the two "Shell" applications. 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.
About Keith Jacquemin
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. Clickhere to view Keith Jacquemin‘s online profile.
Other popular Design and Architecture articles:
The Razor Framework :: Part 1 :: Plugins/Extensibility An extensible dependency based plugin framework for .NET Applications.
Leveraging .NET Components and IDE Integration: UI AOP in an MVC use case An in-depth exploration of the features and the power of .NET Component Model architecture, its integration with the IDE at design-time and the possiblities it opens through extensions at run-time.
Design Patterns Implementation in a Storage Explorer Application A design patterns approach for designing and explaining a Storage Explorer application. The application is used to explore file composition in a computer storage.
The Razor Framework :: Part 2 :: Configuration/Options An extensible dependency based plugin framework for .NET applications.
[Top] Rate this Article for us!     PoorExcellent


FAQ  Message score threshold 1.0 2.0 3.0 4.0 5.0    Search comments
View Normal (slow) Preview (slow) Message View Topic View Thread View Expanded (Supporters only)    Per page 10 25 50
New Message Msgs 1 to 25 of 35 (Total: 35) (Refresh) First PrevNext
Subject  Author  Date

 Designer support for plugins  waveangle  4:19 11 Dec ‘05
  In the source I downloaded, designer support was omitted for plugins. All I had to do was put back a few standard control lines to plugin.cs and the designer can be used.
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse


 Why require a config file?  Greg Donovan  21:06 26 Aug ‘05
  The application should only be concerned with a directory and a particular type of plugin. Why require the plugin in a config file? See the following for a better plugin implementation: www.ms-inc.net
[Reply |Email |View Thread |Get Link] Score: 1.5 (2 votes). Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: Why require a config file?  Keith Jacquemin  12:04 27 Aug ‘05
  The purpose of the article was to show loading User Interface elements at runtime. How you would obtain the list of plugins to load is up to you. I chose an XML file and loaded it into a dataset because it was easy. It could have been the App.Config file, or a table in an sql server, a call to a webservice or any other method that would allow the program identify the plugins to load. It dosen‘t mater where the data comes from.
Keith Jacquemin
kjacquemin@cox.net
[Reply |Email |View Thread |Get Link] Score: 5.0 (1 vote). Rate this message:12345 (out of 5)
Report asSpam orAbuse


 Dependencies  BigBenDk  11:39 27 Jul ‘05
  Hi
I made something similar some time ago, but i hit a problem.
Assembly A (Shell application), loads Assembly B (plugin), and B uses Assembly C (some classlib).
When A loads B, it does not load C - and therefor B fails to load.
Is there any fix to this?
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: Dependencies  Keith Jacquemin  20:44 27 Jul ‘05
  Assembly C needs to be either in the same directory as Assembly B or in the Global Assembly Cache (GAC) so it can be found.
Keith Jacquemin
kjacquemin@cox.net
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: Dependencies  BigBenDk  7:39 2 Aug ‘05
  I tried placing C in the same dir as B, but no go.
I also tried moving B and C to the same folder as A - still no go.
It worked if I added C to the GAC, but it issent a very elegant approach.
If A also uses C it works
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: Dependencies  richerm  9:01 22 Sep ‘05
  There are a few ways in which to load dependencies. The two that our choose to go with was either a dependency attribute which would take a list of assemblies to pre-load, or using reflection.
Dependency Attribute:
_
Public Class SomeClassB
Inherits Plugin
.
.
.
End Class
OR Reflection (assumes variable "asm" is of type System.Reflection.Assembly):
asm.GetReferencesAssemblies() -- returns String() of Assemblies references by that assembly.
Hope this helps.
Matt
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse


 help needed  raghavendra .K  0:11 28 Feb ‘05
  hi
i am working on IDE, which is supposed to inovoke the debugger( debugger is another assembly (exe)) i am using the following code to invoke the debugger
Assembly a;
object instance;
a = Assembly.LoadFrom(@"C:\debug.exe");
instance = a.CreateInstance("debug.mainform");
IDE.instance.Visible = false;
((System.Windows.Forms.Form)instance).Show();
the above code hides the IDE and opens the debugger..
after closing the debugger, IDE appln. should be visible...
how do i do it..
thanks in advance
regards
rag
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse


 how to create database connection inside of DLL  MBCMDR  13:54 15 Oct ‘04
  Hello again
I think the Plugin demo great, everything works BUT one problem I have myself. One of the DLL have SQLconnection to a database and can‘t get working think problem is at NewPlugIn = (PlugIn)ControlLib.CreateInstance(ControlName); I did convert all my code to VB.NET. so my statement is NewPlugIn = CType(ControlLib.CreateInstance(ControlName), PlugIn)
‘Problem might be in CreateInstance for why SQLconnection not working.........
Have you ever tried to put in a database connection in your plugin DLL ???
If so could you post your code you did inside the dll..
Thz
MBCMDR
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse


 OurControls  Dennis Bay  15:08 5 Oct ‘04
  Hello
I‘m still new at programming but I like your sample! I would like to have each Plugin as it‘s own DLL than loading 12 Plugins under one OurControl.dll
Would you have a sample to break up two plugins each under there own dll and work with PlugInLib, Shell & TabShell.. I‘m using Tabshell myself...
Dennnis
[Reply |Email |View Thread |Get Link] [Modify |Delete]
Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: OurControls  MBCMDR  15:16 5 Oct ‘04
  Hello
I found my accountI will also add I moved all the code to VB.NET
Dennis
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: OurControls  Keith Jacquemin  1:28 6 Oct ‘04
  Just create a new project for each plugin and adjust the entries in the config.xml file.
For example:






[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: OurControls  dbay1  11:37 6 Oct ‘04
  Hello
I thank you for your support!! It hit me like a hammer last night sleeping doing just that with the config file..
This Sample Has been very helpful and fit‘s my needs for this project. I have a client running 12 app in one form with 13 tabs hardcoded in a company but only 1 department needs all 12, rest only need 2-5.. Plugin seems a great fix.. 1 tab was a welcome will have in shell.
Thz
Dennis
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse


 Access error when loading the plug-in  Sivankoil  13:04 23 Jul ‘04
  I changed the config.xml file to point to the folder where plug-ins are located. However I am getting the following exception:
An unhandled exception of type ‘System.IO.FileLoadException‘ occurred in mscorlib.dll
Additional information: Access is denied: ‘Debug‘.
Unhandled Exception: System.IO.FileLoadException: Access is denied: ‘Debug‘
I looked at the code access security.
Any thoughts?
Thanks
Bala
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse


 Yet Another Alternative  mooman_fl  1:11 18 Mar ‘04
  There are two other examples that appear superior to this one. One is written by divil (aka Tim Dawson) on theXtreme .Net Talk[^] forums and the other is by me on the same forums.
The one by divil focuses on a basic plugin archtecture that doesn‘t carry any UI information and only works off of controls already present. Mine was based on divils, but expanded to include unique UI‘s contained in the plugin itself and attached to the main form.
The reason I say superior to the method above is that there is no need for a config file listing available plugins... you simply supply the path to the plugin directory and it scans all assemblies via Reflection to find ones that implement the needed Interface and attempts to load them. It also handles the subject of unloading plugins.
I apologize in advance for the condition of my example code. All the explanation is in the code comments although I did try to be as extensive as possible. Also I was mistaken in a few of the assumptions in the nature of the way things work. An experienced coder (I was a newbie at the time I wrote it) will notice these mistakes but they should not effect the usuability of the example.
The tutorials are as follows:
divil‘s plugin tutorial[^]
mooman_fl‘s plugin with embedded UI tutorial[^]
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: Yet Another Alternative  Keith Jacquemin  0:34 19 Mar ‘04
  As I stated before the purpose of the article was not to describe how read and parse XML or any other type of file. Its purpose was to show loading User Interface elements at runtime. How you would obtain the list of plugins to load is up to you. I chose an XML file and loaded it into a dataset because it was easy. It could have been the App.Config file, or a table in an sql server, a call to a webservice or as in your example via Reflection. It dosen‘t mater where the data comes from.
Also I think you will find that your code does not actually unload the plugins. Yes, they are released, and the GC removes the instances that you have created, but the plugin DLL will stay loaded untill the program ends. To actually unload the DLL the plugin must be created in a seperate appdomain, and the appdomain released.
Keith Jacquemin
kjacquemin@cox.net
Keith Jacquemin
kjacquemin@cox.net
[Reply |Email |View Thread |Get Link] Score: 5.0 (1 vote). Rate this message:12345 (out of 5)
Report asSpam orAbuse


 Globalization  Rudolf Ball  7:32 17 Aug ‘03
  Wow, I love that example. I tried it and it wored fine, but one thing I have problems with: how can I globalize that thing (the plugins and the shell)? I tried it with the globalization framework (GlobIt), but it doesn`t work. Please, help me
Thank you
Rudi
Rudi Ball
www.selfdotnet.com
[Reply |Email |View Thread |Get Link] Score: 2.0 (1 vote). Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: Globalization  Keith Jacquemin  11:44 18 Aug ‘03
  I haven‘t tried the GlobIt framework, but you must remember that each plugin contains its owm InitalizeComponent call. You will need to add the "Globalizator" to each plugin.
Keith Jacquemin
kjacquemin@sbcglobal.net
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: Globalization  Keith Jacquemin  20:11 18 Aug ‘03
  At the moment, I do not have access to my development systems. Send me an example of your code, and I will look at it. It will be at least a week before I can get back to you.
Keith Jacquemin
Keith Jacquemin
kjacquemin@sbcglobal.net
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse


 Thank you  dbystrov  8:11 8 Jul ‘03
  I was looking for plug-in‘s articals, yours are the best.
Best regards
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse


 unload the plug-in  khairil  10:48 6 Apr ‘03
  hi,
i like to ask a couple of questions. closely related to MMC style functionality.
1. instead of just the drop down menu, can i inherit toolbar form the plug-in, relpacing the toolbar in parent windows?
2. how can i unload the loaded plug in?
knowledge is power
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: unload the plug-in  Keith Jacquemin  13:04 7 Apr ‘03
  To unload a plugin you need to disconnect all event handlers and Delegates. Then remove and refrences to the plugin.
As with any .Net object, once the last refrence is removed, it will elegible for garbage collection.
You can‘t actually unload the dll that contains the plugin unless it has its own Application Domain and I have not researched AppDomains yet.
http://www.codeproject.com/useritems/ExtensibleUI2.asp shows paterns for accessing parts of the shell app. These paterns should work for any UI controls.
Keith Jacquemin
kjacquemin@sbcglobal.net
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse


 Writing the config?  JohnGH  7:47 18 Mar ‘03
  Is it possible that you post a small sample with which
adds a plugin to the config.xml?
I can‘t seem to be able figure it out myself
[Reply |Email |View Thread |Get Link] Rate this message:12345 (out of 5)
Report asSpam orAbuse

 Re: Writing the config?  khairil  10:39 6 Apr ‘03

i think the request is not quite related to the topic here, anyway, you should refersoap and xml section.
here‘s a couple of good one:-
1.Save Application Settings to XML
2.XML Parser Demo
-my greatest challenge is me-
[Reply |Email |View Thread |Get Link] Score: 2.0 (1 vote). Rate this message:12345 (out of 5)
Report asSpam orAbuse


 can i make convert it into vb.net  khairil  5:36 15 Mar ‘03
  hi,
i have make an application like this using visual basic (not .net) and now i‘m moving into .net environment. beside using mine, i like to try yours, is it possible for me to convert (your application) into vb.net?
knowledge is power
[Reply |Email |View Thread |Get Link] Score: 3.0 (2 votes). Rate this message:12345 (out of 5)
Report asSpam orAbuse

Last Visit: 9:23 Tuesday 2nd January, 2007 First PrevNext
General comment   News / Info   Question   Answer   Joke / Game   Admin message
Updated: 16 Dec 2002 Article content copyright Keith Jacquemin, 2002
everything else Copyright ©CodeProject, 1999-2006.
Web16 |Advertise on The Code Project |Privacy

The Ultimate Toolbox •ASP Alliance •Developer Fusion •Developersdex •DevGuru •Programmers Heaven •Planet Source Code •Tek-Tips Forums •
Help!
Articles
Message Boards
StoreFront
Lounge
What is ‘The Code Project‘?
General FAQ
Post a Question
Site Directory
About Us
Latest
Most Popular
Search
Site Directory
Submit an Article
Update an Article
Article Competition
Windows Vista
Visual C++
ATL / WTL / STL
COM
C++/CLI
C#
ASP.NET
VB.NET
Web Development
.NET Framework
Mobile Development
SQL / ADO / ADO.NET
XML / XSL
OS / SysAdmin
Work Issues
Article Requests
Collaboration
General Discussions
Hardware
Algorithms / Math
Design and Architecture
Subtle Bugs
Suggestions
The Soapbox