1

This is my DataGrid testing code. I set some data on the DataGrid source with ObservableCollection and bound this. And I modify the value where the ObservableCollection member's property. In this case, my UI has to show that value is changed. However, my DataGrid only interact when I selected the cell.

Binding

public ObservableCollection<MyClass> griddata { get; set; } = new ObservableCollection<MyClass>();

My Class

public class MyClass
{
    public int num { get; set; }
    public int idxnumber { get; set; }
}

XAML

<Grid>
    <DataGrid ItemsSource="{Binding griddata, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
    </DataGrid>
</Grid>

Function

//... griddata.add(someOfData);
public void ViewModel()
{
    int i = 0;
    while(i < 30)
    {
        testing(0);
        i++;
    }
}

private void testing(int idxnum)
{
    var test = griddata.Where(z => z.idxnumber == idxnum).FirstOrDefalut();
    test.num += 1;
}

Result
Cell value shows that 0 and I selected value is changed to 30, immediately.

Expect result
Cell value shows 0 to 30 continuously.

EDIT :
This is my whole code:

XAML

<Window x:Class="TestSol3.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:TestSol3"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Grid>
        <DataGrid ItemsSource="{Binding griddata}">
            
        </DataGrid>
    </Grid>
</Window>

Model.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestSol3
{
    public class Model
    {
        public int num { get; set; }
        public int idxnumber { get; set; }
    }
}

ViewModel.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestSol3
{
    public class ViewModel
    {
        public ObservableCollection<Model> griddata { get; set; } = new ObservableCollection<Model>();

        public ViewModel()
        {
            griddata.Add(new Model() { num = 0, idxnumber = 0 });
            griddata.Add(new Model() { num = 1, idxnumber = 1 });

            Load(0);
        }

        private void Load(int idxnum)
        {
            int i = 0;
            while(i < 30)
            {
                i++;
                griddata[idxnum].num++;
                //Thread.Sleep(200);
            }
        }
    }
}
h1d3r00t
  • 43
  • 7
  • add ```Thread.Sleep(100);``` in ```while (i < 30)``` will slow down the value update speed in the UI – cg-zhou Dec 17 '21 at 05:42
  • @cg-zhou Not work. I tried `Thread.sleep()` and `Task.Delay` also but It does not help this. – h1d3r00t Dec 17 '21 at 05:51
  • I've tried to build a project by you description, and it seems hard because partial code missing; If you provide runable xx.xaml.cs and xx.cs, it will help a lot. – cg-zhou Dec 17 '21 at 05:51

2 Answers2

3

Try implementing INotifyPropertyChanged in your model

namespace TestSol3
{
    public class ViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<Model> _griddata = new ObservableCollection<Model>();
        public ObservableCollection<Model> griddata 
        { 
            get => _griddata;
            set
            {
                _griddata= value;
                OnChangedProperty("griddata");
            }
        }

        public void OnChangedProperty(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }
}
Dasheen
  • 56
  • 2
1

[Solution]

In WPF, you can get dispatcher from Application.Current.Dispatcher

How to pass the UI Dispatcher to the ViewModel

And we need to change items in ObservableCollection to notify value change events.

The document of ObservableCollection says:

Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.

https://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.observablecollection-1?view=net-6.0

private void Load(int idxnum)
{
    Task.Run(() =>
    {
        int i = 0;
        while (i < 30)
        {
            i++;
            var model = griddata[idxnum];
            model.num++;

            var dispatcher = Application.Current.Dispatcher;
            dispatcher.Invoke(() =>
            {
                griddata.RemoveAt(idxnum);
                griddata.Insert(idxnum, model);
            });

            Thread.Sleep(500);
        }
    });
}

[Simple Demo]

I wrote a demo project to show how to update UI value continuously as below.

It use Task to start another thread to update UI value, hope it helps.

MainWindow.xaml

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

    <StackPanel>
        <TextBlock Name="MyTextBlock" FontSize="20"></TextBlock>
        <Button Click="Button_Click">Start</Button>
    </StackPanel>
</Window>

MainWindow.xaml.cs

using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace TestWpfApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private Task? CurrentTask { get; set; }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // only one task is supported simultaneously
            if (this.CurrentTask != null
                && !this.CurrentTask.IsCompleted)
            {
                return;
            }

            // start a task to calculate value continuously
            this.CurrentTask = Task.Run(() =>
            {
                int i = 0;
                while (i < 30)
                {
                    ++i;
                    Thread.Sleep(500);

                    // update value in the UI thread
                    this.Dispatcher.Invoke(() =>
                    {
                        this.MyTextBlock.Text = i.ToString();
                    });
                }
            });
        }
    }
}
cg-zhou
  • 528
  • 3
  • 6
  • Thank you for your answer. I'll try this. – h1d3r00t Dec 17 '21 at 06:05
  • I found the biggest issue with this answer. Sorry for this answer, Text block works well used `dispatcher` but `DataGrid` is not work well when the property changes continuously. – h1d3r00t Dec 17 '21 at 06:08
  • I updated the answer, the ```ObservableCollection``` only accepts ```add/remove/refresh``` events. – cg-zhou Dec 17 '21 at 06:20
  • I found this [article](https://stackoverflow.com/questions/1427471/observablecollection-not-noticing-when-item-in-it-changes-even-with-inotifyprop) – h1d3r00t Dec 17 '21 at 10:54