C# MVVM 中的简单进度条

Posted

技术标签:

【中文标题】C# MVVM 中的简单进度条【英文标题】:Simple progress bar in C# MVVM 【发布时间】:2021-12-19 15:27:37 【问题描述】:

我在公司使用 LabVIEW,想改用 C# MVVM。在我的自学之旅中,当遇到一些在 LabVIEW 中相当容易完成但在 C# 中无法完成的事情时,我觉得自己完全是个白痴。

在了解了 MVVM 基础知识之后,我现在尝试获得一个简单的进度条,以 1000 毫秒的间隔显示更新,直到完成。但是在执行下面的代码时,窗口会显示一个空白的进度条,并在 4 秒后跳转到最终值 100。

我认为我在一些绝对的基本编程原则上是错误的,所以如果你能帮助我,我将不胜感激。提前致谢!!

代码如下:

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;
    


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

解决方案 1 - 等待 Task.Delay 选项:

这是 await Task.Delay 选项的示例代码,以防其他人遇到同样的问题:

    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;
    

【问题讨论】:

使Work() 异步以防止Thread.Sleep 的UI 阻塞/冻结。 【参考方案1】:

这里的主要问题是进度条只能在 UI 线程未被阻塞的情况下更新。 IE。它正在处理 Windows 消息。但是Thread.Sleep 会阻塞 UI 线程,阻止它执行任何其他工作,包括绘制进度条。

有几种可能的解决方案

使用异步方法并使用await Task.Delay(1000) 而不是Thread.Sleep。这将允许 UI 线程在等待时执行其他工作。但如果您需要做任何处理密集型的事情,它就行不通了。 在后台线程上运行您的工作,例如使用Task.Run。但这会导致下一个问题:不允许从后台线程更新任何 UI 控件。为此,有两种解决方案: 使用Dispatcher.BeginInvoke 从 UI 线程更新 Progress 属性。 (或在 UI 线程上运行代码的任何等效方式) 从后台线程更新进度字段,但使用单独的计时器为进度属性引发OnPropertyChanged 事件。计时器应该是在 UI 线程上运行的计时器,例如 DispatchTimer。这样做的好处是允许频繁更新进度,而不会向 UI 线程发送垃圾邮件。

【讨论】:

好的,我明白了!我已经尝试了 await Task.Delay 版本并且它有效。如果其他人有同样的问题,我已经添加了上面的实现。【参考方案2】:

在构造函数中:

public MainWindow()

    InitializeComponent();
    this.DataContext = vm;
    this.Show();
    vm.Work();

vm.Work() 阻止您的代码执行 4 秒(每次调用 System.Threading.Thread.Sleep(1000)) 它使 UI 不对那个时间负责。

您需要使用一些非阻塞工具更改进度条Progress,例如DispatcherTimer

例子:

// 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;

我没有测试这段代码。

【讨论】:

以上是关于C# MVVM 中的简单进度条的主要内容,如果未能解决你的问题,请参考以下文章

C#任务栏中的Windows 7进度条?

C# 中的 progressBar1 如何使用这个进度条控件.?

Xamarin Android C# 中的进度条和线程

c#如何根据文件数量控制进度条

从 .NET C# DLL 更新 MFC 对话框中的进度条

C#进度条怎么用 / C#progressBar的用法