ユーザーインターフェイスの再活性化

画像

アプリケーションが応答しない?!



WPFアプリケーションをプログラミングする人の多くは、おそらくビューコンストラクトを何千回も書いているでしょう。

{アイテムのバインド }


Itemsコレクションからのアイテムの受信がメインアプリケーションスレッドで行われ、時間がかかる場合、「デッド」ユーザーインターフェイスが表示されます。 アプリケーションはしばらくの間、状態の変化を描画せず、ユーザー入力に応答しません。 また、処理時間がWindowsウィンドウシステムで定義された特定の時間制限を超えると、システムはこのウィンドウを応答なしとしてマークします。最後に成功したウィンドウレンダリングの画像に白いマスクが適用され、タイトルに特別なマーカー(応答なし)が追加されます( ロシア語のローカライズ ):

画像



はい、あなた自身はおそらく、障害または同期操作の結果としていくつかのアプリケーションが「自分自身に入る」ときに何度も似たような画像を見たことがあるでしょう。 そして、もちろん、ユーザーがどれほど迷惑なのか知っています。 ほとんどの人はヒステリーと戦い、モニターのキーボードを壊し、理解できないまま画面を見て、プログラムで何が起こっているのかを理解しようとします。 長時間の操作の終了を待たずに、より「上級」のユーザーは、タスクマネージャーを使用して「滞納」ウィンドウを閉じます。



解決策1:非同期ObjectDataProvider



このソリューションは非常にシンプルであり、現在のプロジェクトでObjectDataProviderをデータソースとして使用する場合に最適です。



ステップ#1:単純な静的データプロバイダーを実装する


プロバイダーは、1つのメソッドを持つ通常の静的クラスです。

// Emulates a long items getting process using some delay of getting of each item <br/>

public static class AsyncDataProvider<br/>

{ <br/>

private const int _DefaultDelayTime = 300 ; <br/>

<br/>

public static ReadOnlyCollection < string > GetItems ( ) <br/>

{ <br/>

return GetItems ( _DefaultDelayTime ) ; <br/>

} <br/>

<br/>

public static ReadOnlyCollection < string > GetItems ( int delayTime ) <br/>

{ <br/>

List < string > items = new List < string > ( ) ; <br/>

foreach ( string item in Enum . GetNames ( typeof ( AttributeTargets ) ) . OrderBy ( item => item. ToLower ( ) ) ) <br/>

{ <br/>

items. Add ( item ) ; <br/>

// Syntetic delay to emulate a long items getting process <br/>

Thread. Sleep ( delayTime ) ; <br/>

} <br/>

<br/>

return items. AsReadOnly ( ) ; <br/>

} <br/>

}








ステップ#2:XAMLで非同期データソースを宣言する


<Window.Resources > <br/>

<ObjectDataProvider x:Key = "AsyncDataSource" <br/>

IsAsynchronous = "True" <br/>

ObjectType = "Providers:AsyncDataProvider" <br/>

MethodName = "GetItems" /> <br/>

<Converters:NullToBooleanConverter x:Key = "NullToBooleanConverter" /> <br/>

</Window.Resources >








NullToBooleanConverterコンバーターは単なる補助オブジェクトであり、その目的は名前で読み取ることができます( その実装は、トピックにアタッチされたプロジェクトにあります )。 すべての魔法は、 ObjectDataProviderIsAsynchronous = "True"属性にあります。 この属性はデータの受信方法を制御します。この属性が「True」に設定されている場合、WPFコアはDispatcherバックグラウンドオブジェクトを作成してこのプロパティの値を取得します。



ステップ#3:コードでデータプロバイダーを使用する


<ListBox x:Name = "ItemsListBox" <br/>

ItemsSource = "{Binding Source={StaticResource AsyncDataSource}, IsAsync=True}" > <br/>

<ListBox.Style > <br/>

<Style TargetType = "{x:Type ListBox}" > <br/>

<Style.Triggers > <br/>

<Trigger Property = "ItemsSource" Value = "{x:Null}" > <br/>

<Setter Property = "Template" Value = "{StaticResource WaitControlTemplate}" /> <br/>

</Trigger > <br/>

</Style.Triggers > <br/>

</Style > <br/>

</ListBox.Style > <br/>

</ListBox >








注意-リストにはトリガーが使用されます。これにより、ユーザーのデータを取得するプロセスを視覚化できます。 これは非常に重要です-長時間の操作についてユーザーに通知しないと、リスト上のアクションが利用できないため、アプリケーションは単に機能しないと考えられます( もちろん、状態が正しく処理されている場合は後で詳しく説明します)。



ステップ#4:アクションの可用性を処理することを忘れないでください


<Button Grid.Column = "1" <br/>

Content = "Edit" <br/>

Width = "70" <br/>

IsEnabled = "{Binding SelectedItem, ElementName=ItemsListBox, Converter={StaticResource NullToBooleanConverter}}" <br/>

Click = "EditButton_Click" /> <br/>









ステップ#5:実行中


すべての変更を加えた起動後のメインウィンドウは次のとおりです。

動作中の非同期アイテムの読み込み



そして、これはデータが受信された後の外観です:

非同期アイテムの読み込みが完了しました



[ 編集 ]ボタンは、単純なコンバーターを介して選択した要素に添付されます。 選択したアイテムがItemsListBoxのメインリストにない場合、ボタンは使用できません。 また、非同期データプロバイダーのAsyncDataSourceがリストを要素で満たした後にのみ、要素を選択できます。 [ 閉じる ]ボタンは、アプリケーションを制御する機能を視覚化するために追加されます-データを受信して​​メインウィンドウを閉じるプロセス中にクリックすることを妨げるものは何もありません。 同時に、アプリケーションはリクエストに正しく応答して終了します。これは、データソースが同期の場合には発生しませんでした。



