c# - 更新MVVM主线程中的进度条

标签 c# wpf multithreading mvvm dispatcher

在我的应用程序中,我执行了一个长时间操作,并且我想显示操作的进度。在长时间的操作中我使用第3方dll。不幸的是,该 dll 不支持非主线程的调用。所以我不能使用另一个线程来启动我的进程。

我找到了一种使用 Dispather 更新主线程中进度条的方法。首先我编写了一个简单的WPF应用程序,并在代码隐藏中编写了简单的方法。

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    for (int i = 0; i <= 100; i++)
    {
        Dispatcher.Invoke(DispatcherPriority.Loaded,
                            (Action)(() =>
                                {
                                    pb.Value = i;
                                }));
        Thread.Sleep(10);
    }
}

这段代码工作正常。我在窗口中看到了进度。 但是问题是我使用MVVM,所以无法使用这个方法。

为了解决我的问题,我创建了 AttachedProperty

internal class ProgressBarAttachedBehavior
{
public static readonly DependencyProperty ValueAsyncProperty =
    DependencyProperty.RegisterAttached("ValueAsync", 
        typeof (double), 
        typeof (ProgressBarAttachedBehavior), 

        new UIPropertyMetadata(default(double), ValueAsyncChanged));

private static void ValueAsyncChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e)
{
    var pb =
        d as ProgressBar;

    if (pb == null)
    {
        return;
    }          

    var dispatcher =
        d.Dispatcher;

    //if (dispatcher == null || dispatcher.CheckAccess())
    //{
    //    pb.Value = (double) e.NewValue;
    //}
    //else
    {

        DispatcherFrame frame = 
            new DispatcherFrame(true);

        var dispatcherOperation = dispatcher.BeginInvoke(DispatcherPriority.Background,
                            new Action(() =>
                                {
                                    pb.Value = (double)e.NewValue;
                                    frame.Continue = false;
                                })
                            );

        Dispatcher.PushFrame(frame);                
    }                        
}



public static void SetValueAsync(ProgressBar progressBar, double value)   
{
    progressBar.SetValue(ValueAsyncProperty, value);
}

public static double GetValueAsync(ProgressBar progressBar)
{
    return (double)progressBar.GetValue(ValueAsyncProperty);
}

我在 XAML 中编写

<ProgressBar                   tesWpfAppMvvm:ProgressBarAttachedBehavior.ValueAsync="{Binding Progress}"/>

还有我的 ViewModel 代码

class Workspace1ViewModel : WorkspaceViewModel
{
private ICommand _startCommand;
private double _progress;

public ICommand StartCommand
{
    get
    {
        if (_startCommand == null)
        {
            _startCommand =
                new RelayCommand(Start);

        }

        return _startCommand;
    }
}

private void Start()
{
    for (int i = 0; i <= 100; i++)
    {
        Progress = i;
        Thread.Sleep(20);
    }
}

public double Progress
{
    get
    {
        return _progress;
    }
    set
    {                
        _progress = value;
        RaisePropertyChanged(() => Progress);                
    }

}
}

代码运行良好。长进程在主线程中运行,我在窗口中看到进度。

但是问题是,当我将 Active ViewModel 更改为另一个模型时,我收到错误:

Cannot perform this operation while dispatcher processing is suspended.

我尝试到处寻找解决方案,但找不到。任何地方的解决方案都是在单独的线程中运行日志进程。

请告诉我我的错误在哪里以及如何解决我的问题。

您可以下载演示项目来重现该问题 here

最佳答案

为什么不直接在 View 模型中使用 Application.Current.Dispatcher.Invoke() 呢?

请看一下这个示例:

MainViewModel.cs

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;

namespace WpfApplication4
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged
                                                            = delegate { };

        private int mCounter;

        public int Counter
        {
            get { return mCounter; }
            set
            {
                mCounter = value;
                PropertyChanged(this, new PropertyChangedEventArgs("Counter"));
            }
        }

        /// <summary>
        /// Supposed to be run from the background thread
        /// </summary>
        public void Start()
        {
            for(int i = 0; i <= 100; i++)
            {
                if(Application.Current == null)
                {
                    //do not try to update UI if the main window was closed
                    break;
                }

                Application.Current.Dispatcher.Invoke(
                        DispatcherPriority.Background,
                        (Action)(() =>
                        {
                            // Long running operation in main thread
                            // with low priority to prevent UI freeze
                            Thread.Sleep(100);
                            Counter = i;
                        }));
            }
        }
    }
}

MainWindow.xaml.cs

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

namespace WpfApplication4
{
    public partial class MainWindow : Window
    {
        private MainViewModel mainViewModel;
        public MainWindow()
        {
            InitializeComponent();
            Loaded += (sender, args) => StartOperation();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            StartOperation();
        }

        /// <summary>
        /// Start the long running operation in the background.
        /// </summary>
        private void StartOperation()
        {
            DataContext = mainViewModel = new MainViewModel();
            Task.Factory.StartNew(() => mainViewModel.Start());
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ProgressBar Height="20" Width="200" Value="{Binding Counter}" />
        <Button Content="Change View model" Height="23" Margin="0,100,0,0"
                HorizontalAlignment="Center"
                Click="Button_Click" />
    </Grid>
</Window>

关于c# - 更新MVVM主线程中的进度条,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16999824/

相关文章:

c# - System.DirectoryServices 很慢

c# - .Net 中是否有一个类来存储一个值和一个先前的值?

c# - Switch/case 语句中的 String.Empty 生成编译器错误

.net - 虚拟化 TreeView - 滚动时行为不稳定

python-3.x - 在threading.thread中,为什么args末尾带逗号

c# - 生成 XML 文档时出错。类型 Job 不是预期的

c# - 将 bool 值绑定(bind)到 visualstate

WPF应用程序基类?

c# - 为什么不调用 BeginGetResponse 回调?

python - 如何使用字典作为输入进行多线程?