2009-03-23 5 views
21

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

Мой бизнес-объект имеет целочисленное свойство, которое я хочу привязать к 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 } 

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

Это не похоже на меня, поэтому любые советы приветствуются.

ответ

19

Ниже приведена общая реализация RadioGroupBox в духе предложения ArielBH (некоторый код, заимствованный у Джея Эндрю Аллена RadioPanel). Просто добавьте RadioButtons к нему, установите теги на разные целые числа и привяжите их к свойству «Выбранные».

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"); 
    } 
+0

Awesome, спасибо! Я все равно не привязываюсь к дизайнеру, так что это прекрасно. Я использую StrongBind (http://code.google.com/p/strongbind/), чтобы связать свои элементы управления –

+0

Рад помочь ... И спасибо за головы, я проверю StrongBind, выглядит интересно –

2

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

+0

Звучит намного лучше. Спасибо за подсказку –

1

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

Использование 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

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

Я использовал класс RadioButtonBinding, который инкапсулирует все радиообъекты об 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); 
    } 
} 

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

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

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

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

Класс объекта:

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

три отдельных публичных переменных с получить/установить доступ к той же одну глобальную переменную.

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

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

+1

Спасибо за это @paul - я видел очень странное поведение (нажмите кнопку C, setter для свойства B, набрав значение == true), пока я не воспользуюсь вашими вариантами привязки. По-видимому, для этого необходимы параметры formattingEnabled = true и nullValue = false. Не знаю, почему, но он работает, поэтому я на 50% счастлив! – Jon

0

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

Создайте параметр строки, например 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

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

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

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

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

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

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

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

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