Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
EditableViewModel.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;
5 using System.Collections.Generic;
6 using System.Collections.Specialized;
7 using System.Linq;
8 using System.Reflection;
9 using System.Runtime.CompilerServices;
10 
11 using SiliconStudio.ActionStack;
12 using SiliconStudio.Presentation.Services;
13 using SiliconStudio.Presentation.ViewModel.ActionStack;
14 
15 namespace SiliconStudio.Presentation.ViewModel
16 {
17  /// <summary>
18  /// This class is an implementation of the <see cref="DispatcherViewModel"/> class that supports undo/redo of property and collection changes.
19  /// It requires an <see cref="ITransactionalActionStack"/> and can be linked to one or several <see cref="IDirtiableViewModel"/> objects.
20  /// The dirtiable objects will have their dirty flag updated accordingly to the state of the action stack.
21  /// </summary>
22  /// <remarks>
23  /// When one of the <c>SetValue</c> methods is invoked, it will automatically create an <see cref="IActionItem"/> and add it to the
24  /// registered <see cref="ITransactionalActionStack"/>. To modify a property without creating action items, use one of the <c>SetValueUncancellable</c>
25  /// methods, such as <see cref="SetValueUncancellable{T}(ref T, T, string)"/>.
26  /// </remarks>
27  /// <remarks>This class is abstract because it does not provide a default implementation of the <see cref="Dirtiables"/> property.</remarks>
28  public abstract class EditableViewModel : DispatcherViewModel
29  {
30  private readonly Dictionary<string, object> preEditValues = new Dictionary<string, object>();
31 
32  /// <summary>
33  /// Initializes a new instance of the <see cref="EditableViewModel"/> class.
34  /// </summary>
35  /// <param name="serviceProvider">A service provider that can provide a <see cref="IDispatcherService"/> and an <see cref="ITransactionalActionStack"/> to use for this view model.</param>
36  protected EditableViewModel(IViewModelServiceProvider serviceProvider)
37  : base(serviceProvider)
38  {
39  ActionStack = serviceProvider.Get<ITransactionalActionStack>();
40  }
41 
42  /// <summary>
43  /// Gets the list of <see cref="IDirtiableViewModel"/> objects linked to this view model.
44  /// </summary>
45  /// <remarks>Dirtiable objects will have their dirty flag updated when a change occurs or when the action stack is notified that modifications have been saved.</remarks>
46  public abstract IEnumerable<IDirtiableViewModel> Dirtiables { get; }
47 
48  /// <summary>
49  /// Gets the transactional action stack used by this view model.
50  /// </summary>
51  public ITransactionalActionStack ActionStack { get; private set; }
52 
53  /// <summary>
54  /// Registers the given collection to create <see cref="CollectionChangedViewModelActionItem"/> in the action stack when it is modified.
55  /// </summary>
56  /// <param name="name">The name of the collection (used only for formatting the display name of the action item).</param>
57  /// <param name="collection">The collection to register.</param>
58  protected void RegisterMemberCollectionForActionStack(string name, INotifyCollectionChanged collection)
59  {
60  if (collection == null) throw new ArgumentNullException("collection");
61  collection.CollectionChanged += (sender, e) => CollectionChanged(sender, e, name);
62  }
63 
64  /// <summary>
65  /// 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,
66  /// this method does nothing. If they are different, the <see cref="ViewModelBase.PropertyChanging"/> event will be raised first, then the field value will be modified,
67  /// and finally the <see cref="ViewModelBase.PropertyChanged"/> event will be raised.
68  /// </summary>
69  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
70  /// <typeparam name="T">The type of the field.</typeparam>
71  /// <param name="field">A reference to the field to set.</param>
72  /// <param name="value">The new value to set.</param>
73  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
74  /// <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>
75  protected bool SetValueUncancellable<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
76  {
77  return SetValueUncancellable(ref field, value, null, new[] { propertyName });
78  }
79 
80  /// <summary>
81  /// 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,
82  /// this method does nothing. If they are different, the <see cref="ViewModelBase.PropertyChanging"/> event will be raised first, then the field value will be modified.
83  /// The given update action will be executed and finally the <see cref="ViewModelBase.PropertyChanged"/> event will be raised.
84  /// </summary>
85  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
86  /// <typeparam name="T">The type of the field.</typeparam>
87  /// <param name="field">A reference to the field to set.</param>
88  /// <param name="value">The new value to set.</param>
89  /// <param name="updateAction">The update action to execute after setting the value. Can be <c>null</c>.</param>
90  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
91  /// <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>
92  protected bool SetValueUncancellable<T>(ref T field, T value, Action updateAction, [CallerMemberName]string propertyName = null)
93  {
94  return SetValueUncancellable(ref field, value, updateAction, new[] { propertyName });
95  }
96 
97  /// <summary>
98  /// 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,
99  /// this method does nothing. If they are different, the <see cref="ViewModelBase.PropertyChanging"/> will be raised first, then the field value will be modified,
100  /// and finally the <see cref="ViewModelBase.PropertyChanged"/> event will be raised.
101  /// </summary>
102  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
103  /// <typeparam name="T">The type of the field.</typeparam>
104  /// <param name="field">A reference to the field to set.</param>
105  /// <param name="value">The new value to set.</param>
106  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
107  /// <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>
108  protected bool SetValueUncancellable<T>(ref T field, T value, params string[] propertyNames)
109  {
110  return SetValueUncancellable(ref field, value, null, propertyNames);
111  }
112 
113  /// <summary>
114  /// 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,
115  /// this method does nothing. If they are different, the <see cref="ViewModelBase.PropertyChanging"/> event will be raised first, then the field value will be modified.
116  /// The given update action will be executed and finally the <see cref="ViewModelBase.PropertyChanged"/> event will be raised.
117  /// </summary>
118  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
119  /// <typeparam name="T">The type of the field.</typeparam>
120  /// <param name="field">A reference to the field to set.</param>
121  /// <param name="value">The new value to set.</param>
122  /// <param name="updateAction">The update action to execute after setting the value. Can be <c>null</c>.</param>
123  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
124  /// <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>
125  protected bool SetValueUncancellable<T>(ref T field, T value, Action updateAction, params string[] propertyNames)
126  {
127  ActionStack.BeginTransaction();
128 
129  try
130  {
131  var result = SetValue(ref field, value, updateAction, propertyNames);
132  return result;
133  }
134  finally
135  {
136  ActionStack.DiscardTransaction();
137  }
138  }
139 
140  /// <summary>
141  /// Manages a property modification and its notifications. This method will invoke the provided update action. The <see cref="ViewModelBase.PropertyChanging"/>
142  /// event will be raised prior to the update action, and the <see cref="ViewModelBase.PropertyChanged"/> event will be raised after.
143  /// </summary>
144  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
145  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
146  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
147  /// <returns>This method always returns<c>True</c> since it always performs the update.</returns>
148  protected bool SetValueUncancellable(Action updateAction, [CallerMemberName]string propertyName = null)
149  {
150  return SetValueUncancellable(null, updateAction, new[] { propertyName });
151  }
152 
153  /// <summary>
154  /// Manages a property modification and its notifications. This method will invoke the provided update action. The <see cref="ViewModelBase.PropertyChanging"/>
155  /// event will be raised prior to the update action, and the <see cref="ViewModelBase.PropertyChanged"/> event will be raised after.
156  /// </summary>
157  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
158  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
159  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
160  /// <returns>This method always returns<c>True</c> since it always performs the update.</returns>
161  protected bool SetValueUncancellable(Action updateAction, params string[] propertyNames)
162  {
163  return SetValueUncancellable(null, updateAction, propertyNames);
164  }
165 
166  /// <summary>
167  /// Manages a property modification and its notifications. A function is provided to check whether the new value is different from the current one.
168  /// 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="ViewModelBase.PropertyChanging"/>
169  /// event will be raised prior to the update action, and the <see cref="ViewModelBase.PropertyChanged"/> event will be raised after.
170  /// </summary>
171  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
172  /// <param name="hasChangedFunction">A function that check if the new value is different and therefore if the update must be actually done.</param>
173  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
174  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
175  /// <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>
176  protected bool SetValueUncancellable(Func<bool> hasChangedFunction, Action updateAction, [CallerMemberName]string propertyName = null)
177  {
178  return SetValueUncancellable(hasChangedFunction, updateAction, new[] { propertyName });
179  }
180 
181  /// <summary>
182  /// Manages a property modification and its notifications. The first parameter <see cref="hasChanged"/> should indicate whether the property
183  /// should actuallybe updated. If this parameter is <c>True</c>, it will invoke the provided update action. The <see cref="ViewModelBase.PropertyChanging"/>
184  /// event will be raised prior to the update action, and the <see cref="ViewModelBase.PropertyChanged"/> event will be raised after.
185  /// </summary>
186  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
187  /// <param name="hasChanged">A boolean that indicates whether the update must be actually done. If <c>null</c>, the update is always done.</param>
188  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
189  /// <param name="propertyName">The name of the property that must be notified as changing/changed. Can use <see cref="CallerMemberNameAttribute"/>.</param>
190  /// <returns>The value provided in the <see cref="hasChanged"/> argument.</returns>
191  protected bool SetValueUncancellable(bool hasChanged, Action updateAction, [CallerMemberName]string propertyName = null)
192  {
193  return SetValueUncancellable(() => hasChanged, updateAction, new[] { propertyName });
194  }
195 
196  /// <summary>
197  /// Manages a property modification and its notifications. The first parameter <see cref="hasChanged"/> should indicate whether the property
198  /// should actuallybe updated. If this parameter is <c>True</c>, it will invoke the provided update action. The <see cref="ViewModelBase.PropertyChanging"/>
199  /// event will be raised prior to the update action, and the <see cref="ViewModelBase.PropertyChanged"/> event will be raised after.
200  /// </summary>
201  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
202  /// <param name="hasChanged">A boolean that indicates whether the update must be actually done. If <c>null</c>, the update is always done.</param>
203  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
204  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
205  /// <returns>The value provided in the <see cref="hasChanged"/> argument.</returns>
206  protected bool SetValueUncancellable(bool hasChanged, Action updateAction, params string[] propertyNames)
207  {
208  return SetValueUncancellable(() => hasChanged, updateAction, propertyNames);
209  }
210 
211  /// <summary>
212  /// Manages a property modification and its notifications. A function is provided to check whether the new value is different from the current one.
213  /// 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="ViewModelBase.PropertyChanging"/>
214  /// event will be raised prior to the update action, and the <see cref="ViewModelBase.PropertyChanged"/> event will be raised after.
215  /// </summary>
216  /// <remarks>This method does not register <see cref="IActionItem"/> into the associated <see cref="ITransactionalActionStack"/>.</remarks>
217  /// <param name="hasChangedFunction">A function that check if the new value is different and therefore if the update must be actually done.</param>
218  /// <param name="updateAction">The update action that will actually manage the update of the property.</param>
219  /// <param name="propertyNames">The names of the properties that must be notified as changing/changed. At least one property name must be provided.</param>
220  /// <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>
221  protected virtual bool SetValueUncancellable(Func<bool> hasChangedFunction, Action updateAction, params string[] propertyNames)
222  {
223  ActionStack.BeginTransaction();
224 
225  try
226  {
227  var result = SetValue(hasChangedFunction, updateAction, propertyNames);
228  return result;
229  }
230  finally
231  {
232  ActionStack.DiscardTransaction();
233  }
234  }
235 
236  /// <inheritdoc/>
237  protected override bool SetValue<T>(ref T field, T value, Action updateAction, params string[] propertyNames)
238  {
239  if (propertyNames.Length == 0)
240  throw new ArgumentOutOfRangeException("propertyNames", @"This method must be invoked with at least one property name.");
241 
242  if (EqualityComparer<T>.Default.Equals(field, value) == false)
243  {
244  string concatPropertyName = string.Join(", ", propertyNames.Select(s => string.Format("'{0}'", s)));
245  using (ActionStack.BeginEndTransaction("Updated " + concatPropertyName))
246  {
247  return base.SetValue(ref field, value, updateAction, propertyNames);
248  }
249  }
250  return false;
251  }
252 
253  /// <inheritdoc/>
254  protected override bool SetValue(Func<bool> hasChangedFunction, Action updateAction, params string[] propertyNames)
255  {
256  if (propertyNames.Length == 0)
257  throw new ArgumentOutOfRangeException("propertyNames", @"This method must be invoked with at least one property name.");
258 
259  if (hasChangedFunction == null || hasChangedFunction())
260  {
261  string concatPropertyName = string.Join(", ", propertyNames.Select(s => string.Format("'{0}'", s)));
262  using (ActionStack.BeginEndTransaction("Updated " + concatPropertyName))
263  {
264  return base.SetValue(hasChangedFunction, updateAction, propertyNames);
265  }
266  }
267  return false;
268  }
269 
270  /// <inheritdoc/>
271  protected override void OnPropertyChanging(params string[] propertyNames)
272  {
273  foreach (string propertyName in propertyNames.Where(x => x != "IsDirty"))
274  {
275  PropertyInfo propertyInfo = GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
276  if (propertyInfo != null && propertyInfo.GetSetMethod() != null && propertyInfo.GetSetMethod().IsPublic)
277  {
278  preEditValues.Add(propertyName, propertyInfo.GetValue(this));
279  }
280  }
281 
282  base.OnPropertyChanging(propertyNames);
283  }
284 
285  /// <inheritdoc/>
286  protected override void OnPropertyChanged(params string[] propertyNames)
287  {
288  base.OnPropertyChanged(propertyNames);
289 
290  foreach (string propertyName in propertyNames.Where(x => x != "IsDirty"))
291  {
292  string displayName = string.Format("Updated '{0}'", propertyName);
293  object preEditValue;
294  if (preEditValues.TryGetValue(propertyName, out preEditValue))
295  {
296  var actionItem = CreatePropertyChangeActionItem(displayName, propertyName, preEditValue);
297  ActionStack.Add(actionItem);
298  }
299  preEditValues.Remove(propertyName);
300  }
301  }
302 
303  /// <summary>
304  /// Creates an instance of <see cref="ViewModelActionItem"/> corresponding to the action of modifying a property of this view model.
305  /// </summary>
306  /// <param name="displayName">The display name of the action.</param>
307  /// <param name="propertyName">The name of the modified property.</param>
308  /// <param name="preEditValue">The value of the property before the modification.</param>
309  /// <returns>A new instance of the <see cref="ViewModelActionItem"/> class.</returns>
310  protected virtual ViewModelActionItem CreatePropertyChangeActionItem(string displayName, string propertyName, object preEditValue)
311  {
312  return new PropertyChangedViewModelActionItem(displayName, this, Dirtiables, propertyName, preEditValue);
313  }
314 
315  /// <summary>
316  /// Creates an instance of <see cref="ViewModelActionItem"/> corresponding to the action of modifying an observable collection of this view model.
317  /// </summary>
318  /// <param name="displayName">The display name of the action.</param>
319  /// <param name="list">The collection that has been modified.</param>
320  /// <param name="args">A <see cref="NotifyCollectionChangedEventArgs"/> object containing the information of the change.</param>
321  /// <returns>A new instance of the <see cref="ViewModelActionItem"/> class.</returns>
322  protected virtual ViewModelActionItem CreateCollectionChangeActionItem(string displayName, IList list, NotifyCollectionChangedEventArgs args)
323  {
324  return new CollectionChangedViewModelActionItem(displayName, Dirtiables, list, args, Dispatcher);
325  }
326 
327  private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e, string collectionName)
328  {
329  string displayName = string.Format("Updated collection '{0}' ({1})", collectionName, e.Action);
330  var list = sender as IList;
331  if (list == null)
332  {
333  var toIListMethod = sender.GetType().GetMethod("ToIList");
334  if (toIListMethod != null)
335  list = (IList)toIListMethod.Invoke(sender, new object[0]);
336  }
337  var actionItem = CreateCollectionChangeActionItem(displayName, list, e);
338  ActionStack.Add(actionItem);
339  }
340  }
341 }
bool SetValueUncancellable(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...
bool SetValueUncancellable(bool hasChanged, Action updateAction, params string[] propertyNames)
Manages a property modification and its notifications. The first parameter hasChanged should indicate...
bool SetValueUncancellable(Action updateAction, params string[] propertyNames)
Manages a property modification and its notifications. This method will invoke the provided update ac...
bool SetValueUncancellable(bool hasChanged, Action updateAction, [CallerMemberName]string propertyName=null)
Manages a property modification and its notifications. The first parameter hasChanged should indicate...
override void OnPropertyChanging(params string[] propertyNames)
This method will raise the PropertyChanging for each of the property name passed as argument...
override 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...
virtual bool SetValueUncancellable(Func< bool > hasChangedFunction, Action updateAction, params string[] propertyNames)
Manages a property modification and its notifications. A function is provided to check whether the ne...
An abstact class that inherits from ActionItem and can be used to manage actions related to an IDirti...
void RegisterMemberCollectionForActionStack(string name, INotifyCollectionChanged collection)
Registers the given collection to create CollectionChangedViewModelActionItem in the action stack whe...
virtual ViewModelActionItem CreateCollectionChangeActionItem(string displayName, IList list, NotifyCollectionChangedEventArgs args)
Creates an instance of ViewModelActionItem corresponding to the action of modifying an observable col...
This abstract class is an implementation of ViewModelBase that uses a dispatcher to invoke OnProperty...
function s(a)
This class is an implementation of the DispatcherViewModel class that supports undo/redo of property ...
virtual ViewModelActionItem CreatePropertyChangeActionItem(string displayName, string propertyName, object preEditValue)
Creates an instance of ViewModelActionItem corresponding to the action of modifying a property of thi...
EditableViewModel(IViewModelServiceProvider serviceProvider)
Initializes a new instance of the EditableViewModel class.
bool SetValueUncancellable(Action updateAction, [CallerMemberName]string propertyName=null)
Manages a property modification and its notifications. This method will invoke the provided update ac...
This class represents a thread-safe stack of action items that can be undone/redone.
Definition: ActionStack.cs:12
Base interface for a transactional action stack.
override void OnPropertyChanged(params string[] propertyNames)
This method will raise the PropertyChanged for each of the property name passed as argument...