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

Application Suite Template

0.00/5 (No votes)
10 Mar 2004 1  
An example of building an application suite using reflection and custom attributes to dynamically discover and add child applications.

Sample Image - SuiteApp.jpg

Introduction

Suites are a way to package and distribute applications in a common framework to give a common look and feel, or to group functionality under a common container. Extending the suite to add new applications, or to update existing ones, can be problematic. The approach I will demonstrate here is to use dynamic runtime discovery to load applications from DLLs.

Credits

The Outlook Bar used in this project is from Marc Clifton�s article An Outlook Bar Implementation. I made a small modification to support an additional field.

Suite Container

The suite container is the shell that holds everything together. It gives the applications a common look and feel, menu items, and basically a place to hang out. For this project, I�ve created a very simple container with an Outlook Bar to group and show loaded apps and an area to display the apps interface.

Loading Apps

When the suite container starts, it needs to look for any applications to load. One way to do this is to have it search a given path for any DLLs and attempt to load them. The obvious problem with this approach is that the path may contain support DLLs that can�t, or should not, be loaded. A way to get around this is to use custom attributes to indicate that the class, or classes, within the DLL are meant to be part of the application suite.

public static Hashtable FindApps()
{
    // Create hashtable to fill in

    Hashtable hashAssemblies = new Hashtable();

    // Get the current application path

    string strPath = Path.GetDirectoryName(ExecutablePath);
    
    // Iterate through all dll's in this path

    DirectoryInfo di = new DirectoryInfo(strPath);
    foreach(FileInfo file in di.GetFiles("*.dll") )
    {
        // Load the assembly so we can query for info about it.

        Assembly asm = Assembly.LoadFile(file.FullName);
        
        // Iterate through each module in this assembly

        foreach(Module mod in asm.GetModules() )
        {
            // Iterate through the types in this module

            foreach( Type t in mod.GetTypes() )
            {        
              // Check for the custom attribute 

              // and get the group and name

              object[] attributes = 
                t.GetCustomAttributes(typeof(SuiteAppAttrib.SuiteAppAttribute), 
                                                                         true);
              if( attributes.Length == 1 )
              {
                string strName = 
                  ((SuiteAppAttrib.SuiteAppAttribute)attributes[0]).Name;
                string strGroup = 
                  ((SuiteAppAttrib.SuiteAppAttribute)attributes[0]).Group;
                
                // Create a new app instance and add it to the list

                SuiteApp app = new SuiteApp(t.Name, 
                          file.FullName, strName, strGroup);
                    
                // Make sure the names sin't already being used

                if( hashAssemblies.ContainsKey(t.Name) )
                  throw new Exception("Name already in use.");

                hashAssemblies.Add(t.Name, app);
              }
            }
        }
    }

    return hashAssemblies;
}

As you can see from this code, we are searching the application path for any DLLs, then loading them and checking for the presence of our custom attribute.

t.GetCustomAttributes(typeof(SuiteAppAttribute), true);

If found, we then create an instance of the SuiteApp helper class and place it in a Hashtable with the apps name as the key. This will come into play later when we need to look up the app for activation. It does place the limit of not allowing duplicate application names, but to avoid confusion on the user end, it is a good thing.

Using Attributes

Attributes are a means to convey information about an assembly, class, method, etc. Much more information about them can be found here, C# Programmer's Reference Attributes Tutorial. In the case of this project, a custom attribute is created and used to give two pieces of information necessary for the suite to load any app it finds. First, just by querying for the mere presence of the attribute, we can tell that the DLL should be loaded. The second part of information we get from the attribute is the name of the Outlook Bar group it should be added to and the name to be shown for the application.

