Home > Enterprise >  Simple progress bar in C# MVVM
Simple progress bar in C# MVVM

Time:11-06

I am using LabVIEW in my company and would like to change to C# MVVM. On my autodidactic journey I feel like a total idiot, when stumbling over some things that are fairly easy to be done in LabVIEW but I simply cannot get done in C#.

After understanding the MVVM basics I am now trying to get a simple progress bar showing updates in a 1000 ms interval until full. But when executing the following code, the window shows up with an empty progress bar and after 4 seconds jumps to the final value of 100.

I think I am getting some absolute basic programming principle wrong, so I would be very grateful if you could help me with that. Thanks in advance!!

Here are the codes:

MainWindow.xaml

<Window x:Class="MicrosoftMVVMToolkit_Sandbox.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:MicrosoftMVVMToolkit_Sandbox"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <ProgressBar Width="500" Height="10" Value="{Binding Progress}" Minimum="0" Maximum="100"></ProgressBar>
</Grid>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Toolkit.Mvvm;

namespace MicrosoftMVVMToolkit_Sandbox
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    ViewModel vm = new ViewModel();
    
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = vm;
        this.Show();
        vm.Work();
    }
}
}

ViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Toolkit.Mvvm;
using Microsoft.Toolkit.Mvvm.ComponentModel;

namespace MicrosoftMVVMToolkit_Sandbox
{
class ViewModel : ObservableObject
{
    private int progress;

    public int Progress
    {
        get => progress;
        set => SetProperty(ref progress, value);
    }

    public void Work()
    {
        Progress = 0;
        System.Threading.Thread.Sleep(1000); 
        Progress = 25;
        System.Threading.Thread.Sleep(1000);
        Progress = 50;
        System.Threading.Thread.Sleep(1000); 
        Progress = 75;
        System.Threading.Thread.Sleep(1000);
        Progress = 100;
    }
}
}

============================

Solution 1 - await Task.Delay option:

Here is the exemplary code for the await Task.Delay option, in case that anyone else has the same problem:

    public async void Work()
    {          
        Progress = 0;
        await Task.Delay(1000);
        Progress = 25;
        await Task.Delay(1000);
        Progress = 50;
        await Task.Delay(1000);
        Progress = 75;
        await Task.Delay(1000);
        Progress = 100;
    }

CodePudding user response:

The main problem here is that the progress-bar can only be updated when the UI thread is not blocked. I.e. it is processing windows messages. Thread.Sleep will however block the UI thread, preventing it from doing any other work, including drawing your progress bar.

There are a few possible solutions

  • Use an async method and use await Task.Delay(1000) instead of Thread.Sleep. This will allow the UI thread to do other work while waiting. But it will not work if you need to do anything processing-intensive.
  • Run your work on a background thread, for example with Task.Run. But this leads to the next problem: you are not allowed to update any UI control from a background thread. For this there are two solutions:
    • Use Dispatcher.BeginInvoke to update the Progress-property from the UI thread. (or any equivalent ways to run code on the UI thread)
    • Update the progress-field from the background thread, but use a separate timer to raise the OnPropertyChanged event for the Progress-property. The timer should be one that runs on the UI thread, like DispatchTimer. This has an advantage of allowing frequent progress updates without spamming the UI thread with messages.

CodePudding user response:

In constructor:

public MainWindow()
{
    InitializeComponent();
    this.DataContext = vm;
    this.Show();
    vm.Work();
}

vm.Work() blocks your code execution for 4 seconds (each call to System.Threading.Thread.Sleep(1000)) It makes UI not responsible for that time.

You need to change progress bar Progress using some non blocking tool, like DispatcherTimer.

Example:

// remove vm.Work() from constructor

// add private field in MainWindow
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();

// add in constructor
dispatcherTimer.Tick  = dispatcherTimer_Tick;
dispatcherTimer.Interval = new TimeSpan(0,0,1);
dispatcherTimer.Start();

// add private method in MainWindow
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
    vm.Progress  = 1;
}

I didn't test this code.

  • Related