2

在我的应用程序中,我执行了一个长时间的操作,我想显示操作的进度。在长时间操作中,我使用 3rd-party 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.

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

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

您可以在此处下载演示项目以重现该问题

4

3 回答 3

2

为什么不Application.Current.Dispatcher.Invoke()从视图模型中使用?

请看一下这个样本:

主视图模型.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;
                        }));
            }
        }
    }
}

主窗口.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>
于 2013-06-11T13:20:43.943 回答
0

Here's a hack for what you want: Set the progressbar visibility and then provide just enough time for the UI thread to update its state.

NOTE:

When the UI thread awakes from its sleep, the application will become unresponsive as a result of the UI intensive process executing.

Once the UI intensive process has completed, your application will become responsive again.

The following is a code example:

XAML:

<telerik:RadProgressBar x:Name="progress"  
Visibility="{Binding ProgressVisibility, Mode=OneWay}" IsIndeterminate="True"  

ViewModel:

const int MINIMUM_UI_WAIT_REQUIRED = 2;

ProgressVisibility = Visibility.Visible;
await Task.Factory.StartNew(() => { Thread.Sleep(MINIMUM_UI_WAIT_REQUIRED); });
于 2014-10-29T17:02:57.417 回答
0

你能在同一个异常上检查这个线程吗?根据文档,您可以使用另一个委托包装“ValueAsyncChanged”事件处理程序,并使用 Dispatcher.BeginInvoke 方法调用“ValueAsyncChanged”。似乎 WPF 引擎在忙于加载时不允许执行您的 PushFrame 调用。

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

        new UIPropertyMetadata(default(double),
(o, e) => 
Dispatcher.BeginInvoke( 
new DependencyPropertyChangedEventHandler( ValueAsyncChanged), o, e);));
于 2013-06-08T14:33:45.213 回答