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 ofThread.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, likeDispatchTimer
. This has an advantage of allowing frequent progress updates without spamming the UI thread with messages.
- Use
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.