| | | 1 | | // ----------------------------------------------------------------------- |
| | | 2 | | // <copyright file="PropertyChangedForwardingBehavior.cs" company="Stéphane ANDRE"> |
| | | 3 | | // Copyright (c) Stéphane ANDRE. All rights reserved. |
| | | 4 | | // </copyright> |
| | | 5 | | // ----------------------------------------------------------------------- |
| | | 6 | | |
| | | 7 | | using System; |
| | | 8 | | using System.ComponentModel; |
| | | 9 | | using System.Threading; |
| | | 10 | | |
| | | 11 | | namespace MyNet.Observable.Behaviors; |
| | | 12 | | |
| | | 13 | | /// <summary> |
| | | 14 | | /// Relays PropertyChanged notifications from a wrapper object (a property of the owner) |
| | | 15 | | /// to the owner. The behavior watches a named property on the owner; when that |
| | | 16 | | /// property's value implements <see cref="INotifyPropertyChanged"/>, the behavior |
| | | 17 | | /// subscribes to its PropertyChanged and forwards notifications to the owner by |
| | | 18 | | /// calling <see cref="ObservableObject.NotifyPropertyChanged(string, object?, object?)"/> with either |
| | | 19 | | /// the child property name or a qualified name (<c>Wrapper.Property</c>) depending |
| | | 20 | | /// on configuration. |
| | | 21 | | /// </summary> |
| | | 22 | | public sealed class PropertyChangedForwardingBehavior : SuspendableBehavior<ObservableObject>, IPropertyChangedBehavior |
| | | 23 | | { |
| | | 24 | | private readonly string _propertyName; |
| | | 25 | | private readonly bool _concatenatePropertyName; |
| | | 26 | | private readonly bool _raiseInitialSnapshot; |
| | 42 | 27 | | private readonly Lock _gate = new(); |
| | | 28 | | private INotifyPropertyChanged? _source; |
| | | 29 | | |
| | | 30 | | /// <summary> |
| | | 31 | | /// Initializes a new instance of the <see cref="PropertyChangedForwardingBehavior"/> class. |
| | | 32 | | /// </summary> |
| | | 33 | | /// <param name="owner">Owner observable object.</param> |
| | | 34 | | /// <param name="propertyName">The owner property that contains the wrapper source.</param> |
| | | 35 | | /// <param name="concatenatePropertyName">True to emit notifications like Wrapper.Name; false to emit only Name.</pa |
| | | 36 | | /// <param name="raiseInitialSnapshot">When <c>true</c>, relays a snapshot of the source's public properties after e |
| | | 37 | | public PropertyChangedForwardingBehavior( |
| | | 38 | | ObservableObject owner, |
| | | 39 | | string propertyName, |
| | | 40 | | bool concatenatePropertyName = true, |
| | | 41 | | bool raiseInitialSnapshot = true) |
| | 42 | 42 | | : base(owner) |
| | | 43 | | { |
| | 42 | 44 | | _propertyName = propertyName; |
| | 42 | 45 | | _concatenatePropertyName = concatenatePropertyName; |
| | 42 | 46 | | _raiseInitialSnapshot = raiseInitialSnapshot; |
| | | 47 | | |
| | 42 | 48 | | Attach(ObservableObjectPropertyAccess.GetPropertyValue(Owner, _propertyName) as INotifyPropertyChanged); |
| | 42 | 49 | | } |
| | | 50 | | |
| | | 51 | | /// <inheritdoc /> |
| | | 52 | | public void OnPropertyChanged(PropertyMutationContext context) |
| | | 53 | | { |
| | 60 | 54 | | if (IsDisposed || IsSuspended) |
| | 0 | 55 | | return; |
| | | 56 | | |
| | 60 | 57 | | if (!string.Equals(context.PropertyName, _propertyName, StringComparison.Ordinal)) |
| | 51 | 58 | | return; |
| | | 59 | | |
| | 9 | 60 | | Attach(context.NewValue as INotifyPropertyChanged); |
| | 9 | 61 | | } |
| | | 62 | | |
| | | 63 | | /// <summary> |
| | | 64 | | /// Attaches to the specified source object by subscribing to its PropertyChanged event. |
| | | 65 | | /// </summary> |
| | | 66 | | /// <param name="value">The source object to attach to.</param> |
| | | 67 | | private void Attach(INotifyPropertyChanged? value) |
| | 51 | 68 | | { |
| | | 69 | | INotifyPropertyChanged? attached; |
| | | 70 | | |
| | | 71 | | lock (_gate) |
| | | 72 | | { |
| | 51 | 73 | | if (ReferenceEquals(_source, value)) |
| | 0 | 74 | | return; |
| | | 75 | | |
| | 51 | 76 | | _source?.PropertyChanged -= OnSourcePropertyChanged; |
| | | 77 | | |
| | 51 | 78 | | _source = value; |
| | | 79 | | |
| | 51 | 80 | | attached = _source; |
| | | 81 | | |
| | 51 | 82 | | attached?.PropertyChanged += OnSourcePropertyChanged; |
| | 51 | 83 | | } |
| | | 84 | | |
| | 51 | 85 | | if (attached is not null && _raiseInitialSnapshot) |
| | 51 | 86 | | RaiseInitialSnapshot(attached); |
| | 51 | 87 | | } |
| | | 88 | | |
| | | 89 | | /// <summary> |
| | | 90 | | /// Relays current values of the source's public properties (attach-time snapshot). |
| | | 91 | | /// </summary> |
| | | 92 | | private void RaiseInitialSnapshot(INotifyPropertyChanged source) |
| | | 93 | | { |
| | 51 | 94 | | if (IsDisposed || IsSuspended) |
| | 0 | 95 | | return; |
| | | 96 | | |
| | 204 | 97 | | foreach (var (childPropertyName, _) in NotifyPropertyChangedSourceSnapshot.EnumerateValues(source)) |
| | 51 | 98 | | RelayPropertyChanged(childPropertyName); |
| | 51 | 99 | | } |
| | | 100 | | |
| | | 101 | | /// <summary> |
| | | 102 | | /// Handles PropertyChanged events from the source object and relays them to the owner. |
| | | 103 | | /// </summary> |
| | | 104 | | /// <param name="sender">The source object that raised the event.</param> |
| | | 105 | | /// <param name="e">The event arguments containing the name of the property that changed.</param> |
| | | 106 | | private void OnSourcePropertyChanged(object? sender, PropertyChangedEventArgs e) |
| | | 107 | | { |
| | 27 | 108 | | if (IsDisposed || IsSuspended) |
| | 0 | 109 | | return; |
| | | 110 | | |
| | 27 | 111 | | if (string.IsNullOrWhiteSpace(e.PropertyName)) |
| | 0 | 112 | | return; |
| | | 113 | | |
| | | 114 | | lock (_gate) |
| | | 115 | | { |
| | 27 | 116 | | if (!ReferenceEquals(sender, _source)) |
| | 0 | 117 | | return; |
| | 27 | 118 | | } |
| | | 119 | | |
| | 27 | 120 | | RelayPropertyChanged(e.PropertyName); |
| | 27 | 121 | | } |
| | | 122 | | |
| | | 123 | | private void RelayPropertyChanged(string childPropertyName) |
| | | 124 | | { |
| | 78 | 125 | | if (IsDisposed || IsSuspended) |
| | 0 | 126 | | return; |
| | | 127 | | |
| | 78 | 128 | | var relayedName = _concatenatePropertyName ? $"{_propertyName}.{childPropertyName}" : childPropertyName; |
| | 78 | 129 | | Owner.NotifyPropertyChanged(relayedName, UnknownValue.Instance, UnknownValue.Instance); |
| | 78 | 130 | | } |
| | | 131 | | |
| | | 132 | | /// <inheritdoc/> |
| | | 133 | | protected override void DisposeManagedResources() |
| | 18 | 134 | | { |
| | | 135 | | lock (_gate) |
| | | 136 | | { |
| | 18 | 137 | | _source?.PropertyChanged -= OnSourcePropertyChanged; |
| | 18 | 138 | | _source = null; |
| | 18 | 139 | | } |
| | | 140 | | |
| | 18 | 141 | | base.DisposeManagedResources(); |
| | 18 | 142 | | } |
| | | 143 | | } |
| | | 144 | | |