C# 总线指示器允许触发继电器命令两次
我有一个WPF应用程序,它遵循MVVM 为了使UI具有响应性,我使用TPL来执行长时间运行的命令,并向用户显示,该应用程序现在很忙 在其中一个视图模型中,我有以下命令:C# 总线指示器允许触发继电器命令两次,c#,wpf,mvvm,task-parallel-library,busyindicator,C#,Wpf,Mvvm,Task Parallel Library,Busyindicator,我有一个WPF应用程序,它遵循MVVM 为了使UI具有响应性,我使用TPL来执行长时间运行的命令,并向用户显示,该应用程序现在很忙 在其中一个视图模型中,我有以下命令: public ICommand RefreshOrdersCommand { get; private set; } public OrdersEditorVM() { this.Orders = new ObservableCollection<OrderVM>(); this.RefreshOr
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;
}
}
…并禁用内容:
<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的Setter调用
两次。其中一个调用使一个矩形可见,该矩形与VisualStateManager.GoToState
的内容重叠(在我的例子中是按钮)BusyIndicator
- 因此,据我所知,在设置了
之后,我无法实际实现该按钮,因为所有这些事情都发生在同一(UI)线程上ViewModel.IsBusy
但是按钮是如何被按下两次的呢?我不会试图依靠忙指示器来控制有效的程序流。命令执行设置为
IsBusy
,如果IsBusy
已为True
,则它本身不应运行
private void HandleRefreshOrders()
{
if (IsBusy)
return;
...
您还可以将按钮的Enabled
状态绑定到IsBusy
,但我认为核心解决方案是防止您的命令意外再次进入。解雇员工也会增加一些复杂性。我会将状态设置移动到HandleRefreshOrders
中,然后在继续执行中处理状态重置。(可能需要一些额外的建议/阅读,以确保其线程安全。)
*编辑:要明确移动IsBusy
以避免重复执行:
private void HandleRefreshOrders()
{
if (IsBusy)
return;
IsBusy = true;
var task = Task.Factory.StartNew(() => RefreshOrders());
task.ContinueWith(t =>
{
RefreshOrdersCompleted(t.Result);
IsBusy = false;
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task.ContinueWith(t =>
{
this.LogAggregateException(t.Exception, Resources.OrdersEditorVM_OrdersLoading, Resources.OrdersEditorVM_OrdersLoadingFaulted);
IsBusy = false;
},
CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
然后从RefreshOrders
方法中删除IsBusy
引用。单击按钮一次后,IsBusy
将被设置。如果用户快速单击,则触发命令的第二次尝试将跳过。任务将在后台线程上执行,完成后,IsBusy
标志将重置,命令将再次响应。这当前假定对RefreshOrdersCompleted
和LogAggregateException
的调用不冒泡异常,否则如果抛出异常,则不会重置该标志。可以在这些调用之前移动标志重置,或者在annon中使用try/finally。方法声明。>“我不会试图依靠忙指示器来控制有效的程序流”。为什么?你能争辩一下吗?如果(IsBusy)返回
,我看不出与有任何关键区别。此外,问题不在于“如何防止双重命令执行”。我不明白,我怎么能实现双击按钮,它必须在第一次点击后隐藏。也许解释你的问题更清楚,因为你自相矛盾。“看起来BusyIndicator没有及时隐藏工具栏,两个后台线程试图执行RefreshOrders方法,…开始刷新。开始刷新。我做错了什么?”这看起来像是在问为什么要执行双重命令。或者,您是否希望双击按钮?在这种情况下,常规选项是在命令上启动计时器,如果再次及时单击,则执行双击行为;如果超时,则执行单次单击行为。好的,根据您的描述,忙碌指示器绑定到IsBusy
属性。但是,您是从任务内部设置此属性的。(异步)处理单击事件的代码可以很好地完成,并在任务真正开始之前再次触发。如果在事件处理程序中而不是在任务中设置IsBusy
,这可能会被阻止,但我怀疑是否有任何保证。AFAIK繁忙指示器通过控制其他指示器来捕捉事件,从而欺骗用户界面。在按钮捕获下一次单击之前,它可能不会就位。当从UI-thread设置IsBusy
时,同样的效果(双重触发)也会发生。好的,我根据您提供的内容构建了一个快速示例,并确认了该行为。这个问题是我早些时候提出的。忙碌指示器拦截用户事件,但只有在它有机会生效之后。事实上,在busyindicator出现之前,您可以点击按钮的消息泵两下。您可以选择如上所述检测并忽略重新输入,或者将按钮的IsEnabled
绑定到IsBusy
属性。(使用反向转换器)这会转储消息,不会导致重复调用。