WinForms Wizard Series Article 6, a quick look at implementing a meta-property storage class.

See Also:
Article 1 in this series: Article 1 in a series about WinForms Wizards: The fastest Wizard in the West.
Article 2 in this series: A slightly better WinForms wizard, and slightly more work.
Article 3 in this series: Adding Named Panel Navigation to the WizardController
Article 4 in this series: Providing design time support for the Wizard Framework
Article 5 in this series: Adding a design time dialog and creating a VS project sample
Article 6 in this series: You're looking a it

Abstract:
Well, it looks like people like reading the articles at least a little bit.  So I'm going to talk about a superficial enhancement (aka, you can't add this feature in as is, but rather you'll need to wait for the next article) that will add quite a few features.  What kinds of features does this add?  Well, first it adds the ability to store meta-properties.  Meta-properties are little named properties that you simply attach to the meta-store, that are then made available to the dialog, any panels you have, and finally for you, once the wizard is complete.

Someone mentioned that they had added the ability to disable/enable the navigational buttons based on some work in the dialog.  Well, that is the perfect reason for a meta-store.  You see, the enabling of buttons is really properties.  Currently, the properties are set only once, during InitPanel.  The next step would be to have a property store that allows eventing or property change notification.  Then, if the property is changed, the dialog that hosts the navigational controls could update them based on any property changes.

This isn't a joke framework by far, and it isn't a bolt-on.  This is a highly specialized, well planned (nearly a week in the making now), meta-store with plenty of extensibility built into the interfaces, and even more extensibility available to be built in and transparently layered inside of the current wizard controller.  Yes, we are still going on the assumption that we want changes to be as transparent as possible, and that means all of the old dialogs should still run.  Time to check out this excellent addition to our code-base.

TOC:

      1. IWizardMetaStore the magic behind the store
      2. Adding settings and making them generic with IWizardMetaSetting
      3. Implementing a quick and simple store with WizardMetaStore
      4. The IWizardControllerEvents and there use for later
      5. Conclusion

1.  IWizardMetaStore the magic behind the store
Honestly, most of the changes to the code-base will be later when we thread all of this code into the actual framework.  I tried to make the meta-store interfaces as simple and easy to implement as possible, because I think the power they offer lies in how generic they are.  We'll start by figuring out how we want to interact with the store itself.  I'm going to assume that you can add properties, get properties by name, or get a listing of all properties.  Everything will make use of a settings interface IWizardMetaSetting, so that we can implement private class logic that only the creator of a setting can access while providing generic access to the underlying value for consumers.

public interface IWizardMetaStore {
    bool AddSetting(IWizardMetaSetting setting);
    IWizardMetaSetting GetSetting(string keyName);
    IWizardMetaSetting[] GetSettings();
}

Notice that AddSetting returns a bool value indicating success or failure.  I chose this option, since it would be possible for a control to add a setting that has already been added to the store.  Again, think generic and powerful, so we can't go around enforcing name constraint rules and other nonsense.  GetSetting will return a basic IWizardMetaSetting and GetSettings grabs them all.  Pretty easy stuff.  The power in the store comes from the eventing model though.  We need to add an event, StoreChanged, that hosts can hook and enable event notification when properties change and other events occur.  We'll put this right on the interface, and C# will make sure that the meta-store class does the rest.

event WizardMetaStoreChanged StoreChanged

We have to fill out the event definition though.  Normally events have the concepts of taking an object as the sender or originator of the event.  We know that only the meta-store will be calling the event, so we'll ensure that the sender is of type IWizardMetaStore instead.  That makes our implementation a little different from everyone else's I'm sure.

