この記事は、
非同期操作だけでなく 、記事「
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プロパティの値を設定します。
完全なサンプルテキストご清聴ありがとうございました。