我有一個WPF應用程序,它遵循MVVM。BusyIndicator允許觸發兩次RelayCommand
爲了使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)線程上。
但是如何按下按鈕兩次?
>「我不會試圖依靠繁忙的指標來控制有效的程序流」。爲什麼?你能爭辯這個嗎?我無法看到'if(IsBusy)return'的任何關鍵區別。此外,問題不在於「如何防止雙重命令執行」。我不明白,我怎樣才能實現雙擊按鈕,這必須在第一次點擊後隱藏。 – Dennis
也許更清楚地解釋你的問題,因爲你是自相矛盾的。 「看起來像BusyIndicator沒有及時隱藏工具欄,兩個後臺線程試圖執行RefreshOrders方法,...開始刷新,開始刷新 我在做什麼錯了?這看起來像是在問你爲什麼要執行雙重命令。或者,您希望啓用雙擊按鈕的問題?在這種情況下,一般選項是在命令上啓動一個Timer,如果再次點擊時,執行雙擊行爲,如果超時則單擊行爲。 –
你說得對,我已經對這個問題做了一些澄清。 – Dennis