Introduction
Recently, I have been working on a project which requires that a large number of components be enabled/disabled based on what is open at the time. I wanted some options to be always enabled, some to be enabled only when a solution was open, and some enabled only when a child form was opened. Child forms could only be opened when the solution was open.
I experimented with various methods to control the updating, including using the Idle event to update each control based on flags or by adding attributes to each control and then using reflection to change them based on some events happening.
Both methods were really cumbersome and meant that whenever a new component was added, I had to edit the source to add the necessary UI handling. I couldn't believe that something so basic needed to have such a fragile solution.
It eventually dawned on me that there was a way in which this functionality could be added easily, and maintained through the property designer, rather than by manually editing the code. By using an Extender Provider, I was able to create a UI manager that managed the updating for me with minimum intervention.
Background
An excellent place to learn about Extender Providers is James Johnson's article, Getting to know IExtenderProvider.
Using the Code
The ExtenderProvider
class that we are using here is simply a user control that implements the IExtenderProvider
interface. For the purposes of the sample project, our code is going to enable/disable menu and toolbar items based on a solution being opened or closed, and child forms being opened/closed.
We start by providing an enumeration that indicates the level at which the relevant menu/toolbar item will be enabled:
public enum EnableType
{
Always,
Solution,
ChildForm,
}
Menu items that have the type EnableType.Always
set will always be enabled. EnableType.Solution
, means that the menu item will only be enabled when the solution is open, and EnableType.ChildForm
will only be enabled when the solution is open and there is at least one child form open.
We are going to provide the provider properties at the start of the UI manager definition like this:
[ProvideProperty("AutoEnable", typeof(Component))]
public class UIManager : UserControl, IExtenderProvider
{
As both toolbar buttons and menu items inherit from System.ComponentModel.Component
, we can provide one property to control both.
The IExtenderProvider
provides the CanExtend
method which we use to let the forms designer know whether or not an item can be extended. This method passes in an object that we can interrogate to see if it matches the type in the ProvideProperty
.
public bool CanExtend(object extendee)
{
if (object is Component)
return true;
return false;
}
Because we are going to have multiple items hooked up to this extender, we will need to use a Hashtable
.
private Hashtable _controls = new Hashtable();
Now, we are going to implement the methods to actually store the extended values. As our property is called AutoEnable
, we need the methods SetAutoEnable
and GetAutoEnable
:
public EnableType GetAutoEnable(Component value)
{
object ctrl = _controls[value];
if (ctrl == null)
return EnableType.Always;
return (EnableType)ctrl;
}
public void SetAutoEnable(Component ctrl, EnableType value)
{
_controls[ctrl] = value;
}
In GetAutoEnable
, we check to see if we have added this menu or toolbar item into the hashtable
already. If we haven't, we will return the default value where the item will always be enabled.
The last thing that we need to do is provide a method that will allow the forms that will host this object to actually enable/disable items.
public void Enable(EnableType type)
{
foreach (DictionaryEntry de in _controls)
{
EnableType tp = (EnableType)de.Value;
ToolBarButton button = de.Key as ToolBarButton;
if (button != null)
{
if (tp == EnableType.Always)
Enable(button, true);
else if (type == EnableType.Solution && tp == EnableType.Solution)
Enable(button, true);
else if (type == EnableType.ChildForm && (
tp == EnableType.Solution || tp == EnableType.ChildForm)
)
Enable(button, true);
else
Enable(button, false);
}
else
{
MenuItem btn = de.Key as MenuItem;
if (btn != null)
{
if (tp == EnableType.Always)
Enable(btn, true);
else if (type == EnableType.Solution && tp == EnableType.Solution)
Enable(btn, true);
else if (type == EnableType.ChildForm && (
tp == EnableType.Solution || tp == EnableType.ChildForm)
)
Enable(btn, true);
else
Enable(btn, false);
}
}
}
}
private void Enable(ToolBarButton item, bool value)
{
item.Enabled = value;
}
private void Enable(MenuItem item, bool value)
{
item.Enabled = value;
}
Now, compile the component and it is ready to be dropped onto a form.
When it is placed on a form, all of the controls that have descended from Component
will automatically have an extended property called AutoEnable
on UIManager1
with a default value of Always
. If you rename the UIManager
control to uiManager1
, then the property name changes to AutoEnable on uiManager1
.
AutoEnable property set to default value.
We can now go through the toolbars/menu items and change the AutoEnable
value to the appropriate value.
If we run the project at this stage, nothing is disabled. We still need to hook up the Enable
method to respond to the various events.
In the Load
event, we call:
uiManager1.Enable(EnableType.Always);
In the method to open the solution, we need to call:
uiManager1.Enable(EnableType.Solution);
In the method to close the solution, just call:
uiManager1.Enable(EnableType.Always);
In the method that opens the child forms, we need to call:
uiManager1.Enable(EnableType.ChildForm);
When the last child form has closed, just call:
uiManager1.Enable(EnableType.Solution);
Taking It Further
While we have only added one extended property to this control, it would be very easy to add extended properties to display text on the scrollbar based on the currently selected menu or toolbar item. The beauty of the extender provider model is that it is very flexible and very simple to use.
Conclusion
Using IExtenderProvider
makes for a very easy way to control properties on a form. It is simple to implement, and provides a convenient way to add common functionality to a wide variety of controls.
History
- 18th August 2006: Initial post
- 20th August 2005: Minor amendment to text