Introduction
I was developing a Windows Forms application during the last few months. In that application, I have multiple controls for performing a particular action. I need to show/hide, check/uncheck and enable/disable controls based on the action performed on the user control placed in the form. I was facing the problem that I need to set the state of all controls for particular action and if we forgot to update state of any control, the inconsistent state of controls will be left behind.
Background
I will describe this article with the help of an example. We have a user control that is composed in a form. We are taking GridView
in user control that will be displaying some records. The record can be updated by selecting grid row and perform Edit action either of the three ways:
- Edit button
- Edit toolbar button
- Edit context menu
These action controls should be enabled only when the record is selected. There are several ways to accomplish this activity, but every approach has some cost. We can expose events in user control for row selection/deselection and set state of action controls on the form. Here the important point is that you need to set state of all the controls for a particular action. But according to my framework, you do not need to set properties of user controls on the row selection and deselection.
How It Works?
The framework works in the following way:
- Define command constants in user control.
- Add commands in form constructor with the control references. If there is more than one control to perform an action, add command for every control.
UpdateCommand
on the row selection event in user control.
Using the Code
There are two main classes for state handling framework:
StateManager
UiObject
UIStates enum
UiObject
This object will wrap Windows Form controls. It is used to make the object symmetrical. This is a sealed class and its object cannot be created directly. A static
method CreateObject(object o)
is exposed that will take windows form control (Button
, ContextMenuItem
,ToolbarButton
,...) and return instance of UiObject
. This should be the only public
method in this class.
public static UIObject CreateObject(object o)
{
Control ctrl = o as Control;
if (ctrl== null)
{
Component cmp = o as Component;
if (cmp == null)
{
throw new Exception("Invalid control");
}
}
UIObject obj = new UIObject(o);
return obj;
}
The 'o
' parameter should be cast in Control
object. Here we are using win controls therefore we have cast parameter into base class of all the Controls/Components. If you are writing this framework for third party controls, use the base control class of those third party controls.
internal void SetCommandStatus(UIStates state)
{
switch (state)
{
case UIStates.Visible:
this.SetControlVisibility(true);
break;
case UIStates.InVisible:
this.SetControlVisibility(false);
break;
case UIStates.Enabled:
this.SetControlActiveState(true);
break;
case UIStates.Disabled:
this.SetControlActiveState(false);
break;
case UIStates.Checked:
this.SetControlCheckState(true);
break;
case UIStates.UnChecked:
this.SetControlCheckState(false);
break;
}
}
private void SetControlCheckState(bool flag)
{
if (this.mUiObject is RadioButton)
{
((RadioButton)this.mUiObject).Checked = flag;
}
else if (this.mUiObject is CheckBox)
{
((CheckBox)this.mUiObject).Checked = flag;
}
else if (this.mUiObject is MenuItem)
{
((MenuItem)this.mUiObject).Checked = flag;
}
else if (this.mUiObject is ToolStripMenuItem)
{
((ToolStripMenuItem)this.mUiObject).Checked = flag;
}
}
private void SetControlVisibility(bool flag)
{
if (this.mUiObject is ButtonBase)
{
((ButtonBase)this.mUiObject).Visible = flag;
}
else if (this.mUiObject is MenuItem)
{
((MenuItem)this.mUiObject).Visible = flag;
}
else if (this.mUiObject is ToolStripItem)
{
((ToolStripItem)this.mUiObject).Visible = flag;
}
}
private void SetControlActiveState(bool flag)
{
if (this.mUiObject is ButtonBase)
{
((ButtonBase)this.mUiObject).Enabled = flag;
}
else if (this.mUiObject is MenuItem)
{
((MenuItem)this.mUiObject).Enabled = flag;
}
else if (this.mUiObject is ToolStripItem)
{
((ToolStripItem)this.mUiObject).Enabled = flag;
}
}
The SetCommandStatus
method will set the state of the User interface controls. The access specifier of this method is 'internal
' because it is intended to call from 'Statemanager
' class only. The UIStates
parameter will let this method set the object state. The other methods are user as helper methods.
StateManager
It contains a dictionary of string
commands and UIObjects
lists. This object is responsible for the following:
- Subscribe action commands and winform controls.
- Set the status of subscribed commands.
public void AddCommand(string command, UIObject uiObject)
{
List<uiobject> uiObjects = null;
if (this.mUiObjectsMapping.ContainsKey(command))
{
uiObjects = this.mUiObjectsMapping[command];
uiObjects.Add(uiObject);
}
else
{
uiObjects = new List<uiobject>();
uiObjects.Add(uiObject);
this.mUiObjectsMapping.Add(command, uiObjects);
}
}
This method will map action command against UIObject
list.
UIStates Enum
This enum
will contain winform control states:
Visible
: The control will be displayed on the form. InVisible
: The control will be hidden on the form. Enabled
: The control will be visible and active for action. Disabled
: The control will be visible and inactive for action. Checked
: The control will be checked. UnChecked
: The control will be un-checked.
Sample Client Code
I have implemented this framework in the sample application. This TestForm
will compose TestUC
. There is a button for record updation. We can execute edit command in by clicking on Edit button, Edit context menu or from toolbar button. The state handling of these three controls can be controlled by this framework. You do not need to write enable/disable code for all these controls again and again. Just register commands and call UpdateCommandStatus
on row selection event.
You need to perform the following actions for plugging this code into your application:
- Create instance level object of
StateManger
in UserControl
. - Write
AddComand(String cmd, UIObject o)
in UserControl
for subscribing commands. - Add
UpdateCommandStatus()
in user control and write here when to enable / disable controls. - Call
UpdateCommandStatus()
on appropriate event, in my example I have called it on SelectionChangedEvent
.You can call this method either from user control or from WinForm or both, depends on the requirement. - Register commands in WinForm constructor. As discussed earlier for multiple controls, we need to register commands multiple times with each control reference.
this.testUC1.AddCommand(TestUC.COMMAND_ENABLE_BUTTON,
UIObject.CreateObject(this.btnEdit));
this.testUC1.AddCommand(TestUC.COMMAND_ENABLE_BUTTON,
UIObject.CreateObject(this.toolStripBtnEdit));
this.testUC1.AddCommand(TestUC.COMMAND_ENABLE_BUTTON,
UIObject.CreateObject(this.editToolStripMenuItem));
I have added Clear Selection button for demo purposes. The controls will be enabled on row selection and disabled when there is no row selected.
Points of Interest
This approach involves one time cost only. The maintenance and implementation cost of this framework is very low and you will learn how to synchronize controls state in a concentric fashion.
History
- 9th October, 2011: Initial version
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.