attached property
all we need to write in XAML is: <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) { } }
GetAttached
and SetAttached
theoretically not needed and are never called in our script, but the attached property
will not be available without them.AttachedChanged
event method. The general idea is this: look for the content BusyIndicator
'and the ItemsSource
property (more precisely, the dependency property
ItemsSourceProperty
), if such a property is found, we intercept the moment of this property change. If its value is null
- turn on the indicator, otherwise turn it off.AttachedChanged
content of BusyIndicator
is not yet established, which is not surprising.ContentChanged
in ContentControl
, so I had to go around it: 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
method receives metadata of dependency property ContentProperty
and adds (or removes) an event handler for changing the value of this property.PropertyMetadata.cs
module showed that the indication of this initialization is the internal
Sealed
property. In order not to overload the code of the example, I do not give an implementation of the SetPropValu
method, but I think it would be easy for anyone to write a change in the property of an object through reflection.ContentChangedCallback
method will be called up at the time of installation of new content for BusyIndicator
'a. The first line of this method is very important - because this method will be called for all BusyIndicator
, and not just the one in which we set the AsyncIndicator.Attached="true"
property. AsyncIndicator.Attached="true"
. Therefore, we check that the value of this property is exactly true
.SetBusyIndicator
method: 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
method sets an event handler to change the dependency property ItemsSource
change in a way we already know.busyIndicator
in the contentObject
control in contentObject
for the contentObject
know to which BusyIndicator
'y to change the IsBusy
attribute. The simplest and most obvious solution for this, it seemed to me, was to use another private dependency property _busyIndicatorProperty
.UpdateBusyIndicator
method UpdateBusyIndicator
we set the reflection to set the IsBusy property of our BusyIndicator stored in _busyIndicatorProperty
.Source: https://habr.com/ru/post/142127/
All Articles