[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class SuiteAppAttribute : Attribute
{
    private string m_strName;
    private string m_strGroup;

    /// <SUMMARY>

    /// Ctor

    /// </SUMMARY>

    /// <PARAM name="strName">App name</PARAM>

    /// <PARAM name="strGroup">Group name</PARAM>

    public SuiteAppAttribute(string strName, string strGroup)
    {
        m_strName = strName;
        m_strGroup = strGroup;
    }

    /// <SUMMARY>

    /// Name of application

    /// </SUMMARY>

    public string Name
    {
        get{ return m_strName; }
    }

    /// <SUMMARY>

    /// Name of group to which the app

    /// should be assigned

    /// </SUMMARY>

    public string Group
    {
        get{ return m_strGroup; }
    }
}

The first thing to note here is the attribute applied to this custom attribute.

[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]

This tells the runtime that this particular custom attribute can only be applied to a class and further can only be applied once.

Creating supported apps

To create applications to become part of our suite, we need to start with the Build event. To facilitate debugging, the application(s) must be moved to a location where the suite container can detect and load them. This step can be automated by adding a post-build step.

Build Event Dialog

copy $(TargetDir)$(TargetFileName) $(SolutionDir)SuiteAppContainer\bin\debug

Provided that everything is in the same solution, this will copy the output of the application's compile step to the debug folder of the suite container.

Activating Apps

To activate the application once the icon has been selected, we first make a check that it does indeed exist in the HashTable, if not there are some real problems. We also need to make sure the form has not already been created. Once these checks are verified, the path to the assembly is located and loaded. The InvokeMember function is used to create an instance of the form in question. We set a handler for the form closing event, so it can be handled as we�ll see later.

public void OnSelectApp(object sender, EventArgs e)
{
OutlookBar.PanelIcon panel = ((Control)sender).Tag as 
OutlookBar.PanelIcon;
    
    // Get the item clicked

    string strItem = panel.AppName;

    // Make sure the app is in the list

    if( m_hashApps.ContainsKey(strItem) )
    {
        // If the windows hasn't already been created do it now

        if( ((SuiteApp)m_hashApps[strItem]).Form == null )
        {
            // Load the assembly

            SuiteApp app = (SuiteApp)m_hashApps[strItem];
            Assembly asm = Assembly.LoadFile(app.Path);
            Type[] types = asm.GetTypes();

            // Create the application instance

            Form frm = (Form)Activator.CreateInstance(types[0]);
            
            // Set the parameters and show

            frm.MdiParent = this;
            frm.Show();
            // Set the form closing event so we can handle it

            frm.Closing += new 
            CancelEventHandler(ChildFormClosing);

            // Save the form for later use

            ((SuiteApp)m_hashApps[strItem]).Form = frm;

            // We're done for now

            return;
        }
        else
        {
            // Form exists so we just need to activate it

            ((SuiteApp)m_hashApps[strItem]).Form.Activate();
        }
    }
    else
        throw new Exception("Application not found");
}

If the form already exists then we just want to activate it, bring it to the front.

((SuiteApp)m_hashApps[strItem]).Form.Activate();

Form Closing

We need to be able to capture when the child forms close so that the resources can be freed and when it is required again the form will be recreated rather than attempting to activate a form that has already been closed.

private void ChildFormClosing(object sender, CancelEventArgs e)
{
    string strName = ((Form)sender).Text;

    // If the app is in the list then null it

    if( m_hashApps.ContainsKey(strName) )
        ((SuiteApp)m_hashApps[strName]).Form = null;
}

Menus

The next area to address are the menus. The main container app has a basic menu structure and each application it houses will have its own menus, some with the same items.

Sample screenshot

Combining menus isn�t terribly difficult; it just takes some attention to detail and planning. The menu properties MergeType and MergeOrder are used to sort out how the menus are merged and where the items appear. The default settings are MergeType = Add and MergeOrder = 0. In the case of this example, we want to merge the File menu of the container app with the File menu from the child app. To start with, we need to set the MergeType of the main window file menu to MergeItems. The File menu in the child app must also be set to MergeItems.

Sample screenshot

This gets a little closer but as can be seen, we have duplicates of some items. I�ve renamed the child�s exit menu to better illustrate the problem. To correct this, we need to change the main window�s exit menu item to MergeType = Replace.

Sample screenshot

Now, we have the expected results. The next step is to set the MenuOrder. This doesn�t affect the merging of the menus but does affect where the items appear. Reviewing the sample project, we can see that the exit menu items have a MergeOrder of 99. Merging starts at 0 with higher numbered items being merged lower in the menu.

Sample screenshot

By setting the MergeOrder to 0, we see that the exit menu item is placed higher in the file menu.

Conclusion

This is certainly not an earth shattering killer application. What I hope is, it is a learning tool to explorer capabilities and present the seeds for thought, possibly some of the techniques can be incorporated into other applications.

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