C# 在 WinForms 中数据绑定一组单选按钮的最佳方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/675137/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 12:58:11  来源:igfitidea点击:

Best way to databind a group of radiobuttons in WinForms

c#winformsdata-bindingradio-button

提问by Geir-Tore Lindsve

I'm currently working on databinding some of my existing Windows Forms, and I've ran into an issue figuring out the proper way of databinding a group of radiobutton controls within a group box.

我目前正在对我现有的一些 Windows 窗体进行数据绑定,并且我遇到了一个问题,以确定在组框中对一组单选按钮控件进行数据绑定的正确方法。

My business object has an integer property which I want to databind against 4 radiobuttons (where each of them represents the values 0 - 3).

我的业务对象有一个整数属性,我想针对 4 个单选按钮进行数据绑定(其中每个单选按钮代表 0 - 3 的值)。

I'm currently binding against a presenter object which works as the binder between the form and the business object, and the way I've done it now is to have 4 separate properties which each binds against each of these values (I do use INotifyPropertyChanged, but not including that here):

我目前正在绑定一个 Presenter 对象,该对象充当表单和业务对象之间的绑定器,我现在完成的方法是拥有 4 个单独的属性,每个属性都绑定到这些值中的每一个(我确实使用 INotifyPropertyChanged ,但不包括此处):

Private int _propValue;

Public bool PropIsValue0 
{ 
  get { return _propValue == 0; }
  set
  {
    if (value) 
      _propValue = 0;
  }
}

