Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A C# Option Group Control

0.00/5 (No votes)
1 Sep 2006 1  
This control simplifies the grouping of option buttons and processing the user�s selection.

Demo Screenshort

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:

Tollbox

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:

Property Window

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; 
    // Define the very lower-down child control 

    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; 
        
    // Check if there is enough room below for a new control 

    if (this.myControl.ClientRectangle.Contains(padding, m_ymax + 4 * padding))
    {
            m_x =padding; 
            m_y = m_ymax +    padding;
    }
    else
        // As no free space is available at the bottom

        // just place a new control in the up-left 

        m_y = 3 * padding + toppadding; m_x = 3 * padding;

    SelOption seloption; 
    // Get IDesignerHost service and wrap

    // creation of a SelOption in transaction 

    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 this is the first SelOption added

    // to an OptionGroup set its TabStop property to true. 

    // It causes the selection of this option when

    // the user presses TAB key and none SelOption is checked. 

    if (firstoption)
        seloption.TabStop = true; 
    seloption.Value = maxvalue + 1; 
    c.OnComponentChanging(this.myControl, null); 
    // If this is not the first option in an OptionGroup

    // rearrange the controls collection, so that 

    // the new SelOption will be the first item

    // of the controls collection. This imitates the Bring to Front method 

    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here