Following the MVVM pattern, I have a list that contains radio buttons, the state of which is bound to a boolean property in a list of items in the view-model. I have logic that makes changes in the view-model, but I cannot get the UI to recognise these changes.
I have found variations of this problem online but cannot find anything as simple as my example. I don't want to over-complicate the code, so I am hoping there is a simple solution to this.
My real code is way too big to post, but I have managed to construct the following minimal example that shows the same behaviour.
My XAML:
<Window x:Class="WpfApplication2.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:WpfApplication2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Control.Resources>
<ResourceDictionary>
<DataTemplate x:Key="ProgressTrackingDataTemplate">
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{Binding Name}" Margin="0" Padding="2,2,2,2"/>
<Viewbox Grid.Row="0" Grid.Column="1" Height="20">
<RadioButton IsChecked="{Binding State,Mode=TwoWay}"/>
</Viewbox>
</Grid>
</DataTemplate>
<Style x:Key="ProgressTrackingListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Focusable" Value="False"/>
</Style>
</ResourceDictionary>
</Control.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="0" Grid.Column="0"
ItemsSource="{Binding ProgressValues}"
ItemTemplate="{StaticResource ProgressTrackingDataTemplate}"
ItemContainerStyle="{StaticResource ProgressTrackingListBoxItemStyle}"/>
<Button Grid.Row="1" Grid.Column="0" Content="Change selected item" Click="Button_Click"/>
</Grid>
</Window>
The code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = _myViewModel;
}
private void Button_Click( object sender, RoutedEventArgs e )
{
_myViewModel.ChangeSelectedItem();
}
private MyViewModel _myViewModel = new MyViewModel();
}
The view-model:
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication2
{
public class ProgressValue
{
public string Name { get; set; } = "";
public bool State { get; set; } = false;
}
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
_progressValues[ _selectedItemIndex ].State = true;
}
public event PropertyChangedEventHandler PropertyChanged;
public List<ProgressValue> ProgressValues { get { return _progressValues; } }
public void ChangeSelectedItem()
{
_selectedItemIndex++;
if( _selectedItemIndex == _progressValues.Count )
_selectedItemIndex = 0;
foreach( var progressValue in _progressValues )
progressValue.State = false;
_progressValues[ _selectedItemIndex ].State = true;
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( nameof(ProgressValues) ) );
}
private int _selectedItemIndex = 0;
private List<ProgressValue> _progressValues = new List<ProgressValue>()
{
new ProgressValue() { Name = "Not Started", State = false },
new ProgressValue() { Name = "Underway", State = false },
new ProgressValue() { Name = "Completed", State = false },
new ProgressValue() { Name = "Invoiced", State = false }
};
}
}
Note that the initial selection that I perform in the constructor of the view-model is reflected in the UI, which implies there must be a way to get this working.
Any help would be very much appreciated.
Kind wishes ~ Patrick
What I have tried:
I have tried having the radio buttons in a group and not having them in a group.
I have tried various ways to raise the
PropertyChanged
event, including passing an empty string in the
PropertyChangedEventArgs
, which I thought was a way to tell the UI to re-evaluate all bindings?