我有一个 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
property 与 绑定BusyIndicator.IsBusy
,RefreshOrdersCommand
property 与工具栏上的按钮绑定,该按钮位于BusyIndicator
此视图模型的视图中。
问题。
如果用户不经常点击按钮,一切正常:BusyIndicator
用按钮隐藏工具栏,数据被加载,BusyIndicator
消失。在输出窗口中,我可以看到成对的行:
开始刷新。停止刷新。
但是如果用户非常频繁地点击按钮,看起来好像BusyIndicator
没有及时隐藏工具栏,并且两个后台线程尝试执行RefreshOrders
方法,这会导致异常(这没关系,因为EFDbContext
不是线程安全的)。在输出窗口中,我看到了这张图片:
开始刷新。开始刷新。
我究竟做错了什么?
我查看了BusyIndicator
's 的代码。我不知道,那里可能有什么问题:设置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)线程上。
但是按钮如何被按下两次?