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