Click here to Skip to main content
Licence 
First Posted 30 Jul 2003
Views 54,852
Bookmarked 31 times

Visually Present Configuration Data

By | 30 Jul 2003 | Article
A quick approach for presenting configuration settings at runtime.

Sample Image - AppConfigEditor1.jpg

Introduction

When I began this article I envisioned that I would concentrate on demonstrating two things:

  1. My framework for storing and retrieving application configuration settings, and
  2. My approach for presenting those settings at runtime.

I changed my mind after running across the Configuration Management Application Blocks (CMAB) from the good folks at Microsoft. In case you don't already know, CMAB is a rather impressive .NET library for managing configuration data. (You can read more about the CMAB by navigating to this link).

So, instead of showing everyone how I had been reading and writing configuration data before I learned about the CMAB, I decided to concentrate instead on discussing how I present configuration settings to my users at runtime.

Regardless of how you choose to store configuration data (.INI file, .XML file, WIN32 registry, etc.), it's only a matter of time before someone asks you to expose that information to your end-users. It doesn't matter whether your software is targeted towards developers, managers, co-workers, or external customers. The truth of the matter is that, most people prefer to tweak their configuration settings via some sort of GUI. (I make an exception for all my hardcore Linux/Unix friends.)

I know what you are thinking; writing a dialog-box to present a few \ configuration settings seems pretty trivial - right? Well, in reality things aren't always as simple as they seem. For instance, I routinely come across scenarios such as:

  • The need for a solution that is flexible enough to work with dynamically loaded libraries.
  • A requirement that visual elements from multiple libraries be combined into a single, consistent user interface.
  • The fact that some of the groups I get my libraries from, don't always know (or won't disclose) the internal configuration requirements of their code, until after my software goes into testing.

The code I am about to demonstrate is a quick solution to the problem I have just outlined. The code is contained within a .NET library and consists of two classes: AppConfigEditorPanel and AppConfigEditorForm. C'mon, two classes, you can handle this!

AppConfigEditorPanel is a UserControl that provides a context for presenting and managing configuration settings at runtime. The code for this class is as follows:

public class AppConfigEditorPanel : System.Windows.Forms.UserControl
{

    // *******************************************************************
    // Events.
    // *******************************************************************

    public event System.EventHandler PropertyModified;

    // *******************************************************************
    // Attributes.
    // *******************************************************************

    private bool m_dirtyFlag;
    private System.ComponentModel.Container components = null;

    // *******************************************************************
    // Properties.
    // *******************************************************************

    internal bool DirtyFlag
    {
        get {return m_dirtyFlag;}
        set {m_dirtyFlag = value;}
    } // End DirtyFlag

    // *******************************************************************
    // Constructors.
    // *******************************************************************

    public AppConfigEditorPanel()
    {
        InitializeComponent();
    } // End AppConfigEditorPanel()

    // *******************************************************************
    // Public methods.
    // *******************************************************************

    public virtual void WritePropertyState()
    {
        // TODO : override in a derived class.
    } // End WritePropertyState()

    // *******************************************************************

    public virtual void ReadPropertyState()
    {
        // TODO : override in a derived class.
    } // End ReadPropertyState()

    // *******************************************************************
    // Protected methods.
    // *******************************************************************

    protected void FirePropertyModified()
    {

        // Update the dirty flag for this page.
        DirtyFlag = true;

        // Should we fire the event?
        if (PropertyModified != null)
            PropertyModified(this, EventArgs.Empty);

    } // End FirePropertyModified()

} // End class AppConfigEditorPanel

Deriving from AppConfigEditorPanel produces a GUI panel that you can customize by adding controls, no differently than you would normally do with a form. The only real difference is that this GUI will be contained within another form at runtime, as opposed to being displayed on it's own.

AppConfigEditorPanel has two virtual methods, both of which are called automatically by an instance of AppConfigEditorForm at runtime. WritePropertyState is called whenever the underlying configuration data should be saved. ReadPropertyState is called whenever the underlying configuration data should be read.

The DirtyFlag property is managed by the AppConfigEditorForm, and is used to track those pages that have had one or more of their properties changed by a user at runtime.

The PropertyModified event is listened to by the AppConfigEditorForm, and is used to indicate which AppConfigEditorPanels need to have their DirtyFlag property set.

The AppConfigEditorForm class is used to manage and contain all the instances of AppConfigEditorPanel at runtime. The code for this class is shown here:

public class AppConfigEditorForm : System.Windows.Forms.Form
{

    // *******************************************************************
    // Attributes.
    // *******************************************************************

    ArrayList m_panelList;

