Лучший способ привязать группу радиокнопок в WinForms - PullRequest
24 голосов
/ 23 марта 2009

В настоящее время я работаю над привязкой данных к некоторым из моих существующих форм Windows Forms, и я столкнулся с проблемой, выясняющей правильный способ привязки данных к группе элементов управления с помощью радиокнопок в групповом блоке.

Мой бизнес-объект имеет целочисленное свойство, которое я хочу связать с 4 радиокнопками (где каждая из них представляет значения 0 - 3).

В настоящее время я связываюсь с объектом презентатора, который работает как связующее средство между формой и бизнес-объектом, и теперь я сделал так, чтобы у меня было 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 }

И затем я связываю каждую из радиокнопок с соответствующим свойством, как указано выше.

Мне это не кажется правильным, поэтому любые советы высоко ценятся.

Ответы [ 9 ]

23 голосов
/ 07 марта 2010

Ниже приведена общая реализация RadioGroupBox в духе предложения ArielBH (некоторый код заимствован у RadioPanel Джея Эндрю Аллена). Просто добавьте к нему 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());
        }
    }
}

Обратите внимание, что вы не можете привязать свойство 'Selected' через конструктор из-за проблем с порядком инициализации в InitializeComponent (привязка выполняется до инициализации переключателей, поэтому их тег в первом назначении равен нулю). Так что связывай себя так:

    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");
    }
2 голосов
/ 16 февраля 2013

Я знаю, что этот пост старый, но в поиске ответа на эту же проблему я наткнулся на этот пост, и он не решил мою проблему. В итоге я случайно выбил лампочку всего минуту назад и хотел поделиться своим решением.

У меня три переключателя в групповом поле. Я использую список <> объекта пользовательского класса в качестве источника данных.

Объект класса:

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 ...

Три отдельные открытые переменные с get / set обращаются к одной и той же глобальной переменной.

В приведенном ниже коде у меня есть функция, вызываемая во время 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);

Надеюсь, это кому-нибудь поможет !!

2 голосов
/ 24 марта 2009

Думаю, я бы использовал свой собственный GroupBox. Я бы привязал CustomGroupBox к вашей модели и установил бы правильный RadioButton (используя свойства тега или имени) из связанного значения.

1 голос
/ 15 июня 2012

Это мой подход для привязки списка переключателей к перечислению.

Используя Enum как строку в свойстве Tag кнопки, я использую событие Binding.Format и Binding.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;
    }
}

Затем настройте привязку данных следующим образом:

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);
}
0 голосов
/ 04 декабря 2017

Я хотел бы сделать замечание о блоке кода, который может быть полезен для людей, читающих эти посты. Следующий код может не всегда работать должным образом из-за его структуры.

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

Если в блоке try возникает ошибка, блок catch будет выполнен. Код будет продолжать выполняться после блока catch. Поскольку не было обработки источника привязки, переменные, следующие за перехватом, могут оказаться в неопределенном состоянии и могут вызвать другое исключение, которое может или не может быть обработано.

Лучший подход заключается в следующем

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

Это позволяет обрабатывать ошибку значения enum и другие возможные ошибки. Однако используйте EntityException или его варианты перед блоком Exception (все потомки Exception должны стоять первыми). Можно получить конкретную информацию о состоянии сущности для ошибки структуры сущности, используя классы структуры сущности вместо базового класса Exception. Это может быть полезно для отладки или предоставления более четких сообщений времени выполнения для пользователя.

Когда я настраиваю блоки try-catch, мне нравится рассматривать его как «слой» поверх кода. Я принимаю решения о прохождении исключений через программу, их отображении для пользователя и о том, что требуется для очистки, чтобы программа продолжала работать должным образом без объектов в неопределенном состоянии, которые могут каскадироваться в другие ошибки.

0 голосов
/ 03 сентября 2015

Мой подход состоит в том, чтобы поместить каждую радиокнопку в отдельную панель перед привязкой их к логическому свойству:

    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);
    }
0 голосов
/ 24 июля 2015

Мне понравилась идея RadioButtonGroupBox, но я решил создать версию, которая является самонесущей. Нет причин добавлять значение в атрибут Tag или вводить новые атрибуты значения. Любая назначенная радиокнопка все еще является членом RadioButtonGroupBox, и последовательность радиокнопок определяется во время разработки. Су, я изменил код. Теперь я могу получить и установить выбранную радиокнопку по позиции индекса, по имени элемента управления и по тексту. Кстати, текст можно использовать только в том случае, если назначенный вами текст отличается для каждой радиопереключателя.

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

}
0 голосов
/ 27 июня 2015

Установите для имени тега своих радиокнопок значение, соответствующее значению.

Создайте строковый параметр, например, OptionDuplicateFiles, и присвойте ему значение по умолчанию для имени тега для переключателя по умолчанию.

Чтобы сохранить выбранный переключатель:

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

Чтобы загрузить проверенный переключатель:

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

Тад!

0 голосов
/ 08 января 2013

Я начал решать ту же проблему.

Я использовал класс RadioButtonBinding, который инкапсулирует все радиокнопки вокруг перечисления в источнике данных.

Этот следующий класс хранит все переключатели в списке и ищет перечисление:

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

Вы используете класс в форме, добавляя этот код в конструктор формы:

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