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