Reputation: 2888
I'm using WPF and MVVM in an application I'm working on.
I was creating 5 radio buttons dynamically and at the time of creation I was setting the binding using an enumeration and an IValueConverter.
This was okay because I knew I only needed 5 radio buttons, but now the number of radio buttons that I need to create can change, they could be 5 as they could be 30.
So, which is the best way to bind the radio buttons with the code? I think I can't not use an enumeration any more, unless somehow I manage to create the enumeration dynamically, which I don't know if it is possible for what I've read about dynamic enumerations...
Thanks.
Edit
Here it is, more or less, the code I'm using to create the radio buttons dynamically.
public enum MappingOptions {
Option0,
Option1,
Option2,
Option3,
Option4
}
private MappingOptions mappingProperty;
public MappingOptions MappingProperty
{
get { return mappingProperty; }
set
{
mappingProperty= value;
base.RaisePropertyChanged("MappingProperty");
}
}
private void CreateRadioButtons()
{
int limit = 5;
int count = 0;
string groupName = "groupName";
parent.FormWithRadioButtons.Children.Clear();
foreach (CustomValue value in AllCustomValues)
{
if (count < limit)
{
System.Windows.Controls.RadioButton tmpRadioBtn = new System.Windows.Controls.RadioButton();
tmpRadioBtn.DataContext = this;
tmpRadioBtn.Content = value.Name;
tmpRadioBtn.GroupName = groupName;
tmpRadioBtn.Margin = new System.Windows.Thickness(10, 0, 0, 5);
string parameter = string.format("Option{0}", count.ToString());
System.Windows.Data.Binding tmpBinding = new System.Windows.Data.Binding("MappingProperty");
tmpBinding.Converter = new EnumBooleanConverter();
tmpBinding.ConverterParameter = parameter;
tmpBinding.Source = this;
try
{
tmpRadioBtn.SetBinding(System.Windows.Controls.RadioButton.IsCheckedProperty, tmpBinding);
}
catch (Exception ex)
{
//handle exeption
}
parent.FormWithRadioButtons.Children.Add(tmpRadioBtn);
count += 1;
}
else
break;
}
MappingProperty = MappingOptions.Option0;
}
public class EnumBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return System.Windows.DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return System.Windows.DependencyProperty.UnsetValue;
object parameterValue = Enum.Parse(value.GetType(), parameterString);
return parameterValue.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return System.Windows.DependencyProperty.UnsetValue;
return Enum.Parse(targetType, parameterString);
}
#endregion
}
Upvotes: 4
Views: 8256
Reputation: 96870
Create a class that exposes string Text
and boolean IsChecked
properties and that implements INotifyPropertyChanged
. Call it, oh, MutexViewModel
.
Create another class that implements an observable collection of these objects called Mutexes
, and that handles PropertyChanged
on each - e.g. has a constructor like:
public MutexesViewModel(IEnumerable<MutexViewModel> mutexes)
{
_Mutexes = new ObservableCollection<MutexViewModel>();
foreach (MutexViewModel m in Mutexes)
{
_Mutexes.Add(m);
m.PropertyChanged += MutexViewModel_PropertyChanged;
}
}
and an event handler that ensures that only one of the child objects has IsChecked
set to true at any given time:
private void MutexViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MutexViewModel m = (MutexViewModel)sender;
if (e.PropertyName != "IsChecked" || !m.IsChecked)
{
return;
}
foreach (MutexViewModel other in _Mutexes.Where(x: x != m))
{
other.IsChecked = false;
}
}
You now have a mechanism for creating an arbitrary number of named boolean properties that are all mutually exclusive, i.e. only one of them can be true at any given time.
Now create XAML like this - the DataContext
for this is a MutexesViewModel
object, but you can also bind the ItemsSource
to something like {DynamicResource myMutexesViewModel.Mutexes}
.
<ItemsControl ItemsSource="{Binding Mutexes}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:MutexViewModel">
<RadioButton Content="{Binding Text}" IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit
There was a syntax error in the XAML I posted, but nothing that should have ground you to a full stop. These classes work:
public class MutexViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Text { get; set; }
private bool _IsChecked;
public bool IsChecked
{
get { return _IsChecked; }
set
{
if (value != _IsChecked)
{
_IsChecked = value;
OnPropertyChanged("IsChecked");
}
}
}
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler h = PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MutexesViewModel
{
public MutexesViewModel(IEnumerable<MutexViewModel>mutexes)
{
Mutexes = new ObservableCollection<MutexViewModel>(mutexes);
foreach (var m in Mutexes)
{
m.PropertyChanged += MutexViewModel_PropertyChanged;
}
}
private void MutexViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MutexViewModel m = (MutexViewModel) sender;
if (e.PropertyName == "IsChecked" && m.IsChecked)
{
foreach(var other in Mutexes.Where(x => x != m))
{
other.IsChecked = false;
}
}
}
public ObservableCollection<MutexViewModel> Mutexes { get; set; }
}
Create a project, add those classes, paste the ItemsControl
XAML into the main window's XAML, and add this to the code-behind of the main window:
public enum Test
{
Foo,
Bar,
Baz,
Bat
} ;
public MainWindow()
{
InitializeComponent();
var mutexes = Enumerable.Range(0, 4)
.Select(x => new MutexViewModel
{ Text = Enum.GetName(typeof (Test), x) });
DataContext = new MutexesViewModel(mutexes);
}
Upvotes: 7