I've just found this question while starting to learn ReactiveUI.
I agree that having a group of radio buttons bound to a single property is preferable when these are used to control an underlying scalar variable. A RadioButtonGroup control might be a "great" solution, but strikes me as requiring quite a bit of work. As a simpler solution I have written a helper class, which can be set up with more-or-less declarative code:
namespace ReactiveUIHelpers
{
using System;
using System.Collections.Generic;
using System.Windows.Forms;
public class RadioButtonHelper<TValue> where TValue : struct
{
private RadioButton defaultRadioButton;
private Dictionary<RadioButton, TValue> radioButtonValues;
private Dictionary<TValue, RadioButton> valueRadioButtons;
private Action<TValue> setter;
public RadioButtonHelper()
{
this.defaultRadioButton = null;
this.radioButtonValues = new Dictionary<RadioButton, TValue>();
this.valueRadioButtons = new Dictionary<TValue, RadioButton>();
}
public void Register(RadioButton radioButton, TValue value)
{
if (this.setter != null)
{
throw new ApplicationException($"{nameof(this.Register)} must not be called after {nameof(this.Bind)}!");
}
this.defaultRadioButton = this.defaultRadioButton ?? radioButton;
this.radioButtonValues.Add(radioButton, value);
this.valueRadioButtons.Add(value, radioButton);
radioButton.CheckedChanged += this.RadioButton_CheckedChanged;
}
public void Bind(IObservable<TValue> observable, Action<TValue> setter)
{
if (observable == null)
{
throw new ArgumentNullException(nameof(observable));
}
if (setter == null)
{
throw new ArgumentNullException(nameof(setter));
}
this.setter = setter;
observable.Subscribe(value => this.ValueChanged(value));
}
private void ValueChanged(TValue value)
{
RadioButton radioButton;
if (this.valueRadioButtons.TryGetValue(value, out radioButton))
{
radioButton.Checked = true;
}
else
{
this.defaultRadioButton.Checked = true;
}
}
private void RadioButton_CheckedChanged(object sender, EventArgs e)
{
RadioButton radioButton = sender as RadioButton;
if (radioButton == null)
{
return;
}
if (!radioButton.Checked)
{
return;
}
TValue value;
if (this.radioButtonValues.TryGetValue(radioButton, out value))
{
if (this.setter == null)
{
throw new ApplicationException($"{nameof(this.Bind)} has not been called!");
}
this.setter(value);
}
}
}
}
And an example of usage, which would be in/called from a Form constructor, is:
this.loadingQueueStateHelper = new RadioButtonHelper<QueueStates>();
this.loadingQueueStateHelper.Register(this.radioButtonLoadingQueueEmpty, QueueStates.E);
this.loadingQueueStateHelper.Register(this.radioButtonLoadingQueueMedium, QueueStates.M);
this.loadingQueueStateHelper.Register(this.radioButtonLoadingQueueFull, QueueStates.F);
this.loadingQueueStateHelper.Bind(this.WhenAnyValue(view => view.ViewModel.LoadingQueueState),
value => this.ViewModel.LoadingQueueState = value);