MVVM に準拠した WPF アプリケーションがあります。
UI をレスポンシブにするために、TPL を使用して実行時間の長いコマンドを実行し、BusyIndicatorを使用してユーザーに表示しています。そのアプリケーションは現在ビジーです。
ビューモデルの1つに、次のコマンドがあります。
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
すると、ツールバーが時間内に非表示にならず、2 つのバックグラウンド スレッドがRefreshOrders
メソッドを実行しようとし、例外が発生します (EFDbContext
はスレッドセーフではないため、問題ありません)。出力ウィンドウに次の図が表示されます。
リフレッシュ開始。リフレッシュ開始。
私は何を間違っていますか?
BusyIndicator
のコードを調べました。何が間違っているのかわかりません: を設定IsBusy
すると が 2 回呼び出されるだけで、のコンテンツ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>
何か案は?
更新します。問題は、コマンドの再入を防ぐ方法ではありません。ボタンを2回押すことができるメカニズムについて疑問に思っています。
これがどのように機能するかです(私の観点から):
ViewModel.IsBusy
でバインドされていBusyIndicator.IsBusy
ます。バインディングは同期的です。BusyIndicator.IsBusy
呼び出しのセッターはVisualStateManager.GoToState
2 回。BusyIndicator
これらの呼び出しの 1 つで、のコンテンツ (私の場合はボタン)と重なる四角形が表示されます。- したがって、私が理解しているように、 を設定した後、ボタンを物理的に達成することはできません。これは
ViewModel.IsBusy
、これらすべてが同じ (UI) スレッドで発生するためです。
しかし、どのようにしてボタンが 2 回押されるのでしょうか??