Search code examples
c#wpfxamldata-binding

WPF data binding, on properties values changed the UI don't refresh its content


I know there is a lot of questions that can look like this, but i don't realy find anyone answer to my problem here and in another forums.

So, I'm relatively new to WPF and I'm testing data binding, but I'm getting a trouble that data don't get values updated when values of a ObservableCollection get changed.

I will put my exemple were.

Main.xaml

<Window x:Class="TestWPF.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:TestWPF"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid x:Name="MyGrid">

</Grid>
</Window>

Main.xaml.cs

using System.Windows;

namespace TestWPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Stand stand = new Stand("Best Seller Stand");
        stand.cars.Add(new Car()
        {
            ID = "1",
            Brand = "BMW",
            CarNumber = 165,
            HaveRadio = true
        });
        stand.cars.Add(new Car()
        {
            ID = "2",
            Brand = "Toyota",
            CarNumber = 421,
            HaveRadio = true
        });
        stand.cars.Add(new Car()
        {
            ID = "4",
            Brand = "FIAT",
            CarNumber = 312,
            HaveRadio = false
        });
        stand.cars.Add(new Car()
        {
            ID = "3",
            Brand = "Ferrari",
            CarNumber = 12,
            HaveRadio = true
        });

        MyGrid.Children.Add(stand.GetCatalog());
    }
  }
}

Car.cs

using System;

namespace TestWPF
{
public class Car : IComparable, IComparable<int>
{
    public string ID { get; set; }
    public string Brand { get; set; }
    public int CarNumber { get; set; }
    public bool HaveRadio { get; set; }
    public void GerateRandomCarNumber()
    {
        CarNumber = new Random().Next(int.MinValue, int.MaxValue);
    }
    public int CompareTo(int other)
    {
        return CarNumber.CompareTo(other);
    }

    public int CompareTo(object obj)
    {
        Car other = null;
        if (obj is Car)
            other = obj as Car;
        return CarNumber.CompareTo(other.CarNumber);
    }
  }
}

Stand.cs

using System.Collections.Generic;
using System.Linq;

namespace TestWPF
{
public class Stand
{
    public Stand(string name)
    {
        Name = name;
    }
    public string Name { get; set; }
    public SortedSet<Car> cars { get; set; } = new SortedSet<Car>();
    public Car BestChoice
    {
        get
        {
            return cars.First();
        }
    }
    public StandCatalog Catalog { get; set; } = null;
    public StandCatalog GetCatalog()
    {
        if (Catalog == null)
            Catalog = new StandCatalog(this);
        return Catalog;
    }
  }
}

StandCatalog.xaml (UserControl)

<UserControl x:Class="TestWPF.StandCatalog"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:TestWPF"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Vertical">
    <Label Name="StandName" Content="{Binding Model.Name}" Margin="10"/>
    <Label Name="CarBrand"  Content="{Binding Model.BestChoice.Brand}" Margin="10"/>
    <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding CatalogCar}">
        <DataGrid.Columns>
            <DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
            <DataGridTextColumn Header="Brand" Binding="{Binding Brand}"/>
            <DataGridTextColumn Header="Car Number" Binding="{Binding CarNumber}"/>
            <DataGridCheckBoxColumn Header="Have Radio" Binding="{Binding HaveRadio}"/>
        </DataGrid.Columns>
    </DataGrid>
    <Button Content="Gerate Random Number" Click="btn_GerateRandomNumber"/>
</StackPanel>
</UserControl>

StandCatalog.xaml.cs

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;

namespace TestWPF
{
/// <summary>
/// Interaction logic for StandCatalog.xaml
/// </summary>
public partial class StandCatalog : UserControl
{
    public Stand Model { get; init; }
    public ObservableCollection<Car> CatalogCar { get; set; }
    public StandCatalog()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    public StandCatalog(Stand model) : this()
    {
        Model = model;
        CatalogCar = new ObservableCollection<Car>(Model.cars);
    }

    private void btn_GerateRandomNumber(object sender, RoutedEventArgs e)
    {
        foreach (var item in Model.cars)
        {
            item.GerateRandomCarNumber();
        }
    }
  }

}

So I get this aplication: When Open

But when I click on the button to gerate random number, the datagrid don't refresh and the label (Name="CarBrand") don't change either... Doesn't data binding refresh the UI when the elements changed its value?

I know that the value changed because when i reorder the datagrid I get this: enter image description here

Can anyone help me?

Another question, I'm using the class Stand as a Model of the StandCatalog (view/controller), what is the best way to use the SortedSet and the ObservableCollection together? Or should I use a SortedSet in the model?


Solution

  • Option 1: INotifyPropertyChanged

    The Car class should implement the INotifyPropertyChanged interface to inform targets when a property changes.

    public class Car : IComparable, IComparable<int>, INotifyPropertyChanged
    {
        private int _carNumber;
    
        public string ID { get; set; }
        public string Brand { get; set; }
    
        public int CarNumber
        {
            get => _carNumber;
            set
            {
                if (_carNumber == value) return;
    
                _carNumber = value;
                OnPropertyChanged();
            }
        }
    
        public bool HaveRadio { get; set; }
    
        public void GerateRandomCarNumber() { CarNumber = new Random().Next(int.MinValue, int.MaxValue); }
        public int CompareTo(int other) { return CarNumber.CompareTo(other); }
    
        public int CompareTo(object obj)
        {
            Car other = null;
            if (obj is Car)
                other = obj as Car;
            return CarNumber.CompareTo(other.CarNumber);
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
    }
    

    Option 2: DependencyProperty

    Define CarNumber property as a DependencyProperty. The infrastructure will handle the changes.

    public class Car : DependencyObject, IComparable, IComparable<int>
    {
        public static readonly DependencyProperty
            CarNumberProperty = DependencyProperty.Register("CarNumber", typeof(int), typeof(Car));
    
        public string ID { get; set; }
        public string Brand { get; set; }
    
        public int CarNumber
        {
            get => (int)GetValue(CarNumberProperty);
            set => SetValue(CarNumberProperty, value);
        }
    
        public bool HaveRadio { get; set; }
    
        public void GerateRandomCarNumber() { CarNumber = new Random().Next(int.MinValue, int.MaxValue); }
        public int CompareTo(int other) { return CarNumber.CompareTo(other); }
    
        public int CompareTo(object obj)
        {
            Car other = null;
            if (obj is Car)
                other = obj as Car;
            return CarNumber.CompareTo(other.CarNumber);
        }
    }