この記事は、
非同期操作だけでなく 、記事「
Automatic BusyIndicator 」の続きです。
attached propertyを使用する場合、XAMLで記述する必要があるのは次のとおりです。
<BusyIndicator AsyncIndicator.Attached="true" > <ListBox ItemsSource="{Binding DataList, IsAsync=true}" ...> ... </ListBox> <BusyIndicator>
私については-あなたはより少ない想像することはできません!
標準プロトタイプの
attached propertyから始めましょう:
public static class AsyncIndicator { static AsyncIndicator() { } public static readonly DependencyProperty AttachedProperty = DependencyProperty.RegisterAttached("Attached", typeof (bool), typeof (ContentControl), new FrameworkPropertyMetadata(false, AttachedChanged)); public static Boolean GetAttached(UIElement element) { return (Boolean) element.GetValue(AttachedProperty); } public static void SetAttached(UIElement element, Boolean value) { element.SetValue(AttachedProperty, value); } private static void AttachedChanged(DependencyObject busyIndicator, DependencyPropertyChangedEventArgs e) { } }
興味深いことに、 SetAttachedとSetAttached理論的には不要であり、スクリプトで呼び出されることはありませんが、これらがないと、 attached propertyは使用できません。AttachedChangedイベントメソッドに興味があります。 一般的な考え方は次のとおりです
BusyIndicatorのコンテンツと
ItemsSourceプロパティ(より正確には、
dependency property ItemsSourceProperty )を探しています。そのようなプロパティが見つかった場合、プロパティが変更された瞬間をインターセプトします。 値が
null場合-インジケーターをオンにし、そうでない場合-オフにします。
AttachedChangedを呼び出した時点で
BusyIndicatorのコンテンツはまだインストールされていません。これは驚くべきことではありません。
ContentControlのような標準イベントを見つけられませんでしたが、私はそれを回避する必要がありました。
private static void AttachedChanged(DependencyObject busyIndicator, DependencyPropertyChangedEventArgs e) { SetPropertyChangedCallback(ContentControl.ContentProperty, busyIndicator, ContentChangedCallback); } private static void SetPropertyChangedCallback(DependencyProperty dp, DependencyObject d, PropertyChangedCallback callback, bool reset = false) { if (dp == null || d == null) return; var typ = d.GetType(); var metadata = dp.GetMetadata(typ); var oldValue = metadata.SetPropValue("Sealed", false); metadata.PropertyChangedCallback -= callback; if (!reset) metadata.PropertyChangedCallback += callback; metadata.SetPropValue("Sealed", oldValue); } private static void ContentChangedCallback(DependencyObject busyIndicator, DependencyPropertyChangedEventArgs e) { if (!(bool) busyIndicator.GetValue(AttachedProperty)) return; SetBusyIndicator(e.OldValue as DependencyObject, null); SetBusyIndicator(e.NewValue as DependencyObject, busyIndicator); }
このコードについて少しコメントします。
SetPropertyChangedCallbackメソッドは、
dependency property ContentPropertyメタデータを受け取り、このプロパティの値を変更するイベントハンドラーを追加(または削除)します。
小さなハックが1つあります。事実は、初期化後にメタデータを変更することはできないということです。これは例外で明確に述べられています。 ただし、
PropertyMetadata.csモジュールのソースコードを分析したところ、この初期化の兆候は
internal Sealedプロパティであることが
Sealedました。 サンプルコードを
SetPropValuにしないために、
SetPropValuメソッドの実装を引用しませんが、
SetPropValuを介してオブジェクトのプロパティの変更を書き込むことはできないと思います。
WPFのハブの誰かが、この問題をより美しく解決する方法を教えてくれたら、コメント欄に書いてください。これで、
BusyIndicatorでの新しいコンテンツのインストール場所で
ContentChangedCallbackメソッドが呼び出されます。 この方法の最初の行は非常に重要です-なぜなら このメソッドは、
AsyncIndicator.Attached="true"プロパティを設定したものだけでなく、すべての
BusyIndicatorに対して呼び出されます。 したがって、このプロパティの値が
true等しいことを確認し
true 。
このメソッドの2行目は前のコンテンツのItemsSource変更イベントを無効にし、3行目はイベントを新しいコンテンツに追加します。
SetBusyIndicatorメソッドを検討して
SetBusyIndicator 。
private static readonly DependencyProperty _busyIndicatorProperty = DependencyProperty.RegisterAttached("%BusyIndicatorProperty%", typeof (ContentControl), typeof (DependencyObject)); private static void SetBusyIndicator(DependencyObject contentObject, DependencyObject busyIndicator) { if (contentObject != null) { SetPropertyChangedCallback(GetItemsSourceValue(contentObject), contentObject, ItemsSourceChangedCallback, busyIndicator == null); contentObject.SetValue(_busyIndicatorProperty, busyIndicator); } UpdateBusyIndicator(busyIndicator, contentObject); } private static object GetItemsSourceValue(DependencyObject contentObject) { var itemsSourceProperty = contentObject.GetFieldValue("ItemsSourceProperty"); return contentObject == null ? null : contentObject.GetValue(itemsSourceProperty); } private static void UpdateBusyIndicator(DependencyObject busyIndicator, DependencyObject contentObject) { if (busyIndicator == null) return; if (contentObject == null) busyIndicator.SetPropValue("IsBusy", false); else { var itemsSource = contentObject == null ? null : contentObject.GetValue(GetItemsSourceValue(contentObject)); busyIndicator.SetPropValue("IsBusy", itemsSource == null); } } private static void ItemsSourceChangedCallback(DependencyObject contentObject, DependencyPropertyChangedEventArgs e) { var busyIndicator = contentObject == null ? null : contentObject.GetValue(_busyIndicatorProperty) as DependencyObject; UpdateBusyIndicator(busyIndicator, contentObject); }
このコードには説明も必要です。
最初に、
SetBusyIndicatorメソッドは、イベントハンドラーを設定して、
SetBusyIndicator方法で
dependency property ItemsSourceを変更し
dependency property ItemsSource 。
第二に、
contentObjectコントロールの
busyIndicatorインスタンスへのリンクを何らかの方法で保存する必要があります。これにより、
contentObjectは、どの
BusyIndicator 'yが
IsBusy属性を変更するかを
BusyIndicatorます。 これに対する最も簡単で明白な解決策は、別のプライベート
dependency property _busyIndicatorPropertyを使用することであるように思われました。
3
UpdateBusyIndicatorに、
UpdateBusyIndicatorメソッド
UpdateBusyIndicatorは、
_busyIndicatorPropertyを介して_busyIndicatorPropertyに格納されているBusyIndicatorのIsBusyプロパティの値を設定します。
完全なサンプルテキストご清聴ありがとうございました。