2009-03-23 8 views
21

Attualmente sto lavorando su un database di alcuni dei miei Windows Form esistenti, e mi sono imbattuto in un problema che mostrava il modo corretto di legare i dati a un gruppo di controlli di pulsanti all'interno di un gruppo scatola.Il modo migliore per catalogare un gruppo di radiobutton in WinForms

Il mio oggetto business ha una proprietà intera che voglio catalogare con 4 pulsanti radio (dove ognuno di essi rappresenta i valori 0 - 3).

Attualmente sono vincolante con un oggetto presenter che funge da raccoglitore tra il modulo e l'oggetto business e il modo in cui l'ho fatto ora è di avere 4 proprietà separate che si legano ciascuna contro questi valori (io uso INotifyPropertyChanged, ma non compreso quello qui):

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 } 

E io quindi associare ciascuno dei radiobutton alle loro rispettive proprietà come sopra.

Questo non mi sembra giusto, quindi ogni consiglio è molto apprezzato.

risposta

19

Di seguito è una implementazione generica di RadioGroupBox nello spirito del suggerimento di ArielBH (un codice preso in prestito da Jay Andrew Allen RadioPanel). Basta aggiungere RadioButtons, impostare i tag su interi diversi e associare alla proprietà '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()); 
     } 
    } 
} 

Notare che non è possibile associare alla proprietà 'Selected' tramite il progettista a causa di problemi di ordine di inizializzazione in InitializeComponent (l'associazione viene eseguita prima che i pulsanti di opzione vengono inizializzati, quindi il loro tag è nullo nel primo assegnazione). Così appena legarsi in questo modo:

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

Fantastico, grazie! Non sono comunque vincolante attraverso il designer, quindi è perfetto. Sto usando StrongBind (http://code.google.com/p/strongbind/) per legare i miei controlli –

+0

Felice di essere di aiuto :) E grazie per il testa a testa, controllerò StrongBind, sembra interessante –

2

Penso che utilizzerei il mio GroupBox personale. Assocerei CustomGroupBox al tuo modello e imposterei il RadioButton corretto (usando il tag o le proprietà del nome) dal valore bindato.

+0

Sembra molto meglio. Grazie per il suggerimento –

1

Questo è il mio approccio per il legame di un elenco di pulsanti di opzione per un enum.

Utilizzando l'Enum come una stringa nella proprietà Tag del pulsante, io uso il Binding.Format e Binding.Parse evento per decidere quale pulsante dovrebbe essere controllato.

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

quindi l'installazione dei dati di legame come questo:

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

ho iniziato a risolvere la stessa problematica.

Ho utilizzato una classe RadioButtonBinding che incapsula tutti i pulsanti di opzione su un'enumerazione nell'origine dati.

Questa classe seguente mantiene tutti i pulsanti di opzione in un elenco e fa una ricerca per l'enumerazione:

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

Si utilizza la classe in una forma con l'aggiunta che il codice nel costruttore del 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); 
    } 
2

So che questo post è vecchio ma nella mia ricerca di una risposta per questo stesso problema mi sono imbattuto in questo post e non ha risolto il mio problema.Ho finito per far esplodere una lampadina in modo casuale solo un minuto fa e volevo condividere la mia soluzione.

Ho tre pulsanti di opzione in una casella di gruppo. Sto usando un elenco <> di un oggetto di classe personalizzato come origine dati.

classe di oggetti:

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

Tre variabili pubbliche separate con get/set l'accesso alla stessa variabile globale.

Nel codice sottostante ho una funzione chiamata durante l'impostazione Page_Load() di tutti i database di controlli. Per ciascun pulsante di opzione aggiungo il suo databing.

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

Spero che questo aiuta qualcuno !!

+1

Grazie per questo @paul - Stavo osservando un comportamento molto strano (fare clic sul pulsante C, setter per la proprietà B colpire con valore == true) fino a quando non ho usato le opzioni di binding. Apparentemente, entrambi i parametri formattingEnabled = true e nullValue = false sono necessari perché funzioni. Non ho idea del perché, ma funziona così sono felice al 50%! – Jon

0

Imposta il nome del tag dei tuoi pulsanti di opzione su qualcosa che rappresenta il valore.

Creare un'impostazione di stringa, ad esempio OptionDuplicateFiles, e assegnargli il valore predefinito del nome del tag per il pulsante di opzione predefinito.

Per salvare il pulsante di scelta controllato:

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

per caricare il pulsante di scelta controllato:

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

Tada!

0

Mi è piaciuta l'idea di un RadioButtonGroupBox ma ho deciso di creare una versione autosufficiente. Non vi è alcun motivo per aggiungere valore all'attributo Tag o per introdurre nuovi attributi di valore. Qualsiasi pulsante di opzione assegnato è ancora membro di RadioButtonGroupBox e la sequenza di pulsanti radio viene definita durante lo sviluppo. Quindi ho modificato il codice. Ora posso ottenere e impostare il radiobutton selezionato in base alla posizione dell'indice, in base al nome del controllo e in base al testo. BTW Il testo è utilizzabile solo se il testo assegnato è diverso per ogni radiobutton.

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

Il mio approccio è quello di mettere ogni pulsante di scelta in un proprio pannello davanti a loro legame ad una proprietà 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

Vorrei fare un'osservazione circa il blocco di codice che potrebbe essere utile per persone che leggono questi post Il seguente codice potrebbe non funzionare sempre come previsto a causa della sua struttura.

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

Se si verifica un errore nel blocco try, verrà eseguito il blocco catch. Il codice continuerà ad essere eseguito dopo il blocco catch. Poiché non vi era alcuna gestione della fonte di legame, le variabili che seguono la cattura potrebbero finire in uno stato indeterminato e possono lanciare un'altra eccezione che può o non può essere gestita.

Un approccio migliore è la seguente

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

questo modo il valore di enumerazione errore da trattare, nonché altri errori che possono verificarsi.Tuttavia, utilizzare EntityException o varianti di esso prima del blocco Exception (tutti i discendenti di Exception devono venire prima). È possibile ottenere informazioni sullo stato delle entità specifiche per un errore di framework di entità utilizzando le classi di framework di entità anziché la classe di base Exception. Questo può essere utile per il debug o fornire messaggi di run time più chiari per l'utente.

Quando imposto i blocchi try-catch mi piace visualizzarlo come "livello" sopra il codice. Prendo decisioni in merito al flusso delle eccezioni attraverso il programma, la loro visualizzazione all'utente e qualsiasi operazione di pulizia è necessaria per consentire al programma di continuare a funzionare correttamente senza oggetti in uno stato indeterminato che può sovrapporsi ad altri errori.

Problemi correlati