2009-03-23 8 views
21

Actualmente estoy trabajando en el enlace de datos de mis Windows Forms existentes, y me encontré con un problema para encontrar la forma correcta de enlazar datos a un grupo de controles de radiobutton dentro de un grupo caja.La mejor manera de enlazar datos con un grupo de botones de radio en WinForms

Mi objeto de negocio tiene una propiedad entera de la cual quiero enlazar datos con 4 botones de radio (donde cada uno de ellos representa los valores 0 - 3).

Actualmente estoy vinculando contra un objeto presentador que funciona como la carpeta entre el formulario y el objeto comercial, y la forma en que lo he hecho ahora es tener 4 propiedades separadas que cada uno se une a cada uno de estos valores (hago uso de INotifyPropertyChanged, pero sin incluir la de aquí):

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 } 

Y luego ato cada uno de los botones de radio a su respectiva propiedad como anteriormente.

Esto no me parece correcto, por lo que cualquier consejo es muy apreciado.

Respuesta

19

A continuación se muestra una implementación genérica de RadioGroupBox según el espíritu de la sugerencia de ArielBH (parte del código tomado de Jay Andrew Allen RadioPanel). Simplemente agregue RadioButtons, establezca sus etiquetas en diferentes enteros y ligue a la propiedad 'Seleccionado'.

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

Tenga en cuenta que no se puede obligar a la propiedad 'seleccionados' a través del diseñador debido a problemas de orden de inicialización en InitializeComponent (la unión se lleva a cabo antes de que se inicializan los botones de radio, por lo que su etiqueta es nulo en el primer asignación). Así que solo únete a ti mismo así:

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

¡Impresionante, gracias! No estoy vinculado a través del diseñador de todos modos, así que esto es perfecto. Estoy usando StrongBind (http://code.google.com/p/strongbind/) para enlazar mis controles –

+0

Me alegro de ser de ayuda :) Y gracias por el aviso, voy a ver StrongBind, se ve interesante –

2

Creo que usaría mi propio GroupBox. Me gustaría vincular el CustomGroupBox a su modelo y establecer el RadioButton correcto (utilizando la etiqueta o las propiedades del nombre) del valor binded.

+0

Eso suena mucho mejor. Gracias por la sugerencia –

1

Este es mi enfoque para vincular una lista de botones de opción a una enumeración.

Uso de la enumeración como una cadena en propiedad Tag del botón, utilizo el Binding.Format y Binding.Parse caso para decidir qué botón se debe comprobar.

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

a continuación, configurar sus datos de unión como esta:

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

empecé a resolver el mismo problema.

Utilicé una clase RadioButtonBinding que encapsula todos los botones de radio sobre una enumeración en la fuente de datos.

Esta clase siguiente mantiene todos los botones de selección en una lista y hace una búsqueda para la enumeración:

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

Está utilizando la clase en una forma mediante la adición de ese código en el constructor de la forma:

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

Sé que esta publicación es antigua, pero en mi búsqueda de una respuesta para este mismo problema me encontré con esta publicación y no resolvió mi problema.Terminé haciendo estallar una bombilla al azar hace un minuto y quería compartir mi solución.

Tengo tres botones de opción en un cuadro de grupo. Estoy usando una lista <> de un objeto de clase personalizado como fuente de datos.

objeto de clase:

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

Tres variables públicas separadas con get/set para acceder a la misma una variable global.

En el código subyacente, que tienen una función llamada durante el Load() ajustar todos los controles DataBindings. Para cada botón de radio, agrego su propio control de datos.

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

Espero que esto ayude a alguien !!

+1

Gracias por eso @paul: estaba viendo un comportamiento muy extraño (haga clic en el botón C, setter para la propiedad B hit con valor == verdadero) hasta que use sus opciones de encuadernación. Aparentemente, se necesitan ambos parámetros formattingEnabled = true y nullValue = false para que esto funcione. No tengo idea de por qué, pero funciona, ¡así que estoy un 50% contento! – Jon

0

Establezca el nombre de la etiqueta de sus botones de opción en algo que represente el valor.

Crear un entorno de cadena, por ejemplo, OptionDuplicateFiles, y darle el valor por defecto del nombre de la etiqueta para su botón de opción por defecto.

Para guardar el botón de radio verificado:

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

para cargar el botón de radio verificado:

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

Tada!

0

Me gustó la idea de un RadioButtonGroupBox pero decidí crear una versión que sea autosuficiente. No hay ninguna razón para agregar valor al atributo Etiqueta o para introducir nuevos atributos de valor. Cualquier botón de radio asignado todavía es miembro de RadioButtonGroupBox y la secuencia de botones de radio se define durante el desarrollo. Soo, modifiqué el código. Ahora puedo obtener y configurar el botón de radio seleccionado por posición de índice, Por nombre de control y por Texto. BTW El texto solo se puede usar si el texto asignado es diferente para cada botón de radio.

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

Mi enfoque es poner cada botón de radio en su propio panel de delante de ellos unirse a una propiedad booleana:

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

me gustaría hacer una observación sobre el bloque de código que podría ser útil para personas que leen estos mensajes. El siguiente código puede no funcionar siempre como se esperaba debido a su estructura.

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

Si se produce un error en el bloque try, se ejecutará el bloque catch. El código continuará ejecutándose después del bloque catch. Dado que no se manejaba la fuente de enlace, las variables que siguen a la captura podrían terminar en un estado indeterminado y pueden arrojar otra excepción que puede o no ser manejada.

Un mejor enfoque es como sigue

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

Esto permite que el error de valor enum a ser manejado, así como otros errores que pueden ocurrir.Sin embargo, utilice la EntityException o variaciones de la misma antes del bloque Exception (todas las versiones de Exception tienen que aparecer primero). Uno puede obtener información específica del estado de la entidad para un error del marco de la entidad al usar las clases del marco de la entidad en lugar de la clase base de la Excepción. Esto puede ser útil para depurar o proporcionar mensajes de tiempo de ejecución más claros para el usuario.

Cuando configuro los bloques try-catch me gusta verlos como una "capa" encima del código. Tomo decisiones sobre el flujo de las excepciones a lo largo del programa, su visualización para el usuario y cualquier limpieza que se requiera para permitir que el programa continúe funcionando correctamente sin objetos en un estado indeterminado que pueda conectarse en cascada a otros errores.

Cuestiones relacionadas