1+ // Licensed to the .NET Foundation under one or more agreements.
2+ // The .NET Foundation licenses this file to you under the MIT license.
3+ // See the LICENSE file in the project root for more information.
4+
5+ #pragma warning disable SA1512
6+
7+ // This file is inspired from the MvvmLight libray (lbugnion/mvvmlight),
8+ // more info in ThirdPartyNotices.txt in the root of the project.
9+
10+ using System ;
11+ using System . Collections . Generic ;
12+ using System . Runtime . CompilerServices ;
13+ using Microsoft . Toolkit . Mvvm . Messaging ;
14+ using Microsoft . Toolkit . Mvvm . Messaging . Messages ;
15+
16+ namespace Microsoft . Toolkit . Mvvm . ComponentModel
17+ {
18+ /// <summary>
19+ /// A base class for observable objects that also acts as recipients for messages. This class is an extension of
20+ /// <see cref="ObservableObject"/> which also provides built-in support to use the <see cref="IMessenger"/> type.
21+ /// </summary>
22+ public abstract class ObservableRecipient : ObservableObject
23+ {
24+ /// <summary>
25+ /// Initializes a new instance of the <see cref="ObservableRecipient"/> class.
26+ /// </summary>
27+ /// <remarks>
28+ /// This constructor will produce an instance that will use the <see cref="Messaging.Messenger.Default"/> instance
29+ /// to perform requested operations. It will also be available locally through the <see cref="Messenger"/> property.
30+ /// </remarks>
31+ protected ObservableRecipient ( )
32+ : this ( Messaging . Messenger . Default )
33+ {
34+ }
35+
36+ /// <summary>
37+ /// Initializes a new instance of the <see cref="ObservableRecipient"/> class.
38+ /// </summary>
39+ /// <param name="messenger">The <see cref="IMessenger"/> instance to use to send messages.</param>
40+ protected ObservableRecipient ( IMessenger messenger )
41+ {
42+ Messenger = messenger ;
43+ }
44+
45+ /// <summary>
46+ /// Gets the <see cref="IMessenger"/> instance in use.
47+ /// </summary>
48+ protected IMessenger Messenger { get ; }
49+
50+ private bool isActive ;
51+
52+ /// <summary>
53+ /// Gets or sets a value indicating whether the current view model is currently active.
54+ /// </summary>
55+ public bool IsActive
56+ {
57+ get => this . isActive ;
58+ set
59+ {
60+ if ( SetProperty ( ref this . isActive , value , true ) )
61+ {
62+ if ( value )
63+ {
64+ OnActivated ( ) ;
65+ }
66+ else
67+ {
68+ OnDeactivated ( ) ;
69+ }
70+ }
71+ }
72+ }
73+
74+ /// <summary>
75+ /// Raised whenever the <see cref="IsActive"/> property is set to <see langword="true"/>.
76+ /// Use this method to register to messages and do other initialization for this instance.
77+ /// </summary>
78+ /// <remarks>
79+ /// The base implementation registers all messages for this recipients that have been declared
80+ /// explicitly through the <see cref="IRecipient{TMessage}"/> interface, using the default channel.
81+ /// For more details on how this works, see the <see cref="MessengerExtensions.RegisterAll"/> method.
82+ /// If you need more fine tuned control, want to register messages individually or just prefer
83+ /// the lambda-style syntax for message registration, override this method and register manually.
84+ /// </remarks>
85+ protected virtual void OnActivated ( )
86+ {
87+ Messenger . RegisterAll ( this ) ;
88+ }
89+
90+ /// <summary>
91+ /// Raised whenever the <see cref="IsActive"/> property is set to <see langword="false"/>.
92+ /// Use this method to unregister from messages and do general cleanup for this instance.
93+ /// </summary>
94+ /// <remarks>
95+ /// The base implementation unregisters all messages for this recipient. It does so by
96+ /// invoking <see cref="IMessenger.UnregisterAll"/>, which removes all registered
97+ /// handlers for a given subscriber, regardless of what token was used to register them.
98+ /// That is, all registered handlers across all subscription channels will be removed.
99+ /// </remarks>
100+ protected virtual void OnDeactivated ( )
101+ {
102+ Messenger . UnregisterAll ( this ) ;
103+ }
104+
105+ /// <summary>
106+ /// Broadcasts a <see cref="PropertyChangedMessage{T}"/> with the specified
107+ /// parameters, without using any particular token (so using the default channel).
108+ /// </summary>
109+ /// <typeparam name="T">The type of the property that changed.</typeparam>
110+ /// <param name="oldValue">The value of the property before it changed.</param>
111+ /// <param name="newValue">The value of the property after it changed.</param>
112+ /// <param name="propertyName">The name of the property that changed.</param>
113+ /// <remarks>
114+ /// You should override this method if you wish to customize the channel being
115+ /// used to send the message (eg. if you need to use a specific token for the channel).
116+ /// </remarks>
117+ protected virtual void Broadcast < T > ( T oldValue , T newValue , string ? propertyName )
118+ {
119+ var message = new PropertyChangedMessage < T > ( this , propertyName , oldValue , newValue ) ;
120+
121+ Messenger . Send ( message ) ;
122+ }
123+
124+ /// <summary>
125+ /// Compares the current and new values for a given property. If the value has changed,
126+ /// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
127+ /// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
128+ /// </summary>
129+ /// <typeparam name="T">The type of the property that changed.</typeparam>
130+ /// <param name="field">The field storing the property's value.</param>
131+ /// <param name="newValue">The property's value after the change occurred.</param>
132+ /// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
133+ /// <param name="propertyName">(optional) The name of the property that changed.</param>
134+ /// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
135+ /// <remarks>
136+ /// This method is just like <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/>, just with the addition
137+ /// of the <paramref name="broadcast"/> parameter. As such, following the behavior of the base method,
138+ /// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events
139+ /// are not raised if the current and new value for the target property are the same.
140+ /// </remarks>
141+ protected bool SetProperty < T > ( ref T field , T newValue , bool broadcast , [ CallerMemberName ] string ? propertyName = null )
142+ {
143+ return SetProperty ( ref field , newValue , EqualityComparer < T > . Default , broadcast , propertyName ) ;
144+ }
145+
146+ /// <summary>
147+ /// Compares the current and new values for a given property. If the value has changed,
148+ /// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
149+ /// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
150+ /// See additional notes about this overload in <see cref="SetProperty{T}(ref T,T,bool,string)"/>.
151+ /// </summary>
152+ /// <typeparam name="T">The type of the property that changed.</typeparam>
153+ /// <param name="field">The field storing the property's value.</param>
154+ /// <param name="newValue">The property's value after the change occurred.</param>
155+ /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
156+ /// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
157+ /// <param name="propertyName">(optional) The name of the property that changed.</param>
158+ /// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
159+ protected bool SetProperty < T > ( ref T field , T newValue , IEqualityComparer < T > comparer , bool broadcast , [ CallerMemberName ] string ? propertyName = null )
160+ {
161+ if ( ! broadcast )
162+ {
163+ return SetProperty ( ref field , newValue , comparer , propertyName ) ;
164+ }
165+
166+ T oldValue = field ;
167+
168+ if ( SetProperty ( ref field , newValue , comparer , propertyName ) )
169+ {
170+ Broadcast ( oldValue , newValue , propertyName ) ;
171+
172+ return true ;
173+ }
174+
175+ return false ;
176+ }
177+
178+ /// <summary>
179+ /// Compares the current and new values for a given property. If the value has changed,
180+ /// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
181+ /// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event. Similarly to
182+ /// the <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/> method, this overload should only be
183+ /// used when <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/> can't be used directly.
184+ /// </summary>
185+ /// <typeparam name="T">The type of the property that changed.</typeparam>
186+ /// <param name="oldValue">The current property value.</param>
187+ /// <param name="newValue">The property's value after the change occurred.</param>
188+ /// <param name="callback">A callback to invoke to update the property value.</param>
189+ /// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
190+ /// <param name="propertyName">(optional) The name of the property that changed.</param>
191+ /// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
192+ /// <remarks>
193+ /// This method is just like <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/>, just with the addition
194+ /// of the <paramref name="broadcast"/> parameter. As such, following the behavior of the base method,
195+ /// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events
196+ /// are not raised if the current and new value for the target property are the same.
197+ /// </remarks>
198+ protected bool SetProperty < T > ( T oldValue , T newValue , Action < T > callback , bool broadcast , [ CallerMemberName ] string ? propertyName = null )
199+ {
200+ return SetProperty ( oldValue , newValue , EqualityComparer < T > . Default , callback , broadcast , propertyName ) ;
201+ }
202+
203+ /// <summary>
204+ /// Compares the current and new values for a given property. If the value has changed,
205+ /// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
206+ /// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
207+ /// See additional notes about this overload in <see cref="SetProperty{T}(T,T,Action{T},bool,string)"/>.
208+ /// </summary>
209+ /// <typeparam name="T">The type of the property that changed.</typeparam>
210+ /// <param name="oldValue">The current property value.</param>
211+ /// <param name="newValue">The property's value after the change occurred.</param>
212+ /// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
213+ /// <param name="callback">A callback to invoke to update the property value.</param>
214+ /// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
215+ /// <param name="propertyName">(optional) The name of the property that changed.</param>
216+ /// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
217+ protected bool SetProperty < T > ( T oldValue , T newValue , IEqualityComparer < T > comparer , Action < T > callback , bool broadcast , [ CallerMemberName ] string ? propertyName = null )
218+ {
219+ if ( ! broadcast )
220+ {
221+ return SetProperty ( oldValue , newValue , comparer , callback , propertyName ) ;
222+ }
223+
224+ if ( SetProperty ( oldValue , newValue , comparer , callback , propertyName ) )
225+ {
226+ Broadcast ( oldValue , newValue , propertyName ) ;
227+
228+ return true ;
229+ }
230+
231+ return false ;
232+ }
233+ }
234+ }
0 commit comments