C# 选项组控件






4.20/5 (2投票s)
2006 年 9 月 1 日
5分钟阅读

63863

716
该控件简化了选项按钮的分组和用户选择的处理。
引言
该控件模仿了 Visual FoxPro 的 Option Group 控件。Option Group 是一个容器控件,它封装了一组互斥的选项按钮。每个选项按钮代表一个单独的选择。在任何时候只能选择一个选项按钮;选择另一个选项按钮会取消之前选中的按钮。当选项列表较短时,可以使用此控件代替组合框。
背景
当我将一些 Visual FoxPro 项目迁移到 .NET 时,我萌生了创建此控件的想法,因为我找不到任何具有类似功能的自定义 .NET 控件,所以我不得不自己创建一个。当然,使用几个单选按钮和 GroupBox
可以达到相同的效果,但这需要更多的设计和编码工作。
使用代码
在使用此控件之前,需要将其添加到工具箱。首先,需要获取包含该控件的 OptionGroup.DLL。可以使用演示项目中的 DLL,或者编译源代码项目。然后,创建一个 Windows 应用程序项目,并在工具箱的某个位置右键单击。选择要添加此控件的选项卡。最好使用“我的用户控件”或类似的选项卡,而不是“Windows 窗体”选项卡。继续选择“添加/删除项...”,导航到 OptionGroup.DLL 的位置,选择该 DLL,然后单击“确定”。之后,该控件应该就会出现在你的工具箱中。
现在,你可以从工具箱中选择 OptionGroup
控件并将其拖到窗体上。一个新的 OptionGroup
带有两个选项按钮。你可以通过调整属性窗口中的 ButtonCount
属性来更改选项按钮的数量,如下所示:
或者,可以通过右键单击控件并从上下文菜单中选择“添加选项”菜单栏来添加新按钮。
选项按钮是 SelOption
控件的一个实例。你可以在设计器中选择一个选项按钮,并像处理任何控件一样在属性窗口中调整其属性。SelOption
控件派生自 RadioButton
。它只有一个新添加的属性 Value
,该属性指定与选项按钮关联的整数。
OptionGroup
是 GroupBox
的一个子类。新属性、事件和方法如下所示:
属性
ButtonCount
指定与OptionGroup
关联的选项按钮的数量。此属性在运行时是只读的,在设计时是读写的。Value
属性指定分配给控件的当前值。当用户选择一个选项按钮时,它的值被设置为OptionGroup
的Value
属性。当在属性窗口或代码中更改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
属性只能在批量初始化结束时设置,此时所有选项按钮都已设置好并添加到 OptionGroup
的 Controls
集合中,并且 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
方法创建一个新的选项按钮,设置其属性,并将其添加到 OptionGroup
的 Controls
集合中。
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 版本。