In addition, you'll have to notify the sink as to what happened.  WizardMetaStoreChangedEventArgs is used for just this purpose.  It takes the setting that is changing as an IWizardMetaSetting as you might expect.  It also takes a WizardMetaStoreChangedMode enumeration flag.  This tells you how the property changed and can be used for lots of advanced logic.  Right now the values we'll support are None (should never happen), Added (when a new property is added), Removed (we won't support this, but it is there just in case), and Updated (This is the one everyone will use).

public enum WizardMetaStoreChangedMode {
    None            = 0,
    Added           = 1,
    Removed         = 2,
    Updated         = 3
}

public delegate void WizardMetaStoreChanged(IWizardMetaStore sender, WizardMetaStoreChangedEventArgs e);
public class WizardMetaStoreChangedEventArgs : EventArgs {
    private IWizardMetaSetting setting;
    private WizardMetaStoreChangedMode mode;

    public WizardMetaStoreChangedEventArgs(IWizardMetaSetting setting, WizardMetaStoreChangedMode mode) {
        this.setting = setting;
        this.mode = mode;
    }
   
    public IWizardMetaSetting Setting { get { return this.setting; } }
    public WizardMetaStoreChangedMode Mode { get { return this.mode; } }
}

That is it for the store.  I'm always curious if I left something out.  Obviously we might want to add removal later, and in order to do that I'll have to break the interface definition (because I certainly won't make new interfaces just for that).

2.  Adding settings and making them generic with IWizardMetaSetting
I thought for quite some time about how the properties should be added.  I wanted to have the power of read-only properties, full-fledge strongly typed classes for the actual property creator to make use of, generic access to the underlying data for easy consumption, with enough flexibility so that you didn't fell like the interface was preventing you from implementing a feature.  I think I finally came up with just that and it was far simpler than I though.

We start with a couple of basic properties, KeyName to identify the property by name and Value to return the underlying data.  Value is obviously an Object type, so that we can return anything we might want.  Using the data, you'll have to cast to a specific type and make use of it that way.  Normally you'll be using basic values, however, the power is there to use strongly typed objects to fully back each of your panels and implement whatever you might want.  I'll prove this later with Dungeon Dialogs, since the property store is used to fully back the user class, inventory, and other RPG like features of the game.  You can also determine if a property is writeable using the Write property.  If Write is enabled, you should also implement the SetValue method so that the value can be changed.  I made this a method for a simple design reason, just because you want to set a value, and even if the property is writeable, it doesn't mean the change will happen.  With a property, if you set the value, you need to throw an exception to notify the user.  A method implies work being done, aka a black box, and that is exactly what SetValue is in this implementation.  You can do lots of work, like checking the stack to make sure a specific object is accessing trying to change your property, check security permissions, or whatever you want to do.

public interface IWizardMetaSetting {
    string KeyName { get; }
    object Value { get; }
    bool Write { get; }
   
    void SetValue(object value);
    event WizardMetaDataChanged SettingChanged;
}

Uh oh, I snuck another event in there.  The setting is allowed to raise an event when it changes (yet another reason to use a method for setting values).  That is how the store knows that settings are changing.  This gives you two methods for interacting with properties.  Either you can hook the SettingChanged event on only the properties you want, or you can hook the StoreChanged event and check the key name of any changed settings for those you are interested in.  I highly recommend you use the later syntax, since it was the original design behind the model and centralizes all of your property change code into one location (one event handler).

Just as before, we have to fully define the event.  We'll use the sender feature again and set it to an IWizardMetaSetting for item being changed.  We'll define an event arguments class again, but we won't be passing any values on it.  The class is blank and is only there to provide a unique method signature (note proper event handler design calls for you to define a unique arguments class to prevent luring attacks).

public delegate void WizardMetaDataChanged(IWizardMetaSetting sender, WizardMetaDataChangedEventArgs e);
public class WizardMetaDataChangedEventArgs : EventArgs {
    public static new WizardMetaDataChangedEventArgs Empty = new WizardMetaDataChangedEventArgs();
}

As you can tell, the emphasis is still on simplicity and flexibility.  You probably can't see it yet, but this is a really powerful interface (I couldn't see it until I had written the 200 lines of code or so to actually embed it into the wizard framework, so if you can see it then give me some pointers).  We'll implement a very short and generic Meta-store now.  I can't figure out why you'd need anything more complex than what I'm providing here, and this will be the same meta-store that I use for the remainder of the article series and the games I produce.

3. Implementing a quick and simple store with WizardMetaStore
The WizardMetaStore is all about implementing our interface.  Since we want to preserve name uniqueness, we'll simply use a Hashtable to back out store.  We'll use a case-sensitive Hashtable in order to convince you to be careful when creating your dialogs and look for instances where you mis-type property names based on type.  You could easily change out the Hashtable for a case-insensitive version and it wouldn't change things at all.  Normally when creating the class, you won't have any settings ready yet, and you'll let the dialogs and panels add them, so we'll have an empty constructor.  If you do have some properties to add, we'll implement an AddRange style constructor that takes an array of IWizardMetaSetting objects.  We'll then add each setting from the array to the underlying collection, throwing an exception if any of the properties have duplicate names.

public class WizardMetaStore : IWizardMetaStore {
    private Hashtable settingStorage = new Hashtable();

    public WizardMetaStore() { }
    public WizardMetaStore(IWizardMetaSetting[] initialSettings) {
        foreach(IWizardMetaSetting setting in initialSettings) {
            if ( !AddSetting(setting) ) {
                throw new Exception("You attempted to add a setting without a unique key name");
            }
        }
    }
}

We already start using AddSetting even in the constructor.  We'll implement a very simple method that checks for duplicate entires in the Hashtable and then either adds the setting and returns true or doesn't add the setting and returns false.  If we do add the setting then a quick call to OnStoreChanged will also raise our StoreChanged event with the Added mode for the property.  GetSetting is going to rip the setting right out of the Hashtable and return it, while GetSettings will copy all of our values into a temporary array and return it.  I thought about doing some cool caching and what not on these properties, and implementing cloning routines and a bunch of other stuff, but KISS is the name of the day.

The remainder of the methods are for setting up the eventing model.  Proper design guidlines require that we create an OnStoreChanged helper function for launching our event.  The method takes a properly built event args fires the event if anyone has it hooked.  We also have an event handler for the SettingChanged event of the properties.  We use the handler in this case to turn around and fire StoreChanged events.

public class WizardMetaStore : IWizardMetaStore {
    public event WizardMetaStoreChanged StoreChanged;

    public bool AddSetting(IWizardMetaSetting setting) {
        if ( settingStorage.ContainsKey(setting.KeyName) ) { return false; }
       
        settingStorage[setting.KeyName] = setting;
        setting.SettingChanged += new WizardMetaDataChanged(Settings_WizardMetaDataChanged);
        OnStoreChanged(new WizardMetaStoreChangedEventArgs(setting, WizardMetaStoreChangedMode.Added));
       
        return true;
    }
   
    public IWizardMetaSetting GetSetting(string keyName) {
        return (settingStorage[keyName] as IWizardMetaSetting);
    }
   
    public IWizardMetaSetting[] GetSettings() {
        IWizardMetaSetting[] settingStorageCache = new IWizardMetaSetting[settingStorage.Values.Count];
        settingStorage.Values.CopyTo(settingStorageCache, 0);
        return settingStorageCache;
    }
   
    private void OnStoreChanged(WizardMetaStoreChangedEventArgs args) {
        if ( StoreChanged != null ) { StoreChanged(this, args); }
    }
   
    private void Settings_WizardMetaDataChanged(IWizardMetaSetting setting, WizardMetaDataChangedEventArgs args) {
        OnStoreChanged(new WizardMetaStoreChangedEventArgs(setting, WizardMetaStoreChangedMode.Updated));
    }
}

We could have spent a long time making the storage unit complex, but it doesn't need to be.  The complexity comes later when implementing properties.  Since the model is not restrictive, it probably gives you too many options.  I know I had a hard time implementing the first few properties, but we'll get to that later.  Instead we'll work into the eventing model that will be hosted on dialogs and panels in order to interact with the meta-store.

4. The IWizardControllerEvents and there use for later
This was the hardest part of the model to define for me.  There are events that both panels and the dialogs needed to get, but not necessarily any reason to split out one interface for each type of element.  I finally resolved that everything would be implemented using methods (not events), and that when each method is called, would be dependent upon the state of the WizardController.  In other words, the WizardController enables or disables any meta-storage features.  It also allows the dialogs/panels to be completely abstracted from the actual meta-store itself, except for when both the interface is implemented and the store is actually present.  Again, everything had to be backwards compatible.

Since the meta-store is what got all this started, we'll work on store specific methods.  Each method we define is going to retrieve a pointer to the IWizardMetaStore, so you always have access to any properties.  We'll use MetaStoreConnect to denote that a store becomes connected to a controller.  This is where you might add your own settings to the store, or check to make sure the settings are already there.  MetaStoreDisconnect is called whenever a store is removed.  This can be because the store is replaced with another, or set to null on the controller.  You can use this to finalize your settings or remove them (note removing isn't an option at this time, but might be later).  The final meta-store method is MetaStoreChanged.  This one is slightly different because it not only gets a pointer to the store, but also an instance of the WizardMetaStoreChangedEventArgs.  Whenever a property changes and the WizardController wants you to know, it'll call this method with the relavent information.  You can use this to do calculated properties based on the values of other properties, or any other feature you might think of.

public interface IWizardControllerEvents {
    void MetaStoreConnect(IWizardMetaStore store);
    void MetaStoreDisconnect(IWizardMetaStore store);
    void MetaStoreChanged(IWizardMetaStore store, WizardMetaStoreChangedEventArgs args);
    void Initialize(IWizardMetaStore store);
    void NavigateTo(IWizardMetaStore store);
    void NavigateAway(IWizardMetaStore store);
    void Complete(IWizardMetaStore store);
}

Another 4 methods rounds out the interface with controller events.  Initialize will get called during StartWizard, so you always have a place to set default values, or reinit your panels.  NavigateTo will occur whenever you are the target of a navigation, letting you set up any properties.  This should happen before InitPanel gets called and your properties are investigated, so I'll make sure to keep that in mind with the next article.  NavigateAway is a place for you to wrap up any property values before control navigates away from your panel.  I could have implemented some sort of cancellation routines here, but I couldn't find any reason why when you can control the navigation explicitly by enabling or disabling the underlying controls.  I guess if anyone wnats the feature I could add it though.  The final method, Complete, is used whenever the controller is moving into a completed state.  You can use this method from within your dialogs to implement any final calculations or wrap-up code needed.  The idea is that the meta store will be disconnected and investigated later by your code to retreive all of your values (and maybe reconnected if you run the dialog again and want some state in between).

5.  Conclusion
I'm pretty stoked again.  After my last article and attempting to punch out all of this code, I simply couldn't find a good way to talk about it.  Now that I've finally gotten over that mental block, I can definitely say, the next article is going to be super hot, and enable lots of really awesome features.  I also think it will be the final step before I can really make some nice satellite projects.  While there will probably be some room for code clean-up, after everything is done, and maybe some solification of the object model and maybe enhancing the entire framework with custom exceptions and what-not, that won't be my final focus.  My focus has always been to deliver clear and concise documentation on implementing great wizards and hopefully I'm fulfilling my goals.

Published Monday, April 26, 2004 7:02 AM by Justin Rogers
Filed under:

Comments

Monday, April 26, 2004 10:08 PM by TrackBack

# Wizards in .NET with C#

Justin Rogers is the wizard man. He already wrote his sixth article in the series, I've just started with the first one, basically copying and pasting his code into VS.NET, and got a simplistic wizard app to work right away....
Sunday, May 16, 2004 12:33 PM by Sacha De Vos

# re: WinForms Wizard Series Article 6, a quick look at implementing a meta-property storage class.

I can't wait to see the next article ! Thanks for the very good ideas ... First time I see a working real life example of the model/controller/UI approach.
I had some troubles adapting this wizard so that it uses a modal dialog within an existing application though. Anyways this saved me a whole bunch of work. thanks again...
Wednesday, June 23, 2004 7:01 AM by Grey

# re: WinForms Wizard Series Article 6, a quick look at implementing a meta-property storage class.

When is article 7 coming up ? ... and the Gaming sample ;-)
Wednesday, December 01, 2004 6:41 AM by TrackBack

# Problems with CF2002 and Visual inheritance in a Wizard prototype

Monday, March 24, 2008 2:33 PM by John Savold

# re: WinForms Wizard Series Article 6, a quick look at implementing a meta-property storage class.

Did you ever do anything further with this? Article 7? Thanks...

Wednesday, March 04, 2009 6:35 PM by ...

# re: WinForms Wizard Series Article 6, a quick look at implementing a meta-property storage class.

Interessante Informationen.

Leave a Comment

(required) 
(required) 
(optional)
(required)