Introduction
This control imitates the Visual Foxpro Option Group control. The Option Group is a container control that encapsulates a group of option buttons that are mutually exclusive. Each option button represents a single choice. Only one option button can be selected at any time; selecting a different option button will deselect the previously selected one. This control can be used instead of the combobox when there is a short list of options.
Background
I came to an idea to create this control when I worked on the migration of a number of Visual Foxpro projects into .NET and I could not find any custom .NET control with the similar functionality, so I had to create one by myself. Surely, using several radio buttons and the GroupBox
can produce the same result, but this requires more work, both in designing and coding.
Using the code
Before using this control, you need to add it to the toolbox. First, you need to obtain OptionGroup.DLL that contains the control. You can use the DLL included in the demo project, or compile the source project. Then, create a Windows application project and right-click somewhere in your toolbox. Select a tab you want to add this control to. It is better to use the "My User Controls" tab or something like that instead of the Windows Forms tab. Proceed and select "Add/Remove Items...", navigate to the location of the OptionGroup.DLL, select the DLL, and click OK. After that, the control should be in your toolbox:
Now, you can select the OptionGroup
control in the toolbox and drag it on a form. A new OptionGroup
comes with two option buttons. You can change the number of option buttons by adjusting the ButtonCount
property in the Properties window, as shown below:
Alternatively, a new button can be added by right-clicking on the control and choosing the �Add Option� menu bar from the context menu.
An option button is an instance of the SelOption
control. You can select an option button in the designer and adjust its property in the Properties windows like for any control. The SelOption
control derives from the RadioButton
. It has only one newly added property, Value
, which specifies an integer number associated with the option button.
The OptionGroup
is a subclass of the GroupBox
. The new properties, events, and methods are listed below:
Properties
- The
ButtonCount
specifies the number of option buttons associated with an OptionGroup
. This property is read-only at runtime, and read-write at design time.
- The
Value
property specifies the current value assigned to the control. When the user selects an option button, its value is set to the Value
property of the OptionGroup
. When changing the Value
property in the Properties window or within the code, and if there is an option button in the group that has the same value, this option button is selected. If no option button has the specified value, the None option is selected and no exception is thrown.
- The
SelectedOption
property returns the currently selected option. If no option button is selected, the property returns null
.
Events
- The
ValueChanged
indicates that the Value
property has been changed.
Methods
- The
OnValueChanged
raises the ValueChanged
event.
Points of interest
The SelOption control
As mentioned above, this control is a subclass of the RadioButton
. Its code is very straightforward and well commented. This control has a designer. The designer is used just to prevent moving an option button outside of its parent OptionGroup
in the Windows Forms designer. This was done by overriding the designer�s method CanParentedTo
that always returns false
.
The OptionGroup
This control derives from the GroupBox
. It also has a designer to control its designer-time behavior. The control implements ISupportInitialize
.
[ToolboxBitmap(typeof(OptionGroup))]
[DefaultProperty("Value"), DefaultEvent("ValueChanged"),
Designer(typeof(OptionGroupDesigner))]
public class OptionGroup : System.Windows.Forms.GroupBox, ISupportInitialize
I had to implement this interface because the Value
property can be set properly only at the end of the batch initialization when all options buttons are set up and added to the Controls
collection of an OptionGroup
and the setter of the Value
property can iterate over the Controls
collection and identify an option button which will be initially selected. Apart from that, the implementation of the control itself is very simple. But the implementation of its designer was the most challenging part of this development. So, I will provide here some important details of how the designer was implemented. The designer derives from the ParentControlDesigner
class. The Initialize
method is called when an option button is instantiated on a form at design-time. This method retrieves the reference to the OptionGroup
control the designer is associated with.
public override void Initialize(IComponent component)
{
base.Initialize(component);
this.myControl = (OptionGroup) component;
}
When a new option button is dragged and dropped on a form, the designer�s method OnSetComponentDefaults
is called. This method adds two option buttons to the control.
public override void OnSetComponentDefaults()
{
this.OnAddOption(this, new EventArgs());
this.OnAddOption(this, new EventArgs());
}
The OnAddOption
method creates a new option button, sets up its property, and adds it to the Controls
collection of the OptionGroup
.
private void OnAddOption(object sender, EventArgs e) {
int m_x, m_y, m_ymax=0;
SelOption selopt;
int maxvalue = 0;
bool firstoption = true;
int toppadding = myControl.GetFontHeight() + padding;
foreach (Control cnt in this.myControl.Controls)
{
if (cnt is SelOption)
{
m_ymax = Math.Max(m_ymax, cnt.Top + cnt.Height);
selopt = (SelOption)cnt;
maxvalue = Math.Max(selopt.Value,maxvalue);
firstoption = false;
}
}
if (m_ymax == 0)
m_ymax = toppadding;
if (this.myControl.ClientRectangle.Contains(padding, m_ymax + 4 * padding))
{
m_x =padding;
m_y = m_ymax + padding;
}
else
m_y = 3 * padding + toppadding; m_x = 3 * padding;
SelOption seloption;
IDesignerHost h = (IDesignerHost) this.GetService(typeof(IDesignerHost));
IComponentChangeService c = (IComponentChangeService)
this.GetService(typeof(IComponentChangeService));
DesignerTransaction dt;
dt = h.CreateTransaction("Add Option");
seloption = (SelOption) h.CreateComponent(typeof(SelOption));
int i3= this.myControl.ButtonCount + 1;
seloption.Text = "Option" + i3.ToString();
seloption.Top = m_y;
seloption.Left = m_x;
if (firstoption)
seloption.TabStop = true;
seloption.Value = maxvalue + 1;
c.OnComponentChanging(this.myControl, null);
if (this.myControl.Controls.Count == 0) {
int count = this.myControl.Controls.Count;
Control[] controls = new Control[count + 1];
controls[0] = seloption;
this.myControl.Controls.CopyTo(controls,1);
this.myControl.Controls.Clear();
this.myControl.Controls.AddRange(controls);
int i1;
count = this.myControl.Controls.Count - 1;
for (i1 = 0; i1 = this.myControl.Controls.Count - 1; i1++)
this.myControl.Controls[i1].TabIndex = count - i1;
}
else
this.myControl.Controls.Add(seloption);
c.OnComponentChanged(this.myControl, null, null, null);
dt.Commit();
}
The designer also reintroduces the ButtonCount
property of the OptionGroup
control as read-write at design time. I accomplished that by overriding the PreFilterProperties
method and adding a property with the same name to the designer.
protected override void PreFilterProperties(IDictionary properties)
{
base.PreFilterProperties(properties);
Attribute[] attributeArray1 = new Attribute[] { CategoryAttribute.Appearance };
properties["ButtonCount"] = TypeDescriptor.CreateProperty(
typeof(OptionGroupDesigner), "ButtonCount", typeof(int), attributeArray1);
}
The rest of the designer�s code is absolutely straightforward, and is not worth mentioning here.
Conclusion
I hope this control can be useful. Feel free to send any suggestions.
History
I developed the first version in 2004. For this article, I added comments and created a VB version of the control.