// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // This file is inspired from the MvvmLight library (lbugnion/MvvmLight), // more info in ThirdPartyNotices.txt in the root of the project. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; namespace CommunityToolkit.Mvvm.ComponentModel; /// /// A base class for observable objects that also acts as recipients for messages. This class is an extension of /// which also provides built-in support to use the type. /// public abstract class ObservableRecipient : ObservableObject { /// /// Initializes a new instance of the class. /// /// /// This constructor will produce an instance that will use the instance /// to perform requested operations. It will also be available locally through the property. /// protected ObservableRecipient() : this(WeakReferenceMessenger.Default) { } /// /// Initializes a new instance of the class. /// /// The instance to use to send messages. /// Thrown if is . protected ObservableRecipient(IMessenger messenger) { ArgumentNullException.ThrowIfNull(messenger); Messenger = messenger; } /// /// Gets the instance in use. /// protected IMessenger Messenger { get; } private bool isActive; /// /// Gets or sets a value indicating whether the current view model is currently active. /// public bool IsActive { get => this.isActive; set { if (SetProperty(ref this.isActive, value, true)) { if (value) { OnActivated(); } else { OnDeactivated(); } } } } /// /// Raised whenever the property is set to . /// Use this method to register to messages and do other initialization for this instance. /// /// /// The base implementation registers all messages for this recipients that have been declared /// explicitly through the interface, using the default channel. /// For more details on how this works, see the method. /// If you need more fine tuned control, want to register messages individually or just prefer /// the lambda-style syntax for message registration, override this method and register manually. /// protected virtual void OnActivated() { Messenger.RegisterAll(this); } /// /// Raised whenever the property is set to . /// Use this method to unregister from messages and do general cleanup for this instance. /// /// /// The base implementation unregisters all messages for this recipient. It does so by /// invoking , which removes all registered /// handlers for a given subscriber, regardless of what token was used to register them. /// That is, all registered handlers across all subscription channels will be removed. /// protected virtual void OnDeactivated() { Messenger.UnregisterAll(this); } /// /// Broadcasts a with the specified /// parameters, without using any particular token (so using the default channel). /// /// The type of the property that changed. /// The value of the property before it changed. /// The value of the property after it changed. /// The name of the property that changed. /// /// You should override this method if you wish to customize the channel being /// used to send the message (eg. if you need to use a specific token for the channel). /// protected virtual void Broadcast(T oldValue, T newValue, string? propertyName) { PropertyChangedMessage message = new(this, propertyName, oldValue, newValue); _ = Messenger.Send(message); } /// /// Compares the current and new values for a given property. If the value has changed, /// raises the event, updates the property with /// the new value, then raises the event. /// /// The type of the property that changed. /// The field storing the property's value. /// The property's value after the change occurred. /// If , will also be invoked. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// /// This method is just like , just with the addition /// of the parameter. As such, following the behavior of the base method, /// the and events /// are not raised if the current and new value for the target property are the same. /// protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newValue, bool broadcast, [CallerMemberName] string? propertyName = null) { T oldValue = field; // We duplicate the code as in the base class here to leverage // the intrinsics support for EqualityComparer.Default.Equals. bool propertyChanged = SetProperty(ref field, newValue, propertyName); if (propertyChanged && broadcast) { Broadcast(oldValue, newValue, propertyName); } return propertyChanged; } /// /// Compares the current and new values for a given property. If the value has changed, /// raises the event, updates the property with /// the new value, then raises the event. /// See additional notes about this overload in . /// /// The type of the property that changed. /// The field storing the property's value. /// The property's value after the change occurred. /// The instance to use to compare the input values. /// If , will also be invoked. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// Thrown if is . protected bool SetProperty([NotNullIfNotNull("newValue")] ref T field, T newValue, IEqualityComparer comparer, bool broadcast, [CallerMemberName] string? propertyName = null) { ArgumentNullException.ThrowIfNull(comparer); T oldValue = field; bool propertyChanged = SetProperty(ref field, newValue, comparer, propertyName); if (propertyChanged && broadcast) { Broadcast(oldValue, newValue, propertyName); } return propertyChanged; } /// /// Compares the current and new values for a given property. If the value has changed, /// raises the event, updates the property with /// the new value, then raises the event. Similarly to /// the method, this overload should only be /// used when can't be used directly. /// /// The type of the property that changed. /// The current property value. /// The property's value after the change occurred. /// A callback to invoke to update the property value. /// If , will also be invoked. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// /// This method is just like , just with the addition /// of the parameter. As such, following the behavior of the base method, /// the and events /// are not raised if the current and new value for the target property are the same. /// /// Thrown if is . protected bool SetProperty(T oldValue, T newValue, Action callback, bool broadcast, [CallerMemberName] string? propertyName = null) { ArgumentNullException.ThrowIfNull(callback); bool propertyChanged = SetProperty(oldValue, newValue, callback, propertyName); if (propertyChanged && broadcast) { Broadcast(oldValue, newValue, propertyName); } return propertyChanged; } /// /// Compares the current and new values for a given property. If the value has changed, /// raises the event, updates the property with /// the new value, then raises the event. /// See additional notes about this overload in . /// /// The type of the property that changed. /// The current property value. /// The property's value after the change occurred. /// The instance to use to compare the input values. /// A callback to invoke to update the property value. /// If , will also be invoked. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// Thrown if or are . protected bool SetProperty(T oldValue, T newValue, IEqualityComparer comparer, Action callback, bool broadcast, [CallerMemberName] string? propertyName = null) { ArgumentNullException.ThrowIfNull(comparer); ArgumentNullException.ThrowIfNull(callback); bool propertyChanged = SetProperty(oldValue, newValue, comparer, callback, propertyName); if (propertyChanged && broadcast) { Broadcast(oldValue, newValue, propertyName); } return propertyChanged; } /// /// Compares the current and new values for a given nested property. If the value has changed, /// raises the event, updates the property and then raises the /// event. The behavior mirrors that of /// , with the difference being that this /// method is used to relay properties from a wrapped model in the current instance. For more info, see the docs for /// . /// /// The type of model whose property (or field) to set. /// The type of property (or field) to set. /// The current property value. /// The property's value after the change occurred. /// The model /// The callback to invoke to set the target property value, if a change has occurred. /// If , will also be invoked. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// Thrown if or are . protected bool SetProperty(T oldValue, T newValue, TModel model, Action callback, bool broadcast, [CallerMemberName] string? propertyName = null) where TModel : class { ArgumentNullException.ThrowIfNull(model); ArgumentNullException.ThrowIfNull(callback); bool propertyChanged = SetProperty(oldValue, newValue, model, callback, propertyName); if (propertyChanged && broadcast) { Broadcast(oldValue, newValue, propertyName); } return propertyChanged; } /// /// Compares the current and new values for a given nested property. If the value has changed, /// raises the event, updates the property and then raises the /// event. The behavior mirrors that of /// , /// with the difference being that this method is used to relay properties from a wrapped model in the /// current instance. For more info, see the docs for /// . /// /// The type of model whose property (or field) to set. /// The type of property (or field) to set. /// The current property value. /// The property's value after the change occurred. /// The instance to use to compare the input values. /// The model /// The callback to invoke to set the target property value, if a change has occurred. /// If , will also be invoked. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// Thrown if , or are . protected bool SetProperty(T oldValue, T newValue, IEqualityComparer comparer, TModel model, Action callback, bool broadcast, [CallerMemberName] string? propertyName = null) where TModel : class { ArgumentNullException.ThrowIfNull(comparer); ArgumentNullException.ThrowIfNull(model); ArgumentNullException.ThrowIfNull(callback); bool propertyChanged = SetProperty(oldValue, newValue, comparer, model, callback, propertyName); if (propertyChanged && broadcast) { Broadcast(oldValue, newValue, propertyName); } return propertyChanged; } }