1

In my view I have a ProgressBar which binds to a "Progress" property in my viewmodel. The viewmodel implements INotifyPropertyChanged and when the property is changed, OnPropertyChanged() is called.

The binding works however the view rarely updates the progress of the ProgressBar control. It only regularly updates when I am draging the window around with the mouse.

MainWindow.xaml

<Window
    x:Class="WpfTest.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:local="clr-namespace:WpfTest"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="500"
    Height="500"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <Grid>
        <ProgressBar Value="{Binding Progress}"/>
    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainWindowViewModel();
    }
}

MainWindowViewModel.cs

class MainWindowViewModel : INotifyPropertyChanged
{
    private readonly Timer updateProgressBarTimer;
    private int progress;

    public int Progress
    {
        get => progress;
        set
        {
            this.progress = value;
            OnPropertyChanged();
        }
    }

    public MainWindowViewModel()
    {
        updateProgressBarTimer = new Timer(OnUpdateProgressBarTimerTick, null, 0, 50);
    }

    private void OnUpdateProgressBarTimerTick(object state)
    {
        this.Progress += 2;
        if (this.Progress > 100)
            this.Progress -= 100;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

I have used INotifyPropertyChanged a lot and usually never have issues with it but I can't see the problem here.

Any suggestions how to fix this issue?

  • Beware that you're raising the event on a non-UI thread. WPF *should* marshal that back to the UI thread, but there's potentially some weirdness there – canton7 Jan 05 '21 at 13:08
  • 1
    Better use a DispatcherTimer. Its Tick handler is called in the thread in which the timer was created, i.e. the UI thread. Also, if you really need to run a Task, make sure to await it *before* updating the Process property, e.g. like `Process = await Task.Run(() => { ... return p; });` – Clemens Jan 05 '21 at 13:21
  • @canton7 excuse me, I forgot to remove the async and Task thing in this code example. It was just a thing I've tried out and has nothing to do with the issue. -> removed it – soaringmatty Jan 05 '21 at 13:51
  • 1
    Be aware that `Timer` still invokes its callback in a background thread. – Clemens Jan 05 '21 at 13:54
  • Exactly, `OnUpdateProgressBarTimerTick` is still called on a background thread. Use a `DispatcherTimer`, as Clemens mentioned – canton7 Jan 05 '21 at 13:56
  • Since you are using a System.Timers.Timer, instead of a DispatcherTimer as Clemens suggested, your code is updating the value of the ProgressBar on the wrong thread. I'm surprised it isn't throwing an exception. I'm not sure that this is your problem, but I don't see anything else wrong, so I suspect that this is the case. Use a DispatcherTimer so that the update correctly occurs on the UI thread. – Russ Jan 05 '21 at 13:57
  • 2
    @Russ It's actually System.Threading.Timer, but the problem is the same. – Clemens Jan 05 '21 at 14:12
  • 1
    Thank you all for your help! DispatcherTimer solved the problem – soaringmatty Jan 05 '21 at 14:27

1 Answers1

2

Replacing the System.Threading.Timer with a DispatcherTimer (with DispatcherPriority.Normal) solved the problem.

Thank you for your suggestions

Rekshino
  • 6,954
  • 2
  • 19
  • 44
  • **`DispatcherPriority.Normal`** is the key word! Without **`DispatcherPriority.Normal`** `DispatcherTimer` acts like a `System.Threading.Timer`. – Rekshino Jan 05 '21 at 14:44
  • 2
    @Rekshino Where did you get that from? I'm pretty sure that's wrong – canton7 Jan 05 '21 at 15:23
  • @canton7 Do you mean the "key word"? – Rekshino Jan 05 '21 at 15:34
  • 1
    @Rekshino You said "Without DispatcherPriority.Normal DispatcherTimer acts like a System.Threading.Timer". I'm asking for your source, as I'm pretty sure that's wrong – canton7 Jan 05 '21 at 15:35
  • I meant "acts like" as how it looks at the GUI. The source is my tests. Have you tested both? – Rekshino Jan 05 '21 at 15:38
  • 2
    I interpret "acts like" as meaning it dispatches on a background thread, which is false – canton7 Jan 05 '21 at 15:48