    private System.ComponentModel.Container components = null;
    private System.Windows.Forms.StatusBar m_statusBar;
    private System.Windows.Forms.TabControl m_tabControl;
    private System.Windows.Forms.Panel m_panelButtons;
    private System.Windows.Forms.Button m_buttonOK;
    private System.Windows.Forms.Button m_buttonCancel;
    private System.Windows.Forms.Button m_buttonApply;

    // *******************************************************************
    // Constructors.
    // *******************************************************************

    public AppConfigEditorForm()
    {
        
        try
        {

            // Show the wait cursor.
            this.Cursor = Cursors.WaitCursor;

            // Initialize the form.
            InitializeComponent();

            // Initialize the CMAB library.
            ConfigurationManager.Initialize();

            // Create a list to contain references to the panels.
            m_panelList = new ArrayList();

            // Attempt to read the "applicationConfigurationEditor" section.
            IDictionary table = (IDictionary)ConfigurationSettings.GetConfig(
                "applicationConfigurationEditor");

            // Is the section missing?
            if (table == null)
                throw new ConfigurationException
                  ("Missing 'applicationConfigurationEditor' 
                  section!");

            // Sanity check the "panelCount" section.
            if (!table.Contains("panelCount"))
                throw new ConfigurationException
                  ("Malformed 'applicationConfigurationEditor' 
                  section!");

            // Get the number of elements contained within the section.
            int panelCount = int.Parse((string)table["panelCount"]);

            // Loop and create the editor panels.
            for (int x = 0; x < panelCount; x++ )
            {

                // Read the next editor panel string.
                string panelString = (string)table["panel" + x];

                // Sanity check the result.
                if (panelString == null)
                    throw new ConfigurationException
                      ("Malformed 'applicationConfigurationEditor' 
                      section!");

                // Attempt to split out the various sections.
                string[] panelSections = 
                  panelString.Split(",".ToCharArray());

                // Sanity check the results.
                if (panelSections.Length != 3)
                    throw new ConfigurationException
                      ("Malformed 
                      'applicationConfigurationEditor' 
                      section!");
                                        
                // Attempt to load the panel assembly & class.
                AppConfigEditorPanel panel = 
                   (AppConfigEditorPanel)Assembly.Load(
                    panelSections[0]).CreateInstance(
                    panelSections[0] + "." + panelSections[1]);

                // Sanity check the result.
                if (panel == null)
                    throw new ApplicationException(
                        "Failed to create '" + 
                        panelSections[0] + "." + panelSections[1] + 
                        "' panel!"
                        );

                // Fixup the editor fill style.
                panel.Dock = DockStyle.Fill;
                        
                // Attempt to create a tab page to
                // physically contain the editor panel
                TabPage tp = new TabPage(panelSections[2]);

                // Add the editor panel to the tab page.
                tp.Controls.Add(panel);

                // Add the tab page to the tab control.
                m_tabControl.TabPages.Add(tp);

                // Add the panel to the list.
                m_panelList.Add(panel);

                // Populate the panel.
                panel.ReadPropertyState();

                // Wire up the event sink(s).
                panel.PropertyModified += new 
                  System.EventHandler(_OnPropertyModified);

            } // End for all the panels.

        } // End try

        finally
        {

            // Reset the cursor.
            this.Cursor = Cursors.Default;

        } // End finally
        
    } // End AppConfigEditorForm()

    // ************************************************
    // Form event handlers.
    // ************************************************

    private void AppConfigEditorForm_Closing(object sender, 
                     System.ComponentModel.CancelEventArgs e)
    {
    
        // Are there unsaved changes?
        if (this.DialogResult != DialogResult.OK && 
                                m_buttonApply.Enabled)
        {

            // Prompt the user.
            switch (MessageBox.Show(
                this,
                "There are unsaved changed. 
                Do you want to save them now?",
                this.Text,
                MessageBoxButtons.YesNoCancel,
                MessageBoxIcon.Question 
                ))
            {

                case DialogResult.Cancel :

                    e.Cancel = true;

                    return;

                case DialogResult.No :

                    return;

                case DialogResult.Yes :

                    break;
                                            
            } // End switch
                                
        } // End if the apply button is enabled.

        m_buttonApply.PerformClick();
        this.Close();

    } // End AppConfigEditorForm_Closing()

    // **********************************************
    // Button event handlers.
    // **********************************************

    private void m_buttonApply_Click(object sender, 
                                   System.EventArgs e)
    {

        try
        {

            // Show the wait cursor.
            this.Cursor = Cursors.WaitCursor;

            // Loop through the panels and save any changes.
            foreach (AppConfigEditorPanel panel in m_panelList)
            {

                // Should we save changes?
                if (panel.DirtyFlag)
                {
                
                    panel.WritePropertyState();
                    panel.DirtyFlag = false;

                } // End if we saved the changes.

            } // End for every panel.

            // Disable the button again.
            m_buttonApply.Enabled = false;

        } // End try

        finally
        {

            // Reset the cursor.
            this.Cursor = Cursors.Default;

        } // End finally

    } // End m_buttonApply_Click()

    // *******************************************************************
    // Private methods.
    // *******************************************************************

    private void _OnPropertyModified(object sender, System.EventArgs e)
    {

        // Enable the apply button.
        m_buttonApply.Enabled = true;

    } // End _OnPropertyModified()
    
} // End class AppConfigEditorForm

The constructor looks at the App.config file for the applicationConfigurationEditor section, and uses that information to determine how many tab pages should be created, and where the code for each associated instance of AppConfigEditorPanel may be found.

The Closing event handler ensures that the user is prompted whenever the form is canceled with an unsaved property change.

The apply button handler is used to direct all the AppConfigEditorPanel instances that contain unsaved changes to save their state.

The PropertyModified event handler is used to enable the 'Apply' button whenever a user modifies a property anywhere within the form.

Using the code

Using the code is pretty simple: first create one or more AppConfigEditorPanel derived classes and populate them with controls and such. After that, derive from AppConfigEditorForm in your main application, make any customizations you require, and then add the code to call the form like this:

private void m_buttonSettings_Click(object sender, System.EventArgs e)
{
    new SettingsForm().ShowDialog(this);
} // End m_buttonSettings_Click()

After that, open the App.config file for your application and add something like the following:

<configuration>
    <configSections>
        <section 
            name="applicationConfigurationEditor" 
            type="System.Configuration.SingleTagSectionHandler" />
    </configSections>

    <applicationConfigurationEditor 
        panelCount="2" 
        panel0="AppConfigEditor.TestGUI,TestPanel1,Application Settings" 
        panel1="AppConfigEditor.TestLib,TestPanel2,Library Settings"         
        />

    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <qualifyAssembly 
            partialName="AppConfigEditor.TestLib" 
            fullName="AppConfigEditor.TestLib,
              Version=1.0.0.0,Culture=neutral,PublicKeyToken=null" />
        </assemblyBinding>
    </runtime>
</configuration>

The configSections section is standard .NET, and is used to declare sections to the .NET configuration classes. This is how the .NET framework finds the various sections at runtime. In this case I have added a declaration for the applicationConfigurationEditor section.

Inside the applicationConfigurationEditor section itself, I have inserted parameters to tell the AppConfigEditorForm how many panels to load, and where those panels should come from. Each panel is configured with a string that contains the assembly that contains the panel, the class that produces the panel, and finally a title for the GUI. The elements of this string are delimited with a ',' character.

I have also shown an example of the runtime section, which is used to load assemblies (libraries) dynamically at runtime. It just so happens that the demo application loads an AppConfigEditorPanel from a library called AppConfigEditor.TestLib, so this section configures the .NET runtime to load that assembly.

The demo application

The demo application uses a form derived from AppConfigEditorForm to present some dummy configuration settings for the application and a library named TestLib. The form is configured using the App.config file to load one AppConfigEditorPanel from the application, and another from the library. The configuration data in the demo is managed via the CMAB from Microsoft, but you can use whatever method you are comfortable with in your own projects.

Conclusion

What I have shown here is a simple method for displaying configuration settings. The nice thing about my approach is that it has nothing to do with managing the settings themselves - it simply focuses on presenting them. That means you can use this code regardless of where or how you manage your configuration data.

Note that my approach also works with libraries licensed from third-party vendors. All you need to do is create a panel to present the configuration data and add the resulting code to your application. Don't forget to also add the appropriate lines to the App.config file.

Have fun! :o)

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

About the Author

Martin Cook

Web Developer

United States United States

Member

I am a C# developer specializing in creating object-oriented software for Microsoft Windows. When I am not programming I enjoy reading, playing the guitar/piano, running, watching New York Ranger hockey, designing and building wacky electronic devices, and of course enjoying good times with my wife and children.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralSectionGroup PinsussConfigProgrammer23:55 1 Oct '03  
GeneralRe: SectionGroup Pinmemberlionhurt20000:43 2 Oct '03  
GeneralWrite method PinmemberRoel Ang14:10 31 Jul '03  
GeneralRe: Write method PinmemberRoel Ang14:25 31 Jul '03  
GeneralRe: Write method PinmemberRoel Ang14:32 31 Jul '03  
GeneralRe: Write method PinmemberMartin Cook2:12 1 Aug '03  
GeneralRe: Write method PinmemberRoel Ang10:40 1 Aug '03  
GeneralCMAB URL PinmemberAdam Nelson6:41 31 Jul '03  
GeneralRe: CMAB URL PinmemberMartin Cook6:58 31 Jul '03  

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.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120517.1 | Last Updated 31 Jul 2003
Article Copyright 2003 by Martin Cook
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid