我有一个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
方法,这会导致异常(它是OK的,因为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)线程。
但是,如何在按钮变为按下两次?