ソリューション#2非同期バインディング



この問題の2番目の解決策は、 MV-VM(Model-View-ViewModel)パターンを使用します。これはおそらく、現在最も一般的なWPFおよびSilverlightのモジュールアプリケーション構築パターンの1つです。 このパターンの説明は、この記事の範囲を超えています-必要に応じて、ネットワーク上でそれに関する多くの情報を簡単に見つけることができます( 探すのが面倒すぎる場合は、記事の最後にあるリンクセクションをご覧ください )。



ステップ#1:


メインアプリケーションウィンドウのビューモデルを作成します。

public class MainViewModel<br/>

{ <br/>

private ICommand _commandClose ; <br/>

<br/>

private ICommand _commandEdit ; <br/>

<br/>

private ReadOnlyCollection < string > _items ; <br/>

<br/>

public ReadOnlyCollection < string > Items<br/>

{ <br/>

get<br/>

{ <br/>

if ( _items == null ) <br/>

{ <br/>

_items = AsyncDataProvider. GetItems ( ) ; <br/>

} <br/>

<br/>

return _items ; <br/>

} <br/>

} <br/>

<br/>

public ICommand CommandClose<br/>

{ <br/>

get<br/>

{ <br/>

if ( _commandClose == null ) <br/>

{ <br/>

_commandClose = new RelayCommand ( p => OnClose ( ) ) ; <br/>

} <br/>

<br/>

return _commandClose ; <br/>

} <br/>

} <br/>

<br/>

public ICommand CommandEdit<br/>

{ <br/>

get<br/>

{ <br/>

if ( _commandEdit == null ) <br/>

{ <br/>

_commandEdit = new RelayCommand ( p => OnEdit ( p ) , p => CanEdit ) ; <br/>

} <br/>

<br/>

return _commandEdit ; <br/>

} <br/>

} <br/>

<br/>

public string SelectedItem<br/>

{ <br/>

get ; <br/>

set ; <br/>

} <br/>

<br/>

private void OnClose ( ) <br/>

{ <br/>

App. Current . Shutdown ( ) ; <br/>

} <br/>

<br/>

private void OnEdit ( object parameter ) <br/>

{ <br/>

MessageBox. Show ( String . Format ( "Edtiting item: {0}" , <br/>

parameter != null ? parameter. ToString ( ) : "Not selected" ) ) ; <br/>

} <br/>

<br/>

private bool CanEdit<br/>

{ <br/>

get<br/>

{ <br/>

return SelectedItem != null ; <br/>

} <br/>

} <br/>

}








ステップ#2:メインビューのXAMLコードでバインディング宣言をわずかに変更する


<ListBox x:Name = "ItemsListBox" <br/>

Grid.Row = "0" <br/>

ItemsSource = "{Binding Items, IsAsync=True}" <br/>

SelectedItem = "{Binding SelectedItem}" > <br/>

<ListBox.Style > <br/>

<Style TargetType = "{x:Type ListBox}" > <br/>

<Style.Triggers > <br/>

<Trigger Property = "ItemsSource" Value = "{x:Null}" > <br/>

<Setter Property = "Template" Value = "{StaticResource WaitControlTemplate}" /> <br/>

</Trigger > <br/>

</Style.Triggers > <br/>

</Style > <br/>

</ListBox.Style > <br/>

</ListBox >








このシナリオでは、バインディングマークアップで指定された属性が非同期を担当します: "{Binding Items、IsAsync = True}"ObjectDataProviderの例のよう 、WPFコアは個別のバックグラウンドディスパッチャーを作成して、個別の非同期コンテキストでバインディング値を取得します。



それとは別に、このシナリオでは、メインウィンドウビューのXAMLコードでコントロールを表示するためのルールをコーディングする必要はありません。 上記のビューモデルコードでは、 MainViewModel.CommandEditコマンドの一部であるMainViewModel.CanEditプロパティが、 フォームの [ 編集 ]ボタンの表示を担当します。 コマンドセクションの詳細については、 リンクセクションをご覧ください。 ここでは、手動で何もする必要がないことに注意するのが適切です。CommandManagerクラスがすべてを処理します 。 必要なのは、 RelayCommandクラスが提供するICommandコントラクトの正しい実装だけです(添付プロジェクトでのこのクラスの実装に慣れることができます)。



宿題



自由に実行します-確認することも、尋ねることもしません:)すべてのルールに従って本格的なコントロールを作成する場合は、 Borderクラスまたはその祖先Decoratorから継承される本格的なコントロールに変更することで、 WaitControlTemplateテンプレートをわずかに改善できます。 この要素の動作とロジックは簡単です:





まとめ



開発者の主な顧客はユーザーです。 また、各開発者は、単純なユーザーの代わりに自分自身を置き、アプリケーションをテストするフルサイクルを実行し、自分(ユーザーとして)に何が悩み、何を(ユーザーとして)改善したいのかを正確に分析しようとすると便利です。



ソースコード



AsyncBinding.zip



参照資料



プレゼンテーションのデザインモデルモデルビューモデルを備えたWPFアプリケーション

コマンドシステムの概要

Windows Presentation Foundationの非同期データバインディング

非同期WPF

トピックのメイン画像の元はここから取得されます。



All Articles