WPF RadioButton Binding Issue with Dictionary in MVVM

fatih uyanık 225 Reputation points
2025-04-04T08:26:27.4533333+00:00

Hello,

In my WPF project, I'm trying to filter data by binding the IsChecked states of RadioButton controls to a Dictionary<string, object> property named Filters in my ViewModel. However, when users change the IsChecked state, the setter of Filters doesn't trigger, preventing updates from occurring.

To solve this, I created a custom ObservableDictionary class, but I'm encountering errors on the OnPropertyChanged lines. How can I fix this issue? Alternatively, is there a better approach you would recommend instead of using this method?

Thanks!

Error: Exited with code -2146232797 from "csc.exe".

using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Kütüphane_Otomasyonu.PresentationLayer.Views.Helpers
{
    public class ObservableDictionary<TKey, TValue> : ObservableObject, IEnumerable<KeyValuePair<TKey, TValue>> where TKey : notnull
    {
        private readonly Dictionary<TKey, TValue> _dictionary = new();
        public TValue this[TKey key]
        {
            get => _dictionary.ContainsKey(key) ? _dictionary[key] : default!;
            set
            {
                if (!_dictionary.ContainsKey(key) || !EqualityComparer<TValue>.Default.Equals(_dictionary[key], value))
                {
                    _dictionary[key] = value;
                    OnPropertyChanged(nameof(this[key]));
                                    }
            }
        }
        public void Add(TKey key, TValue value)
        {
            _dictionary.Add(key, value);
            OnPropertyChanged(nameof(this[key]));
        }
        public bool Remove(TKey key)
        {
            if ((_dictionary.Remove(key)))
            {
                OnPropertyChanged(nameof(this[key]));
                return true;
            }
            return false;
        }
        public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
        public int Count=>_dictionary.Count;
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return _dictionary.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,853 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Jack J Jun 25,291 Reputation points
    2025-04-04T09:48:34.31+00:00

    @fatih uyanık, Welcome to Microsoft Q&A, based on my test, I also reproduced the same problem with you when I build the ObservableDictionary class.

    I recommend that you use the ViewModel Method to do it,

    Here is a code example you could refer to.

    ViewModel class:

    using CommunityToolkit.Mvvm.Input;
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Windows.Input;
    public class MainViewModel : INotifyPropertyChanged
    {
        // Sample data collection
        private ObservableCollection<DataItem> _originalItems = new ObservableCollection<DataItem>
        {
            new DataItem { Name = "Apple", Category = "Fruit", IsActive = true },
            new DataItem { Name = "Carrot", Category = "Vegetable", IsActive = true },
            new DataItem { Name = "Banana", Category = "Fruit", IsActive = false },
            new DataItem { Name = "Broccoli", Category = "Vegetable", IsActive = true }
        };
        private ObservableCollection<DataItem> _filteredItems;
        public ObservableCollection<DataItem> FilteredItems
        {
            get => _filteredItems;
            set
            {
                _filteredItems = value;
                OnPropertyChanged();
            }
        }
        // Filter properties
        private bool _showAll = true;
        public bool ShowAll
        {
            get => _showAll;
            set
            {
                if(_showAll != value)
                {
                    _showAll = value;
                    if(value)
                    {
                        ShowFruits = false;
                        ShowVegetables = false;
                    }
                    OnPropertyChanged();
                    ApplyFilters();
                }
            }
        }
        private bool _showFruits;
        public bool ShowFruits
        {
            get => _showFruits;
            set
            {
                if(_showFruits != value)
                {
                    _showFruits = value;
                    if(value)
                    {
                        ShowAll = false;
                        ShowVegetables = false;
                    }
                    OnPropertyChanged();
                    ApplyFilters();
                }
            }
        }
        private bool _showVegetables;
        public bool ShowVegetables
        {
            get => _showVegetables;
            set
            {
                if(_showVegetables != value)
                {
                    _showVegetables = value;
                    if(value)
                    {
                        ShowAll = false;
                        ShowFruits = false;
                    }
                    OnPropertyChanged();
                    ApplyFilters();
                }
            }
        }
        private bool _showActiveOnly;
        public bool ShowActiveOnly
        {
            get => _showActiveOnly;
            set
            {
                if(_showActiveOnly != value)
                {
                    _showActiveOnly = value;
                    OnPropertyChanged();
                    ApplyFilters();
                }
            }
        }
        public MainViewModel()
        {
            FilteredItems = new ObservableCollection<DataItem>( _originalItems );
        }
        private void ApplyFilters()
        {
            var filtered = _originalItems.AsEnumerable();
            // Apply category filter
            if(!ShowAll)
            {
                if(ShowFruits)
                {
                    filtered = filtered.Where( item => item.Category == "Fruit" ); // Fixed typo
                }
                else if(ShowVegetables)
                {
                    filtered = filtered.Where( item => item.Category == "Vegetable" );
                }
            }
            // Apply active filter
            if(ShowActiveOnly)
            {
                filtered = filtered.Where( item => item.IsActive );
            }
            FilteredItems.Clear();
            foreach(var item in filtered)
            {
                FilteredItems.Add( item );
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
        }
        private ICommand _applyFiltersCommand;
        public ICommand ApplyFiltersCommand => _applyFiltersCommand ??= new RelayCommand( ApplyFilters );
    }
    // Simple RelayCommand implementation
    public class RelayCommand : ICommand
    {
        private readonly Action _execute;
        
        public RelayCommand(Action execute)
        {
            _execute = execute;
        }
        
        public bool CanExecute(object parameter) => true;
        
        public void Execute(object parameter)
        {
            _execute();
        }
        
        public event EventHandler CanExecuteChanged;
    }
    public class DataItem
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public bool IsActive { get; set; }
    }
    
    
    

    MainWindow.xaml.cs

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }
    

    XAML:

     <Grid Margin="10">
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="*"/>
         </Grid.RowDefinitions>
         <!-- Filter Controls -->
         <GroupBox Grid.Row="0" Header="Category Filters" Margin="0,0,0,10">
             <StackPanel Orientation="Horizontal">
                 <RadioButton GroupName="CategoryGroup" Content="All" 
                     IsChecked="{Binding ShowAll, Mode=TwoWay}" Margin="5">
                     <RadioButton.Style>
                         <Style TargetType="RadioButton">
                             <Setter Property="Command" Value="{Binding ApplyFiltersCommand}"/>
                         </Style>
                     </RadioButton.Style>
                 </RadioButton>
                 <RadioButton GroupName="CategoryGroup" Content="Fruits" 
                     IsChecked="{Binding ShowFruits, Mode=TwoWay}" Margin="5">
                     <RadioButton.Style>
                         <Style TargetType="RadioButton">
                             <Setter Property="Command" Value="{Binding ApplyFiltersCommand}"/>
                         </Style>
                     </RadioButton.Style>
                 </RadioButton>
                 <RadioButton GroupName="CategoryGroup" Content="Vegetables" 
                     IsChecked="{Binding ShowVegetables, Mode=TwoWay}" Margin="5">
                     <RadioButton.Style>
                         <Style TargetType="RadioButton">
                             <Setter Property="Command" Value="{Binding ApplyFiltersCommand}"/>
                         </Style>
                     </RadioButton.Style>
                 </RadioButton>
             </StackPanel>
         </GroupBox>
         <!-- Additional Filter -->
         <CheckBox Grid.Row="0" Content="Show Active Items Only" 
                  IsChecked="{Binding ShowActiveOnly, Mode=TwoWay}" 
                  Margin="0,0,0,10" VerticalAlignment="Bottom"/>
         <!-- Data Display -->
         <DataGrid Grid.Row="1" ItemsSource="{Binding FilteredItems}" AutoGenerateColumns="False">
             <DataGrid.Columns>
                 <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*"/>
                 <DataGridTextColumn Header="Category" Binding="{Binding Category}" Width="*"/>
                 <DataGridCheckBoxColumn Header="Active" Binding="{Binding IsActive}" Width="Auto"/>
             </DataGrid.Columns>
         </DataGrid>
     </Grid>
    
    
    

    Tested Result:

    User's image

    User's image

    Best Regards,

    Jack


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.