BusyIndi​​cator控件允许火RelayCommand两次(BusyIndicator a

2019-09-27 11:55发布

我有一个WPF应用程序,它遵循MVVM。

为了使UI响应,我使用TPL用于执行长时间运行的命令和BusyIndi​​cator控件来显示用户,该应用程序现在正忙。

在视图模型之一,我有这样的命令:

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)线程。

但是,如何在按钮变为按下两次?

Answer 1:

我不会试图依靠一个繁忙的指标,有效控制程序流程。 该命令执行设置IsBusy ,如果它本身应该不会运行IsBusy已经是True

private void HandleRefreshOrders()
{
    if (IsBusy)
        return;
...

您也可以绑定按钮的Enabled状态IsBusy为好,但我觉得最核心的解决方案是从无意重新门禁你的命令。 开球工人也将增加一些复杂性。 我的状态设置移动到HandleRefreshOrders然后处理状态的复位在ContinueWith实施。 (可能需要一些额外的意见/读取,以确保它是线程安全的。)

*编辑:澄清移动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());
}

然后取出IsBusy从引用RefreshOrders方法。 一旦你点击按钮一次, IsBusy将被设置。 如果用户快速点击,来触发命令第二次尝试将跳过了。 该任务将执行在后台线程,一旦完成, IsBusy标志将被重置,并命令将再次作出回应。 目前这假定将呼叫RefreshOrdersCompletedLogAggregateException不冒泡的例外,否则的话他们扔,该标志不会被重置。 可以移动的标志提前重置这些调用,或使用try /终于annon内。 方法声明。



文章来源: BusyIndicator allows to fire RelayCommand twice