MVVM: Binding radio buttons to a view model?
If you start with Jason's suggestion then the problem becomes a single bound selection from a list which translates very nicely to a ListBox
. At that point it's trivial to apply styling to a ListBox
control so that it shows up as a RadioButton
list.
<ListBox ItemsSource="{Binding ...}" SelectedItem="{Binding ...}"> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <RadioButton Content="{TemplateBinding Content}" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListBox.ItemContainerStyle></ListBox>
For the benefit of anyone researching this question down the road, here is the solution I ultimately implemented. It builds on John Bowen's answer, which I selected as the best solution to the problem.
First, I created a style for a transparent list box containing radio buttons as items. Then, I created the buttons to go in the list box--my buttons are fixed, rather than read into the app as data, so I hard-coded them into the markup.
I use an enum called ListButtons
in the view model to represent the buttons in the list box, and I use each button's Tag
property to pass a string value of the enum value to use for that button. The ListBox.SelectedValuePath
property allows me to specify the Tag
property as the source for the selected value, which I bind to the view model using the SelectedValue
property. I thought I would need a value converter to convert between the string and its enum value, but WPF's built-in converters handled the conversion without problem.
Here is the complete markup for Window1.xaml:
<Window x:Class="RadioButtonMvvmDemo.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <!-- Resources --> <Window.Resources> <Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}"> <Setter Property="Background" Value="Transparent"/> <Setter Property="ItemContainerStyle"> <Setter.Value> <Style TargetType="{x:Type ListBoxItem}" > <Setter Property="Margin" Value="5" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Border BorderThickness="0" Background="Transparent"> <RadioButton Focusable="False" IsHitTestVisible="False" IsChecked="{TemplateBinding IsSelected}"> <ContentPresenter /> </RadioButton> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Setter.Value> </Setter> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBox}"> <Border BorderThickness="0" Padding="0" BorderBrush="Transparent" Background="Transparent" Name="Bd" SnapsToDevicePixels="True"> <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <!-- Layout --> <Grid> <!-- Note that we use SelectedValue, instead of SelectedItem. This allows us to specify the property to take the value from, using SelectedValuePath. --> <ListBox Style="{StaticResource RadioButtonList}" SelectedValuePath="Tag" SelectedValue="{Binding Path=SelectedButton}"> <ListBoxItem Tag="ButtonA">Button A</ListBoxItem> <ListBoxItem Tag="ButtonB">Button B</ListBoxItem> </ListBox> </Grid></Window>
The view model has a single property, SelectedButton, which uses a ListButtons enum to show which button is selected. The property calls an event in the base class I use for view models, which raises the PropertyChanged
event:
namespace RadioButtonMvvmDemo{ public enum ListButtons {ButtonA, ButtonB} public class Window1ViewModel : ViewModelBase { private ListButtons p_SelectedButton; public Window1ViewModel() { SelectedButton = ListButtons.ButtonB; } /// <summary> /// The button selected by the user. /// </summary> public ListButtons SelectedButton { get { return p_SelectedButton; } set { p_SelectedButton = value; base.RaisePropertyChangedEvent("SelectedButton"); } } }}
In my production app, the SelectedButton
setter will call a service class method that will take the action required when a button is selected.
And to be complete, here is the base class:
using System.ComponentModel;namespace RadioButtonMvvmDemo{ public abstract class ViewModelBase : INotifyPropertyChanged { #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion #region Protected Methods /// <summary> /// Raises the PropertyChanged event. /// </summary> /// <param name="propertyName">The name of the changed property.</param> protected void RaisePropertyChangedEvent(string propertyName) { if (PropertyChanged != null) { PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName); PropertyChanged(this, e); } } #endregion }}
Hope that helps!