BusyIndi​​catorの添付プロパティを段階的に作成する

この記事は、 非同期操作だけでなく 、記事「 Automatic BusyIndi​​cator 」の続きです。


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) { } } 

興味深いことに、 SetAttachedSetAttached理論的には不要であり、スクリプトで呼び出されることはありませんが、これらがないと、 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を介して_busyIndi​​catorPropertyに格納されているBusyIndi​​catorのIsBusyプロパティの値を設定します。

完全なサンプルテキスト

ご清聴ありがとうございました。

Source: https://habr.com/ru/post/J142127/


All Articles