Public bool PropIsValue1 { // As above, but with value == 1 }
Public bool PropIsValue2 { // As above, but with value == 2 }
Public bool PropIsValue3 { // As above, but with value == 3 }

And I then bind each of the radiobuttons to their respective property as above.

然后我将每个单选按钮绑定到它们各自的属性,如上所述。

This does not seem right to me, so any advice are highly appreciated.

这对我来说似乎不正确,因此非常感谢任何建议。

采纳答案by Ohad Schneider

Following is a generic RadioGroupBox implementation in the spirit of ArielBH's suggestion (some code borrowed from Jay Andrew Allen's RadioPanel). Just add RadioButtons to it, set their tags to different integers and bind to the 'Selected' property.

以下是本着 ArielBH 建议(从 Jay Andrew Allen 的RadioPanel借用的一些代码)的精神的通用 RadioGroupBox 实现。只需将 RadioButtons 添加到其中,将它们的标签设置为不同的整数并绑定到“Selected”属性。

public class RadioGroupBox : GroupBox
{
    public event EventHandler SelectedChanged = delegate { };

    int _selected;
    public int Selected
    {
        get
        {
            return _selected;
        }
        set
        {
            int val = 0;
            var radioButton = this.Controls.OfType<RadioButton>()
                .FirstOrDefault(radio =>
                    radio.Tag != null 
                   && int.TryParse(radio.Tag.ToString(), out val) && val == value);

            if (radioButton != null)
            {
                radioButton.Checked = true;
                _selected = val;
            }
        }
    }

    protected override void OnControlAdded(ControlEventArgs e)
    {
        base.OnControlAdded(e);

        var radioButton = e.Control as RadioButton;
        if (radioButton != null)
            radioButton.CheckedChanged += radioButton_CheckedChanged;
    }

    void radioButton_CheckedChanged(object sender, EventArgs e)
    {
        var radio = (RadioButton)sender;
        int val = 0;
        if (radio.Checked && radio.Tag != null 
             && int.TryParse(radio.Tag.ToString(), out val))
        {
            _selected = val;
            SelectedChanged(this, new EventArgs());
        }
    }
}

Note that you can't bind to the 'Selected' property via the designer due to initialization order problems in InitializeComponent (the binding is performed before the radio buttons are initialized, so their tag is null in the first assignment). So just bind yourself like so:

请注意,由于 InitializeComponent 中的初始化顺序问题,您无法通过设计器绑定到 'Selected' 属性(绑定在初始化单选按钮之前执行,因此它们的标记在第一次赋值中为空)。所以就这样绑定自己:

    public Form1()
    {
        InitializeComponent();
        //Assuming selected1 and selected2 are defined as integer application settings
        radioGroup1.DataBindings.Add("Selected", Properties.Settings.Default, "selected1");
        radioGroup2.DataBindings.Add("Selected", Properties.Settings.Default, "selected2");
    }

回答by ArielBH

I think I would use my own GroupBox. I would bind the CustomGroupBox to your Model and set the correct RadioButton (using the tag or name properties) from the binded value.

我想我会使用我自己的 GroupBox。我会将 CustomGroupBox 绑定到您的模型,并从绑定值设置正确的 RadioButton(使用标签或名称属性)。

回答by Michelangelo Partipilo

This is my approach for binding a list of radio buttons to an enum.

这是我将单选按钮列表绑定到枚举的方法。

Using the Enum as a string in the button's Tag property, I use the Binding.Formatand Binding.Parseevent to decide which button should be checked.

在按钮的 Tag 属性中使用 Enum 作为字符串,我使用Binding.FormatBinding.Parse事件来决定应该检查哪个按钮。

public enum OptionEnum
{
   Option1 = 0,
   Option2
}

OptionEnum _rbEnum = OptionEnum.Option1;
OptionEnum PropertyRBEnum
{
    get { return _rbEnum; }
    set
    {
        _rbEnum = value;
        RaisePropertyChanged("PropertyRBEnum");
    }
}

public static void FormatSelectedEnum<T>(object sender, ConvertEventArgs args) where T : struct
{
    Binding binding = (sender as Binding);
    if (binding == null) return;

    Control button = binding.Control;

    if (button == null || args.DesiredType != typeof(Boolean)) return;

    T value = (T)args.Value;
    T controlValue;

    if (Enum.TryParse(button.Tag.ToString(), out controlValue))
    {
        args.Value = value.Equals(controlValue);
    }
    else
    {
        Exception ex = new Exception("String not found in Enum");
        ex.Data.Add("Tag", button.Tag);

        throw ex;
    }
}

public static void ParseSelectedEnum<T>(object sender, ConvertEventArgs args) where T : struct
{
    Binding binding = (sender as Binding);
    if (binding == null) return;

    Control button = binding.Control;
    bool value = (bool)args.Value;

    if (button == null || value != true) return;

    T controlValue;

    if (Enum.TryParse(button.Tag.ToString(), out controlValue))
    {
        args.Value = controlValue;
    }
    else
    {
        Exception ex = new Exception("String not found in Enum");
        ex.Data.Add("Tag", button.Tag);

        throw ex;
    }
}

Then setup your data binding like this:

然后像这样设置你的数据绑定:

radioButton1.Tag = "Option1";
radioButton2.Tag = "Option2";

foreach (RadioButtonUx rb in new RadioButtonUx[] { radioButton1, radioButton2 })
{
    Binding b = new Binding("Checked", this, "PropertyRBEnum");
    b.Format += FormatSelectedRadioButton<OptionEnum>;
    b.Parse += ParseSelectedRadioButton<OptionEnum>;

    rb.DataBindings.Add(b);
}

回答by user1587368

I started to resolve the same problematic.

我开始解决同样的问题。

I used a RadioButtonBinding class which encapsulates all radiobuttons about an enum in the data source.

我使用了一个 RadioButtonBinding 类,它封装了数据源中关于枚举的所有单选按钮。

This following class keeps all radio buttons in a list and makes a lookup for the enum :

下面这个类将所有单选按钮保存在一个列表中,并查找 enum :

class RadioButtonBinding : ILookup<System.Enum, System.Windows.Forms.RadioButton>
{
    private Type enumType;
    private List<System.Windows.Forms.RadioButton> radioButtons;
    private System.Windows.Forms.BindingSource bindingSource;
    private string propertyName;

    public RadioButtonBinding(Type myEnum, System.Windows.Forms.BindingSource bs, string propertyName)
    {
        this.enumType = myEnum;
        this.radioButtons = new List<System.Windows.Forms.RadioButton>();
        foreach (string name in System.Enum.GetNames(this.enumType))
        {
            System.Windows.Forms.RadioButton rb = new System.Windows.Forms.RadioButton();
            rb.Text = name;
            this.radioButtons.Add(rb);
            rb.CheckedChanged += new EventHandler(rb_CheckedChanged);
        }
        this.bindingSource = bs;
        this.propertyName = propertyName;
        this.bindingSource.DataSourceChanged += new EventHandler(bindingSource_DataSourceChanged);
    }

    void bindingSource_DataSourceChanged(object sender, EventArgs e)
    {
        object obj = this.bindingSource.Current;
        System.Enum item = obj.GetType().GetProperty(propertyName).GetValue(obj, new object[] { }) as System.Enum;
        foreach (System.Enum value in System.Enum.GetValues(this.enumType))
        {
            if (this.Contains(value))
            {
                System.Windows.Forms.RadioButton rb = this[value].First();
                if (value.Equals(item))
                {
                    rb.Checked = true;
                }
                else
                {
                    rb.Checked = false;
                }
            }
        }
    }

    void rb_CheckedChanged(object sender, EventArgs e)
    {
        System.Windows.Forms.RadioButton rb = sender as System.Windows.Forms.RadioButton;
        System.Enum val = null;
        try
        {
            val = System.Enum.Parse(this.enumType, rb.Text) as System.Enum;
        }
        catch(Exception ex)
        {
            // cannot occurred if code is safe
            System.Windows.Forms.MessageBox.Show("No enum value for this radio button : " + ex.ToString());
        }
        object obj = this.bindingSource.Current;
        obj.GetType().GetProperty(propertyName).SetValue(obj, val, new object[] { });
        this.bindingSource.CurrencyManager.Refresh();
    }

    public int Count
    {
        get
        {
            return System.Enum.GetNames(this.enumType).Count();
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.radioButtons.GetEnumerator();
    }

    public bool Contains(Enum key)
    {
        return System.Enum.GetNames(this.enumType).Contains(key.ToString());
    }

    public IEnumerable<System.Windows.Forms.RadioButton> this[Enum key]
    {
        get
        {
            return this.radioButtons.FindAll(a => { return a.Text == key.ToString(); });
        }
    }

    IEnumerator<IGrouping<Enum, System.Windows.Forms.RadioButton>> IEnumerable<IGrouping<Enum, System.Windows.Forms.RadioButton>>.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public void AddControlsIntoGroupBox(System.Windows.Forms.GroupBox gb)
    {
        System.Windows.Forms.FlowLayoutPanel panel = new System.Windows.Forms.FlowLayoutPanel();
        panel.Dock = System.Windows.Forms.DockStyle.Fill;
        panel.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
        foreach (System.Windows.Forms.RadioButton rb in this.radioButtons)
        {
            panel.Controls.Add(rb);
        }
        gb.Controls.Add(panel);
    }
}

You are using the class into a form by adding that code in the constructor of the form:

您通过在表单的构造函数中添加该代码来将该类使用到表单中:

    public PageView()
    {
        InitializeComponent();
        RadioButtonBinding rbWidth = new RadioButtonBinding(typeof(Library.EnumConstraint), this.pageBindingSource, "ConstraintWidth");
        rbWidth.AddControlsIntoGroupBox(this.groupBox1);
        RadioButtonBinding rbHeight = new RadioButtonBinding(typeof(Library.EnumConstraint), this.pageBindingSource, "ConstraintHeight");
        rbHeight.AddControlsIntoGroupBox(this.groupBox3);
        this.pageBindingSource.CurrentItemChanged += new EventHandler(pageBindingSource_CurrentItemChanged);
    }

回答by Paul - SGS PfINDE

I know this post is old but in my search for an answer for this same problem I came across this post and it didn't solve my problem. I ended up having a lightbulb go off randomly just a minute ago and wanted to share my solution.

我知道这篇文章很旧,但在我寻找同样问题的答案时,我遇到了这篇文章,但它没有解决我的问题。就在一分钟前,我最终让一个灯泡随机熄灭,并想分享我的解决方案。

I have three radio buttons in a group box. I'm using a List<> of a custom class object as the data source.

我在一个组框中有三个单选按钮。我使用自定义类对象的 List<> 作为数据源。

Class Object:

类对象:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BAL
{
class ProductItem
{

    // Global Variable to store the value of which radio button should be checked
    private int glbTaxStatus;
    // Public variable to set initial value passed from 
    // database query and get value to save to database
    public int TaxStatus
    {
        get { return glbTaxStatus; }
        set { glbTaxStatus = value; }
    }

    // Get/Set for 1st Radio button
    public bool Resale
    {
        // If the Global Variable = 1 return true, else return false
        get
        {
            if (glbTaxStatus.Equals(1))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        // If the value being passed in = 1 set the Global Variable = 1, else do nothing
        set
        {
            if (value.Equals(true))
            {
                glbTaxStatus = 1;
            }
        }
    }

    // Get/Set for 2nd Radio button
    public bool NeverTax
    {
        // If the Global Variable = 2 return true, else return false
        get
        {
            if (glbTaxStatus.Equals(2))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        // If the value being passed in = 2 set the Global Variable = 2, else do nothing
        set
        {
            if (value.Equals(true))
            {
                glbTaxStatus = 2;
            }
        }
    }

    // Get/Set for 3rd Radio button
    public bool AlwaysTax
    {
        // If the Global Variable = 3 return true, else return false
        get
        {
            if (glbTaxStatus.Equals(3))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        // If the value being passed in = 3 set the Global Variable = 3, else do nothing
        set
        {
            if (value.Equals(true))
            {
                glbTaxStatus = 3;
            }
        }
    }

// More code ...

Three seperate public variables with get/set accessing the same one global variable.

三个单独的公共变量,get/set 访问同一个全局变量。

In the code behind, I have a function called during the Page_Load() setting all the controls databindings. For each radio button I add its own databinging.

在后面的代码中,我在 Page_Load() 设置所有控件数据绑定期间调用了一个函数。对于每个单选按钮,我添加了自己的数据绑定。

radResale.DataBindings.Add("Checked", glbProductList, "Resale", true, DataSourceUpdateMode.OnPropertyChanged, false);
radNeverTax.DataBindings.Add("Checked", glbProductList, "NeverTax", true, DataSourceUpdateMode.OnPropertyChanged, false);
radAlwaysTax.DataBindings.Add("Checked", glbProductList, "Always", true, DataSourceUpdateMode.OnPropertyChanged, false);

I hope this helps someone!!

我希望这可以帮助别人!!

回答by paultechguy

Set the tag name of your radio buttons to something that represents the value.

将单选按钮的标签名称设置为代表值的名称。

Create a string setting, for example, OptionDuplicateFiles, and give it the default value of the tag name for your default radio button.

创建一个字符串设置,例如 OptionDuplicateFiles,并为其指定默认单选按钮的标签名称的默认值。

To save your checked radio button:

要保存选中的单选按钮:

Settings.Default.OptionDuplicateFiles = gbxDuplicateFiles.Controls
   .OfType<RadioButton>()
   .Where(b => b.Checked)
   .Select(b => b.Tag)
   .First()
   .ToString();

To load your checked radio button:

要加载选中的单选按钮:

(gbxDuplicateFiles.Controls
   .OfType<RadioButton>()
   .Where(b => b.Tag.ToString() == Settings.Default.OptionDuplicateFiles)
   .First())
   .Checked = true;

Tada!

多田!

回答by Jan Hoogma

I liked the idea of a RadioButtonGroupBox but I decided to create a version that is self supporting. There is no reason to add value to Tag attribute or to introduce new value attributes. Any assigned radio button is still a member of the RadioButtonGroupBox and the sequence of radiobuttons is defined during development. Soo, I modified the code. Now I can get and set the selected radiobutton by index position, By Control Name and by Text. BTW Text is only useable if your asssigned Text is different for each radiobutton.

我喜欢 RadioButtonGroupBox 的想法,但我决定创建一个自支持的版本。没有理由为 Tag 属性添加值或引入新的值属性。任何分配的单选按钮仍然是 RadioButtonGroupBox 的成员,并且单选按钮的序列是在开发过程中定义的。Soo,我修改了代码。现在我可以按索引位置、按控件名称和按文本获取和设置选定的单选按钮。顺便说一句,只有当您为每个单选按钮分配的文本不同时,文本才可用。

public class RadioButtonGroupBox : GroupBox
{
    public event EventHandler SelectedChanged = delegate { };

    int _nIndexPosCheckRadioButton = -1;
    int _selected;
    public int Selected
    {
        get
        {
            return _selected;
        }
    }


    public int CheckedRadioButtonIndexPos
    {
        set
        {
            int nPosInList = -1;
            foreach (RadioButton item in this.Controls.OfType<RadioButton>())
            {
                // There are RadioButtonItems in the list...
                nPosInList++;

                // Set the RB that should be checked
                if (nPosInList == value)
                {
                    item.Checked = true;
                    // We can stop with the loop
                    break;
                }
            }
            _nIndexPosCheckRadioButton = nPosInList;
        }
        get
        {
            int nPosInList = -1;
            int nPosCheckeItemInList = -1;

            foreach (RadioButton item in this.Controls.OfType<RadioButton>())
            {
                // There are RadioButtonItems in the list...
                nPosInList++;

                // Find the RB that is checked
                if (item.Checked)
                {
                    nPosCheckeItemInList = nPosInList;
                    // We can stop with the loop
                    break;
                }
            }
            _nIndexPosCheckRadioButton = nPosCheckeItemInList;
            return _nIndexPosCheckRadioButton;
        }
    }

    public string CheckedRadioButtonByText
    {
        set
        {
            int nPosInList = -1;
            foreach (RadioButton item in this.Controls.OfType<RadioButton>())
            {
                // There are RadioButtonItems in the list...
                nPosInList++;

                // Set the RB that should be checked
                if (item.Text == value)
                {
                    item.Checked = true;
                    // We can stop with the loop
                    break;
                }
            }
            _nIndexPosCheckRadioButton = nPosInList;
        }
        get
        {
            string cByTextValue = "__UNDEFINED__";
            int nPosInList = -1;
            int nPosCheckeItemInList = -1;

            foreach (RadioButton item in this.Controls.OfType<RadioButton>())
            {
                // There are RadioButtonItems in the list...
                nPosInList++;

                // Find the RB that is checked
                if (item.Checked)
                {
                    cByTextValue = item.Text;
                    nPosCheckeItemInList = nPosInList;
                    // We can stop with the loop
                    break;
                }
            }
            _nIndexPosCheckRadioButton = nPosCheckeItemInList;
            return cByTextValue;
        }
    }

    public string CheckedRadioButtonByName
    {
        set
        {
            int nPosInList = -1;
            foreach (RadioButton item in this.Controls.OfType<RadioButton>())
            {
                // There are RadioButtonItems in the list...
                nPosInList++;

                // Set the RB that should be checked
                if (item.Name == value)
                {
                    item.Checked = true;
                    // We can stop with the loop
                    break;
                }
            }
            _nIndexPosCheckRadioButton = nPosInList;
        }
        get
        {
            String cByNameValue = "__UNDEFINED__";
            int nPosInList = -1;
            int nPosCheckeItemInList = -1;

            foreach (RadioButton item in this.Controls.OfType<RadioButton>())
            {
                // There are RadioButtonItems in the list...
                nPosInList++;

                // Find the RB that is checked
                if (item.Checked)
                {
                    cByNameValue = item.Name;
                    nPosCheckeItemInList = nPosInList;
                    // We can stop with the loop
                    break;
                }
            }
            _nIndexPosCheckRadioButton = nPosCheckeItemInList;
            return cByNameValue;
        }
    }


    protected override void OnControlAdded(ControlEventArgs e)
    {
        base.OnControlAdded(e);

        var radioButton = e.Control as RadioButton;
        if (radioButton != null)
            radioButton.CheckedChanged += radioButton_CheckedChanged;
    }


    void radioButton_CheckedChanged(object sender, EventArgs e)
    {
        _selected = CheckedRadioButtonIndexPos;
        SelectedChanged(this, new EventArgs());
    }

}

回答by SiuFay

My approach is to put each radio button into its own panel before binding them to a boolean property:

我的方法是在将每个单选按钮绑定到布尔属性之前将它们放入自己的面板中:

    public static Binding Bind<TObject>(this RadioButton control, object dataSource, string dataMember)
    {
        // Put the radio button into its own panel
        Panel panel = new Panel();
        control.Parent.Controls.Add(panel);
        panel.Location = control.Location;
        panel.Size = control.Size;
        panel.Controls.Add(control);
        control.Location = new Point(0, 0);

        // Do the actual data binding
        return control.DataBindings.Add("Checked", dataSource, dataMember);
    }

回答by Creative

I would like to make an observation about the code block that might be helpful to people reading these posts. The following code may not always work as expected due to it's structure.

我想对可能对阅读这些帖子的人有所帮助的代码块进行观察。由于其结构,以下代码可能并不总是按预期工作。

try
    {
      val = System.Enum.Parse(this.enumType, rb.Text) as System.Enum;
    }
    catch(Exception ex)
    {
      // cannot occurred if code is safe
      System.Windows.Forms.MessageBox.Show("No enum value for this radio button : " + ex.ToString());
    }
    object obj = this.bindingSource.Current;
    obj.GetType().GetProperty(propertyName).SetValue(obj, val, new object[] {
    }
  );
  this.bindingSource.CurrencyManager.Refresh();

If an error occurs in the try block, the catch block will be executed. The code will continue to execute after the catch block. Since there was no handling of binding source, the variables following the catch could end up in a indeterminate state and may throw another exception that may or may not be handled.

如果 try 块中发生错误,则将执行 catch 块。代码将在 catch 块之后继续执行。由于没有处理绑定源,catch 后面的变量可能最终处于不确定状态,并可能抛出另一个可能会也可能不会被处理的异常。

A better approach is as follows

更好的方法如下

 try
        {
            val = System.Enum.Parse(this.enumType, rb.Text) as System.Enum;

        object obj = this.bindingSource.Current;
        obj.GetType().GetProperty(propertyName).SetValue(obj, val, new object[] { });
        this.bindingSource.CurrencyManager.Refresh();
        }
        catch(EntityException ex)
        {
            // handle error
        }
        catch(Exception ex)
        {
            // cannot occurred if code is safe
            System.Windows.Forms.MessageBox.Show("No enum value for this radio button : " + ex.ToString());
        }

This allows the enum value error to be handled as well as other errors that may occur. However use the EntityException or variations of it before the Exception block (all decendents of Exception have to come first). One can get specific entity state information for a entity framework error by using the entity framework classes instead of the Exception base class. This can be helpful for debugging or providing clearer run time messages for the user.

这允许处理枚举值错误以及可能发生的其他错误。但是,在 Exception 块之前使用 EntityException 或它的变体(所有 Exception 的后代都必须首先出现)。可以通过使用实体框架类而不是 Exception 基类来获取实体框架错误的特定实体状态信息。这有助于调试或为用户提供更清晰的运行时消息。

When I setup try-catch blocks I like to view it as a "layer" on top of the code. I make decisions about the flow of the exceptions through out the program, their display to the user, and what ever cleanup is required to allow the program to continue working properly without objects in a indeterminate state that can cascade to other errors.

当我设置 try-catch 块时,我喜欢将其视为代码顶部的“层”。我决定整个程序中的异常流、它们向用户的显示,以及允许程序继续正常工作而无需对象处于不确定状态的情况下需要什么清理,这些状态可能会级联到其他错误。