【发布时间】:2012-12-09 00:09:56
【问题描述】:
我有一个 WPF 应用程序,它遵循 MVVM。
为了使 UI 具有响应性,我使用 TPL 执行长时间运行的命令,并使用 BusyIndicator 向用户显示,该应用程序现在很忙。
在其中一个视图模型中我有这个命令:
public ICommand RefreshOrdersCommand { get; private set; }
public OrdersEditorVM()
{
this.Orders = new ObservableCollection<OrderVM>();
this.RefreshOrdersCommand = new RelayCommand(HandleRefreshOrders);
HandleRefreshOrders();
}
private void HandleRefreshOrders()
{
var task = Task.Factory.StartNew(() => RefreshOrders());
task.ContinueWith(t => RefreshOrdersCompleted(t.Result),
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task.ContinueWith(t => this.LogAggregateException(t.Exception, Resources.OrdersEditorVM_OrdersLoading, Resources.OrdersEditorVM_OrdersLoadingFaulted),
CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
private Order[] RefreshOrders()
{
IsBusy = true;
System.Diagnostics.Debug.WriteLine("Start refresh.");
try
{
var orders = // building a query with Entity Framework DB Context API
return orders
.ToArray();
}
finally
{
IsBusy = false;
System.Diagnostics.Debug.WriteLine("Stop refresh.");
}
}
private void RefreshOrdersCompleted(Order[] orders)
{
Orders.RelpaceContent(orders.Select(o => new OrderVM(this, o)));
if (Orders.Count > 0)
{
Orders[0].IsSelected = true;
}
}
IsBusy 属性与BusyIndicator.IsBusy 绑定,RefreshOrdersCommand 属性与工具栏上的按钮绑定,该按钮位于此视图模型视图中的BusyIndicator 内。
问题。
如果用户不经常点击按钮,一切正常:BusyIndicator 用按钮隐藏工具栏,数据被加载,BusyIndicator 消失。在输出窗口中,我可以看到成对的行:
开始刷新。 停止刷新。
但是如果用户频繁点击按钮非常,看起来BusyIndicator没有及时隐藏工具栏,两个后台线程尝试执行RefreshOrders方法,导致异常(和没关系,因为 EF DbContext 不是线程安全的)。在输出窗口中,我看到了这张图片:
开始刷新。 开始刷新。
我做错了什么?
我查看了BusyIndicator 的代码。我不知道,那里可能有什么问题:设置IsBusy 只是对VisualStateManager.GoToState 进行两次调用,这反过来又使一个矩形可见,它隐藏了BusyIndicator 的内容:
<VisualState x:Name="Visible">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="busycontent" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="overlay" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
...并禁用内容:
<VisualState x:Name="Busy">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.001" Storyboard.TargetName="content" Storyboard.TargetProperty="(Control.IsEnabled)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>False</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
有什么想法吗?
更新。 问题不在于如何防止命令重入。我想知道允许按两次按钮的机制。
这是它的工作原理(根据我的观点):
-
ViewModel.IsBusy与BusyIndicator.IsBusy绑定。绑定是同步的。 -
BusyIndicator.IsBusy的设置器调用VisualStateManager.GoToState两次。其中一个调用使一个矩形可见,该矩形与BusyIndicator的内容(在我的例子中是按钮)重叠。 - 因此,据我了解,在设置
ViewModel.IsBusy后,我无法实际实现按钮,因为所有这些事情都发生在同一个 (UI) 线程上。
但是按钮是如何被按下两次的??
【问题讨论】:
标签: c# wpf mvvm task-parallel-library busyindicator