65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 选项组控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (2投票s)

2006 年 9 月 1 日

5分钟阅读

viewsIcon

63863

downloadIcon

716

该控件简化了选项按钮的分组和用户选择的处理。

Demo Screenshort

引言

该控件模仿了 Visual FoxPro 的 Option Group 控件。Option Group 是一个容器控件,它封装了一组互斥的选项按钮。每个选项按钮代表一个单独的选择。在任何时候只能选择一个选项按钮;选择另一个选项按钮会取消之前选中的按钮。当选项列表较短时,可以使用此控件代替组合框。

背景

当我将一些 Visual FoxPro 项目迁移到 .NET 时,我萌生了创建此控件的想法,因为我找不到任何具有类似功能的自定义 .NET 控件,所以我不得不自己创建一个。当然,使用几个单选按钮和 GroupBox 可以达到相同的效果,但这需要更多的设计和编码工作。

使用代码

在使用此控件之前,需要将其添加到工具箱。首先,需要获取包含该控件的 OptionGroup.DLL。可以使用演示项目中的 DLL,或者编译源代码项目。然后,创建一个 Windows 应用程序项目,并在工具箱的某个位置右键单击。选择要添加此控件的选项卡。最好使用“我的用户控件”或类似的选项卡,而不是“Windows 窗体”选项卡。继续选择“添加/删除项...”,导航到 OptionGroup.DLL 的位置,选择该 DLL,然后单击“确定”。之后,该控件应该就会出现在你的工具箱中。

Tollbox

现在,你可以从工具箱中选择 OptionGroup 控件并将其拖到窗体上。一个新的 OptionGroup 带有两个选项按钮。你可以通过调整属性窗口中的 ButtonCount 属性来更改选项按钮的数量,如下所示:

Property Window

或者,可以通过右键单击控件并从上下文菜单中选择“添加选项”菜单栏来添加新按钮。

选项按钮是 SelOption 控件的一个实例。你可以在设计器中选择一个选项按钮,并像处理任何控件一样在属性窗口中调整其属性。SelOption 控件派生自 RadioButton。它只有一个新添加的属性 Value,该属性指定与选项按钮关联的整数。

OptionGroupGroupBox 的一个子类。新属性、事件和方法如下所示:

属性

  • ButtonCount 指定与 OptionGroup 关联的选项按钮的数量。此属性在运行时是只读的,在设计时是读写的。
  • Value 属性指定分配给控件的当前值。当用户选择一个选项按钮时,它的值被设置为 OptionGroupValue 属性。当在属性窗口或代码中更改 Value 属性时,如果组中有一个具有相同值的选项按钮,则会选中该选项按钮。如果没有任何选项按钮具有指定的值,则选择“无”选项,并且不会抛出异常。
  • SelectedOption 属性返回当前选中的选项。如果没有选中选项按钮,则该属性返回 null

事件

  • ValueChanged 指示 Value 属性已更改。

方法

  • OnValueChanged 引发 ValueChanged 事件。

关注点

SelOption 控件

如上所述,此控件是 RadioButton 的子类。其代码非常直接且有良好的注释。此控件有一个设计器。设计器仅用于在 Windows Forms 设计器中阻止选项按钮移出其父 OptionGroup。这是通过覆盖设计器的 CanParentedTo 方法实现的,该方法始终返回 false

OptionGroup

此控件派生自 GroupBox。它还有一个设计器来控制其设计时行为。该控件实现了 ISupportInitialize

[ToolboxBitmap(typeof(OptionGroup))]
[DefaultProperty("Value"), DefaultEvent("ValueChanged"),
Designer(typeof(OptionGroupDesigner))]
public class OptionGroup : System.Windows.Forms.GroupBox, ISupportInitialize

我必须实现这个接口,因为 Value 属性只能在批量初始化结束时设置,此时所有选项按钮都已设置好并添加到 OptionGroupControls 集合中,并且 Value 属性的 setter 可以遍历 Controls 集合并识别将被初始选中的选项按钮。除此之外,控件本身的实现非常简单。但其设计器的实现是本次开发中最具挑战性的部分。因此,我将在此提供有关设计器实现方式的一些重要细节。该设计器派生自 ParentControlDesigner 类。当在设计时窗体上实例化一个选项按钮时,会调用 Initialize 方法。此方法获取与设计器关联的 OptionGroup 控件的引用。

public override void Initialize(IComponent component)
{
    base.Initialize(component);
    this.myControl = (OptionGroup) component;
}

当一个新的选项按钮被拖放到窗体上时,会调用设计器的 OnSetComponentDefaults 方法。此方法向控件添加两个选项按钮。

public override void OnSetComponentDefaults()
{
    this.OnAddOption(this, new EventArgs());
    this.OnAddOption(this, new EventArgs());
}

OnAddOption 方法创建一个新的选项按钮,设置其属性,并将其添加到 OptionGroupControls 集合中。

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();
}

设计器还将 OptionGroup 控件的 ButtonCount 属性重新引入为设计时可读写。我通过覆盖 PreFilterProperties 方法并向设计器添加同名属性来实现这一点。

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);
}

其余的设计器代码非常直接,不值得在此提及。

结论

希望这个控件能有所帮助。欢迎提出任何建议。

历史

我于 2004 年开发了第一个版本。在此文章中,我添加了注释并创建了该控件的 VB 版本。

© . All rights reserved.