gpt4 book ai didi

c# - BusyIndi​​cator 允许两次触发 RelayCommand

转载 作者:行者123 更新时间:2023-12-03 10:23:46 25 4
gpt4 key购买 nike

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

为了使 UI 响应,我使用 TPL 来执行长时间运行的命令和 BusyIndicator向用户显示,该应用程序现在很忙。

在其中一个 View 模型中,我有这个命令:

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 绑定(bind), 和 RefreshOrdersCommand属性与工具栏上的按钮绑定(bind),该按钮位于 BusyIndicator 内在这个 View 模型的 View 中。

问题 .

如果用户不经常点击按钮,一切正常: BusyIndicator使用按钮隐藏工具栏,加载数据, BusyIndicator消失。在输出窗口中,我可以看到成对的行:

Start refresh. Stop refresh.



但是如果用户非常频繁地点击按钮,看起来像 BusyIndicator没有及时隐藏工具栏,两个后台线程试图执行 RefreshOrders方法,这会导致异常(这没关系,因为 EF DbContext 不是线程安全的)。在输出窗口中,我看到了这张图片:

Start refresh. Start refresh.



我究竟做错了什么?

我查看了 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.IsBusyBusyIndicator.IsBusy 绑定(bind).绑定(bind)是同步的。
  • BusyIndicator.IsBusy 的二传手来电VisualStateManager.GoToState两次。其中一个调用使一个矩形可见,它与 BusyIndicator 重叠。的内容(在我的例子中是按钮)。
  • 因此,据我了解,设置 ViewModel.IsBusy 后我无法实际实现按钮,因为所有这些事情都发生在同一个(UI)线程上。

  • 但是按钮如何被按下两次?

    最佳答案

    我不会尝试依靠繁忙指示器来控制有效的程序流程。命令执行集IsBusy , 如果 IsBusy 它本身不应该运行已经是 True .

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

    也可以绑定(bind)按钮的 Enabled状态为 IsBusy也是如此,但我认为核心解决方案是防止您的命令意外重新进入。解雇 worker 也会增加一些复杂性。我会将状态设置移至 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不要冒泡异常,否则如果它们抛出,标志将不会被重置。可以在这些调用之前移动标志重置,或者在 annon 中使用 try/finally。方法声明。

    关于c# - BusyIndi​​cator 允许两次触发 RelayCommand,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13989684/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com