How to manage control states in WinForm Applications.

It’s quite a common problem almost every developer has if he wants to develop a WinForm Application. How to set the control state of buttons, menu items and other WinForm controls?

The stupid approach is to establish a new method that handles the different states of the controls regarding the internal context of the application. This normally ends up in a very big, unconfortable to mantain and extremly buggy SetState Method.

So what is the answer to that problem?

Because I currently have this problem in one of my WinForm projects too, I developed a control extender that manages the states of all WinForm controls transparent for the developer. The advantage is, that it’s highly configurable using the Application Config File.

Here’s an example:
UIP Config

As you can see, every form has it’s own entry within the StateSections. And every control has it’s entry within that section. It’s set up like the WinForm itself.

Now every control has several possibilities to be configured.

Attribute Description
state Can be set to enabled / disabled or can be left empty. If a state has been set, all other checks will be ignored.
roles It’s a comma separated list with roles, the current principal must belong to. If the current principal does not belong to the role, the control will be disabled.
check The Control State Extender calls a check routine that tells the extender if the control has to be disabled or enabled. Such a check routine can be implemented in a controller class or within the WinForm itself.

 

All you have to do is, throw the ControlStateExtender onto your WinForm and name the controls with a new Property called ControlKey. This ControlKey property is used to identify the control within the App.Config file.

Properties of a WinForm Control

Furthermore the ControlStateExtender control has a property called Section, which defines the section the ControlStateExtender has to use within the config file.

Properties of the control state extender

When you initialize your Form you have to initialize the ControlExtenderClass too. The parameter you have to specify is the controller class that handles the check methods. This might be the WinForm itself.

public Form1()
{
    InitializeComponent();
    controlState.Initialize(this);
}

So the only thing that is left, is to implement the check methods we defined within the application config. This example has a very poor logic, but ok – it’s only for testing purpose.

public ControlState GetButton1State(Control toCheck)
{
    return switchButton ? ControlState.Enabled : ControlState.Disabled;
}

Last but not least, you have to initiate the Refresh every time the user press a button. This can’t be done automatically, you have to call the Refresh Method.

private void button1_Click(object sender, EventArgs e)
{
    switchButton = false;
    controlState.Refresh();
}

Now – watch the application.

Control State Extender Example Application

The complete example and control state extender can be downloaded using the following link:
Control State Extender and Example Application

I hope that I could make your life a little bit easier with that piece of code.
Wish you all the best.

Cheers
Gerhard

Initialize [NonSerialized] Members

Hi guys,

after long time of abstinence I found an interesting issue to talk about. In our web project we are putting some context informations into the view state, like 1000 other web applications too I think. We developed a special class that holds the values – it called “Context”.

This context class holds some NonSerialized Members like a local cache designed as a hashtable. The problem ist, that after de-serializing the context class the local cache is not initialized although we initialized it within the constructor.

That’s strange – and I have to confess that I really don’t know why it happens.

Here’s a small the example of the context that fails:

/// <summary>
/// Context class
/// </summary>
[Serializable]
public class ContextThatFails
{
    private string serializedValue;
    
    [NonSerialized]
    private Hashtable localCache;
    
    /// <summary>
    /// Initializes a new instance of the <see cref="ContextThatFails"/> class.
    /// </summary>
    public ContextThatFails()
    {
        /*
         * The constructor won't be used when de-serializing the object
         */
        localCache = new Hashtable();
    }
    
    /// <summary>
    /// Gets the local cache.
    /// </summary>
    /// <value>The local cache.</value>
    public Hashtable LocalCache
    {
        get { return localCache; }
    }
    
    /// <summary>
    /// Gets or sets the serialized value.
    /// </summary>
    /// <value>The serialized value.</value>
    public string SerializedValue
    {
        get { return serializedValue; }
        set { serializedValue = value; }
    }
}

When trying to access the hashtable after de-serializing the context the compiler will throw a NullReferenceException.

We solved this problem by verifying the value when accessing the property.
The solution looks like that:

/// <summary>
/// Context class
/// </summary>
[Serializable]
public class ContextThatSucceeds
{
    private string serializedValue;
    
    [NonSerialized]
    private Hashtable localCache;
    
    /// <summary>
    /// Gets the local cache.
    /// </summary>
    /// <value>The local cache.</value>
    public Hashtable LocalCache
    {
        get
        {
            /*
             * We have to initialize the hashtable on access.
             */
            if (localCache == null)
            localCache = new Hashtable();
            return localCache;
        }
    }
    
    /// <summary>
    /// Gets or sets the serialized value.
    /// </summary>
    /// <value>The serialized value.</value>
    public string SerializedValue
    {
        get { return serializedValue; }
        set { serializedValue = value; }
    }
}

The complete source that shows the failing and running example can be downloaded from here:
Initialize [NonSerialized] Members

But my question is still un-answered. Why is the local cache member not initialized after de-serializing although I initialized it within the constructor? If someone has an answer to this I would appreciate if he posts a comment.

So have fun
Cheers

Gerhard