Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ViewModelBase.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.ComponentModel;
6 using System.Linq;
7 using System.Runtime.CompilerServices;
8 
9 namespace SiliconStudio.Presentation.ViewModel
10 {
11  /// <summary>
12  /// This abstract class represents a basic view model, implementing <see cref="INotifyPropertyChanging"/> and <see cref="INotifyPropertyChanged"/> and providing
13  /// a set of <b>SetValue</b> helper methods to easly update a property and trigger the change notifications.
14  /// </summary>
16  {
17 #if DEBUG
18  private readonly List<string> changingProperties = new List<string>();
19 #endif
20 
21  /// <summary>
22  /// An <see cref="IViewModelServiceProvider"/> that allows to retrieve various service objects.
23  /// </summary>
24  public IViewModelServiceProvider ServiceProvider = ViewModelServiceProvider.NullServiceProvider;
25 
26  /// <summary>
27  /// A list of couple of property names that are dependent. For each couple of this list, if the first property name is notified as being changed, then
28  /// the second property name will also be notified has being changed.
29  /// </summary>
30  protected List<Tuple<string, string>> DependentProperties = new List<Tuple<string, string>>();
31 
32  protected ViewModelBase()
33  {
34  }
35 
36  protected ViewModelBase(IViewModelServiceProvider serviceProvider)
37  {
38  if (serviceProvider == null) throw new ArgumentNullException("serviceProvider");
39  ServiceProvider = serviceProvider;
40  }
41 
42  /// <summary>
43  /// Sets the value of a field to the given value. Both values are compared with the default <see cref="EqualityComparer{T}"/>, and if they are equals,
44  /// this method does nothing. If they are different, the <see cref="PropertyChanging"/> event will be raised first, then the field value will be modified,
45  /// and finally the <see cref="PropertyChanged"/> event will be raised.
46  /// </summary>
47  /// <typeparam name="T">The type of the field.</typeparam>
48  /// <param name="field">A reference to the field to set.</param>
49  /// <param name="value">The new value to set.</param>
50  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
51  /// <returns><c>True</c> if the field was modified and events were raised, <c>False</c> if the new value was equal to the old one and nothing was done.</returns>
52  protected bool SetValue<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
53  {
54  return SetValue(ref field, value, null, new[] { propertyName });
55  }
56 
57  /// <summary>
58  /// Sets the value of a field to the given value. Both values are compared with the default <see cref="EqualityComparer{T}"/>, and if they are equals,
59  /// this method does nothing. If they are different, the <see cref="PropertyChanging"/> will be raised first, then the field value will be modified,
60  /// and finally the <see cref="PropertyChanged"/> event will be raised.
61  /// </summary>
62  /// <typeparam name="T">The type of the field.</typeparam>
63  /// <param name="field">A reference to the field to set.</param>
64  /// <param name="value">The new value to set.</param>
65  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
66  /// <returns><c>True</c> if the field was modified and events were raised, <c>False</c> if the new value was equal to the old one and nothing was done.</returns>
67  protected bool SetValue<T>(ref T field, T value, params string[] propertyNames)
68  {
69  return SetValue(ref field, value, null, propertyNames);
70  }
71 
72  /// <summary>
73  /// Sets the value of a field to the given value. Both values are compared with the default <see cref="EqualityComparer{T}"/>, and if they are equals,
74  /// this method does nothing. If they are different, the <see cref="PropertyChanging"/> event will be raised first, then the field value will be modified.
75  /// The given update action will be executed and finally the <see cref="PropertyChanged"/> event will be raised.
76  /// </summary>
77  /// <typeparam name="T">The type of the field.</typeparam>
78  /// <param name="field">A reference to the field to set.</param>
79  /// <param name="value">The new value to set.</param>
80  /// <param name="updateAction">The update action to execute after setting the value. Can be <c>null</c>.</param>
81  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
82  /// <returns><c>True</c> if the field was modified and events were raised, <c>False</c> if the new value was equal to the old one and nothing was done.</returns>
83  protected bool SetValue<T>(ref T field, T value, Action updateAction, [CallerMemberName]string propertyName = null)
84  {
85  return SetValue(ref field, value, updateAction, new[] { propertyName });
86  }
87 
88  /// <summary>
89  /// Sets the value of a field to the given value. Both values are compared with the default <see cref="EqualityComparer{T}"/>, and if they are equals,
90  /// this method does nothing. If they are different, the <see cref="PropertyChanging"/> event will be raised first, then the field value will be modified.
91  /// The given update action will be executed and finally the <see cref="PropertyChanged"/> event will be raised.
92  /// </summary>
93  /// <typeparam name="T">The type of the field.</typeparam>
94  /// <param name="field">A reference to the field to set.</param>
95  /// <param name="value">The new value to set.</param>
96  /// <param name="updateAction">The update action to execute after setting the value. Can be <c>null</c>.</param>
97  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
98  /// <returns><c>True</c> if the field was modified and events were raised, <c>False</c> if the new value was equal to the old one and nothing was done.</returns>
99  protected virtual bool SetValue<T>(ref T field, T value, Action updateAction, params string[] propertyNames)
100  {
101  if (propertyNames.Length == 0)
102  throw new ArgumentOutOfRangeException("propertyNames", @"This method must be invoked with at least one property name.");
103 
104  if (EqualityComparer<T>.Default.Equals(field, value) == false)
105  {
106  OnPropertyChanging(propertyNames);
107  field = value;
108  if (updateAction != null)
109  updateAction();
110  OnPropertyChanged(propertyNames);
111  return true;
112  }
113 
114  return false;
115  }
116 
117  /// <summary>
118  /// Manages a property modification and its notifications. This method will invoke the provided update action. The <see cref="PropertyChanging"/>
119  /// event will be raised prior to the update action, and the <see cref="PropertyChanged"/> event will be raised after.
120  /// </summary>
121  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
122  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
123  /// <returns>This method always returns<c>True</c> since it always performs the update.</returns>
124  protected bool SetValue(Action updateAction, [CallerMemberName]string propertyName = null)
125  {
126  return SetValue(null, updateAction, new[] { propertyName });
127  }
128 
129  /// <summary>
130  /// Manages a property modification and its notifications. This method will invoke the provided update action. The <see cref="PropertyChanging"/>
131  /// event will be raised prior to the update action, and the <see cref="PropertyChanged"/> event will be raised after.
132  /// </summary>
133  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
134  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
135  /// <returns>This method always returns<c>True</c> since it always performs the update.</returns>
136  protected bool SetValue(Action updateAction, params string[] propertyNames)
137  {
138  return SetValue(null, updateAction, propertyNames);
139  }
140 
141  /// <summary>
142  /// Manages a property modification and its notifications. A function is provided to check whether the new value is different from the current one.
143  /// This function will be invoked by this method, and if it returns <c>True</c>, it will invoke the provided update action. The <see cref="PropertyChanging"/>
144  /// event will be raised prior to the update action, and the <see cref="PropertyChanged"/> event will be raised after.
145  /// </summary>
146  /// <param name="hasChangedFunction">A function that check if the new value is different and therefore if the update must be actually done.</param>
147  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
148  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
149  /// <returns><c>True</c> if the update was done and events were raised, <c>False</c> if <see cref="hasChangedFunction"/> is not <c>null</c> and returned false.</returns>
150  protected bool SetValue(Func<bool> hasChangedFunction, Action updateAction, [CallerMemberName]string propertyName = null)
151  {
152  return SetValue(hasChangedFunction, updateAction, new[] { propertyName });
153  }
154 
155  /// <summary>
156  /// Manages a property modification and its notifications. The first parameter <see cref="hasChanged"/> should indicate whether the property
157  /// should actuallybe updated. If this parameter is <c>True</c>, it will invoke the provided update action. The <see cref="PropertyChanging"/>
158  /// event will be raised prior to the update action, and the <see cref="PropertyChanged"/> event will be raised after.
159  /// </summary>
160  /// <param name="hasChanged">A boolean that indicates whether the update must be actually done. If <c>null</c>, the update is always done.</param>
161  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
162  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
163  /// <returns>The value provided in the <see cref="hasChanged"/> argument.</returns>
164  protected bool SetValue(bool hasChanged, Action updateAction, [CallerMemberName]string propertyName = null)
165  {
166  return SetValue(() => hasChanged, updateAction, new[] { propertyName });
167  }
168 
169  /// <summary>
170  /// Manages a property modification and its notifications. The first parameter <see cref="hasChanged"/> should indicate whether the property
171  /// should actuallybe updated. If this parameter is <c>True</c>, it will invoke the provided update action. The <see cref="PropertyChanging"/>
172  /// event will be raised prior to the update action, and the <see cref="PropertyChanged"/> event will be raised after.
173  /// </summary>
174  /// <param name="hasChanged">A boolean that indicates whether the update must be actually done. If <c>null</c>, the update is always done.</param>
175  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
176  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
177  /// <returns>The value provided in the <see cref="hasChanged"/> argument.</returns>
178  protected bool SetValue(bool hasChanged, Action updateAction, params string[] propertyNames)
179  {
180  return SetValue(() => hasChanged, updateAction, propertyNames);
181  }
182 
183  /// <summary>
184  /// Manages a property modification and its notifications. A function is provided to check whether the new value is different from the current one.
185  /// This function will be invoked by this method, and if it returns <c>True</c>, it will invoke the provided update action. The <see cref="PropertyChanging"/>
186  /// event will be raised prior to the update action, and the <see cref="PropertyChanged"/> event will be raised after.
187  /// </summary>
188  /// <param name="hasChangedFunction">A function that check if the new value is different and therefore if the update must be actually done.</param>
189  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
190  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
191  /// <returns><c>True</c> if the update was done and events were raised, <c>False</c> if <see cref="hasChangedFunction"/> is not <c>null</c> and returned false.</returns>
192  protected virtual bool SetValue(Func<bool> hasChangedFunction, Action updateAction, params string[] propertyNames)
193  {
194  if (propertyNames.Length == 0)
195  throw new ArgumentOutOfRangeException("propertyNames", @"This method must be invoked with at least one property name.");
196 
197  bool hasChanged = true;
198  if (hasChangedFunction != null)
199  {
200  hasChanged = hasChangedFunction();
201  }
202  if (hasChanged)
203  {
204  OnPropertyChanging(propertyNames);
205  if (updateAction != null)
206  updateAction();
207  OnPropertyChanged(propertyNames);
208  }
209  return hasChanged;
210  }
211 
212  /// <summary>
213  /// This method will raise the <see cref="PropertyChanging"/> for each of the property name passed as argument.
214  /// </summary>
215  /// <param name="propertyNames">The names of the properties that is changing.</param>
216  protected virtual void OnPropertyChanging(params string[] propertyNames)
217  {
218  var propertyChanging = PropertyChanging;
219 
220  foreach (string propertyName in propertyNames)
221  {
222 #if DEBUG
223  if (changingProperties.Contains(propertyName))
224  throw new InvalidOperationException(string.Format("OnPropertyChanging called twice for property '{0}' without invoking OnPropertyChanged between calls.", propertyName));
225 
226  changingProperties.Add(propertyName);
227 #endif
228 
229  if (propertyChanging != null)
230  {
231  propertyChanging(this, new PropertyChangingEventArgs(propertyName));
232  }
233  string name = propertyName;
234  OnPropertyChanging(DependentProperties.Where(x => x.Item1 == name).Select(x => x.Item2).Distinct().ToArray());
235  }
236  }
237 
238  /// <summary>
239  /// This method will raise the <see cref="PropertyChanged"/> for each of the property name passed as argument.
240  /// </summary>
241  /// <param name="propertyNames">The names of the properties that has changed.</param>
242  protected virtual void OnPropertyChanged(params string[] propertyNames)
243  {
244  var propertyChanged = PropertyChanged;
245 
246  foreach (string propertyName in propertyNames.Reverse())
247  {
248  string name = propertyName;
249  OnPropertyChanged(DependentProperties.Where(x => x.Item1 == name).Select(x => x.Item2).Distinct().Reverse().ToArray());
250  if (propertyChanged != null)
251  {
252  propertyChanged(this, new PropertyChangedEventArgs(propertyName));
253  }
254 
255 #if DEBUG
256  if (!changingProperties.Contains(propertyName))
257  throw new InvalidOperationException(string.Format("OnPropertyChanged called for property '{0}' but OnPropertyChanging was not invoked before.", propertyName));
258 
259  changingProperties.Remove(propertyName);
260 #endif
261  }
262  }
263 
264  /// <inheritdoc/>
265  public event PropertyChangingEventHandler PropertyChanging;
266 
267  /// <inheritdoc/>
268  public event PropertyChangedEventHandler PropertyChanged;
269  }
270 }
bool SetValue(bool hasChanged, Action updateAction, params string[] propertyNames)
Manages a property modification and its notifications. The first parameter hasChanged should indicate...
virtual void OnPropertyChanged(params string[] propertyNames)
This method will raise the PropertyChanged for each of the property name passed as argument...
ViewModelBase(IViewModelServiceProvider serviceProvider)
virtual bool SetValue(Func< bool > hasChangedFunction, Action updateAction, params string[] propertyNames)
Manages a property modification and its notifications. A function is provided to check whether the ne...
bool SetValue(bool hasChanged, Action updateAction, [CallerMemberName]string propertyName=null)
Manages a property modification and its notifications. The first parameter hasChanged should indicate...
This abstract class represents a basic view model, implementing INotifyPropertyChanging and INotifyPr...
bool SetValue(Func< bool > hasChangedFunction, Action updateAction, [CallerMemberName]string propertyName=null)
Manages a property modification and its notifications. A function is provided to check whether the ne...
virtual void OnPropertyChanging(params string[] propertyNames)
This method will raise the PropertyChanging for each of the property name passed as argument...
bool SetValue(Action updateAction, params string[] propertyNames)
Manages a property modification and its notifications. This method will invoke the provided update ac...
bool SetValue(Action updateAction, [CallerMemberName]string propertyName=null)
Manages a property modification and its notifications. This method will invoke the provided update ac...