If you create a radio button group inside a control where the data context changes. When you change the data context from an entry where the later defined radio button is true to one where it's false but an earlier defined one is true, the original item has its bound value updated to false.
How do you work around this issue? (While the code is in VB, it will work in any flavour of .net. I used dotnet 4.5.2 for reproduction)
You can find a minimal problem solution on github here https://github.com/PhoenixStoneham/RadioButtonGroupBinding
Main Window
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFRadioButtonGroupBinding"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Durations}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedDuration}"/>
<Grid Grid.Column="1" DataContext="{Binding SelectedDuration}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="Name"/>
<TextBlock Text="{Binding Name}" Grid.Column="1"/>
<Label Content="Duration" Grid.Row="1"/>
<TextBox Text="{Binding Frequency}" Grid.Row="1" Grid.Column="1"/>
<RadioButton GroupName="DurationType" IsChecked="{Binding Hourly}" Grid.Row="2" Grid.Column="1" Content="Hours"/>
<RadioButton GroupName="DurationType" IsChecked="{Binding Daily}" Grid.Row="3" Grid.Column="1" Content="Days"/>
<RadioButton GroupName="DurationType" IsChecked="{Binding Weekly}" Grid.Row="4" Grid.Column="1" Content="Weeks"/>
<RadioButton GroupName="DurationType" IsChecked="{Binding Monthly}" Grid.Row="5" Grid.Column="1" Content="Months"/>
</Grid>
</Grid>
</Window>
MainWindowViewModel
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Public Class MainWindowViewModel
Implements INotifyPropertyChanged
Public ReadOnly Property Durations As ObservableCollection(Of DurationViewModel)
Public Sub New()
Durations = New ObservableCollection(Of DurationViewModel)
Durations.Add(New DurationViewModel("Daily", 1, False, True, False, False))
Durations.Add(New DurationViewModel("Weekly", 1, False, False, True, False))
Durations.Add(New DurationViewModel("Fortnightly", 1, False, False, True, False))
Durations.Add(New DurationViewModel("Monthly", 1, False, False, False, True))
Durations.Add(New DurationViewModel("1/2 yearly", 6, False, False, False, True))
Durations.Add(New DurationViewModel("Other Days", 2, False, True, False, False))
Durations.Add(New DurationViewModel("Take Over", 1, True, False, False, False))
Durations.Add(New DurationViewModel("1/2 Day Takeover", 12, True, False, False, False))
End Sub
Private _SelectedDuration As DurationViewModel
Public Property SelectedDuration As DurationViewModel
Get
Return _SelectedDuration
End Get
Set(value As DurationViewModel)
_SelectedDuration = value
DoPropertyChanged("SelectedDuration")
End Set
End Property
Public Sub DoPropertyChanged(name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
End Class
DurationViewModel
Imports System.ComponentModel
Public Class DurationViewModel
Implements INotifyPropertyChanged
Private _Name As String
Public Property Name As String
Get
Return _Name
End Get
Set(value As String)
_Name = value
DoPropertyChanged("Name")
End Set
End Property
Private _Hourly As Boolean
Public Property Hourly As Boolean
Get
Return _Hourly
End Get
Set(value As Boolean)
_Hourly = value
DoPropertyChanged("Hourly")
End Set
End Property
Private _Daily As Boolean
Public Property Daily As Boolean
Get
Return _Daily
End Get
Set(value As Boolean)
_Daily = value
DoPropertyChanged("Daily")
End Set
End Property
Private _Weekly As Boolean
Public Property Weekly As Boolean
Get
Return _Weekly
End Get
Set(value As Boolean)
_Weekly = value
DoPropertyChanged("Weekly")
End Set
End Property
Private _Monthly As Boolean
Public Property Monthly As Boolean
Get
Return _Monthly
End Get
Set(value As Boolean)
_Monthly = value
DoPropertyChanged("Monthly")
End Set
End Property
Public Sub New(name As String, frequency As Integer, hourly As Boolean, daily As Boolean, weekly As Boolean, monthly As Boolean)
Me.Name = name
Me.Frequency = frequency
Me.Hourly = hourly
Me.Daily = daily
Me.Weekly = weekly
Me.Monthly = monthly
End Sub
Private _Frequency As Integer
Public Property Frequency As Integer
Get
Return _Frequency
End Get
Set(value As Integer)
_Frequency = value
DoPropertyChanged("Frequency")
End Set
End Property
Public Sub DoPropertyChanged(name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
End Class
The cleanest way to bind to a radio button group is to define an enum type for each group, and then use a value converter for the binding.
[Sorry my code samples are in C#, but you should be able to convert it to VB.Net easily enough.]
public enum MyEnum
{
A,
B,
C
}
.
public class EnumToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return parameter != null && parameter.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null && value.Equals(true) ? parameter : DependencyProperty.UnsetValue;
}
}
Then in the ViewModel, define a property of the enum type
public class MainViewModel: ViewModelBase
{
private MyEnum _e;
public MyEnum E
{
get => _e;
set => Set(nameof(E), ref _e, value);
}
}
and bind in the View using the converter
<Window ...>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:EnumToBoolConverter x:Key="EnumConverter"/>
</Window.Resources>
<StackPanel>
<RadioButton
Content="A"
IsChecked="{Binding Path=E, Converter={StaticResource EnumConverter}, ConverterParameter={x:Static local:MyEnum.A}}" />
<RadioButton
Content="B"
IsChecked="{Binding Path=E, Converter={StaticResource EnumConverter}, ConverterParameter={x:Static local:MyEnum.B}}" />
<RadioButton
Content="C"
IsChecked="{Binding Path=E, Converter={StaticResource EnumConverter}, ConverterParameter={x:Static local:MyEnum.C}}" />
<TextBlock Text="{Binding E}"/>
</StackPanel>
</Window>