在我的应用程序中,我执行了长时间的操作,并且我想显示操作的进度。在长时间的操作中,我使用第三方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
答案 0 :(得分:2)
为什么不直接使用视图模型中的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>
答案 1 :(得分:0)
您可以查看同一例外的this thread吗?根据文档,您可以使用另一个委托包装“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);));
答案 2 :(得分:0)
这里有你想要的黑客: 设置进度条可见性,然后为UI线程提供足够的时间来更新其状态。
注:
当UI线程从睡眠状态唤醒时,由于UI密集型进程的执行,应用程序将无法响应。
完成UI密集型流程后,您的应用程序将再次响应。
以下是代码示例:
<强> XAML:强>
<telerik:RadProgressBar x:Name="progress"
Visibility="{Binding ProgressVisibility, Mode=OneWay}" IsIndeterminate="True"
<强>视图模型:强>
const int MINIMUM_UI_WAIT_REQUIRED = 2;
ProgressVisibility = Visibility.Visible;
await Task.Factory.StartNew(() => { Thread.Sleep(MINIMUM_UI_WAIT_REQUIRED); });