Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ObservableViewModel.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.Diagnostics.Contracts;
6 using System.Linq;
7 
8 using SiliconStudio.Presentation.Services;
9 using SiliconStudio.Presentation.ViewModel;
10 using SiliconStudio.Quantum;
11 
12 namespace SiliconStudio.Presentation.Quantum
13 {
15  {
16  public const string DefaultLoggerName = "Quantum";
17  public const string HasChildPrefix = "HasChild_";
18  public const string HasCommandPrefix = "HasCommand_";
19  public const string HasAssociatedDataPrefix = "HasAssociatedData_";
20 
21  private readonly ObservableViewModelService observableViewModelService;
22  private readonly ModelContainer modelContainer;
23  private IObservableNode rootNode;
24  private bool singleNodeActionRegistered;
25 
26  private Func<SingleObservableNode, object, string> formatSingleUpdateMessage = (node, value) => string.Format("Update '{0}'", node.Name);
27  private Func<CombinedObservableNode, object, string> formatCombinedUpdateMessage = (node, value) => string.Format("Update '{0}'", node.Name);
28 
29  private readonly Dictionary<ObservableModelNode, List<IDirtiableViewModel>> dirtiableViewModels = new Dictionary<ObservableModelNode, List<IDirtiableViewModel>>();
30 
31  /// <summary>
32  /// Initializes a new instance of the <see cref="ObservableViewModel"/> class.
33  /// </summary>
34  /// <param name="serviceProvider">A service provider that can provide a <see cref="IDispatcherService"/> and an <see cref="ObservableViewModelService"/> to use for this view model.</param>
35  /// <param name="modelContainer">A <see cref="ModelContainer"/> to use to build view model nodes.</param>
36  private ObservableViewModel(IViewModelServiceProvider serviceProvider, ModelContainer modelContainer)
37  : base(serviceProvider)
38  {
39  if (modelContainer == null) throw new ArgumentNullException("modelContainer");
40  this.modelContainer = modelContainer;
41  observableViewModelService = serviceProvider.Get<ObservableViewModelService>();
42  }
43 
44  /// <summary>
45  /// Initializes a new instance of the <see cref="ObservableViewModel"/> class.
46  /// </summary>
47  /// <param name="serviceProvider">A service provider that can provide a <see cref="IDispatcherService"/> and an <see cref="ObservableViewModelService"/> to use for this view model.</param>
48  /// <param name="modelContainer">A <see cref="ModelContainer"/> to use to build view model nodes.</param>
49  /// <param name="modelNode">The root model node of the view model to generate.</param>
50  /// <param name="dirtiables">The list of <see cref="IDirtiableViewModel"/> objects linked to this view model.</param>
51  public ObservableViewModel(IViewModelServiceProvider serviceProvider, ModelContainer modelContainer, IModelNode modelNode, IEnumerable<IDirtiableViewModel> dirtiables)
52  : this(serviceProvider, modelContainer)
53  {
54  if (modelNode == null) throw new ArgumentNullException("modelNode");
55  var node = ObservableModelNode.Create(this, "Root", modelNode.Content.IsPrimitive, null, modelNode, modelNode.Content.Type, null);
56  Identifier = new ObservableViewModelIdentifier(node.ModelGuid);
57  dirtiableViewModels.Add(node, dirtiables.ToList());
58  node.Initialize();
59  RootNode = node;
60  node.CheckConsistency();
61  }
62 
64  {
65  var combinedViewModel = new ObservableViewModel(serviceProvider, modelContainer);
66 
67  var rootNodes = new List<ObservableModelNode>();
68  foreach (var viewModel in viewModels)
69  {
70  if (!(viewModel.RootNode is SingleObservableNode))
71  throw new ArgumentException(@"The view models to combine must contains SingleObservableNode.", "viewModels");
72 
73  foreach (var dirtiableViewModel in viewModel.dirtiableViewModels)
74  combinedViewModel.dirtiableViewModels.Add(dirtiableViewModel.Key, dirtiableViewModel.Value.ToList());
75 
76  var rootNode = (ObservableModelNode)viewModel.RootNode;
77  rootNodes.Add(rootNode);
78  }
79 
80  if (rootNodes.Count < 2)
81  throw new ArgumentException(@"Called CombineViewModels with a collection of view models that is either empty or containt just a single item.", "viewModels");
82 
83  CombinedObservableNode rootCombinedNode = CombinedObservableNode.Create(combinedViewModel, "Root", null, typeof(object), rootNodes, null);
84  combinedViewModel.Identifier = new ObservableViewModelIdentifier(rootNodes.Select(x => x.ModelGuid));
85  rootCombinedNode.Initialize();
86  combinedViewModel.RootNode = rootCombinedNode;
87  return combinedViewModel;
88  }
89 
90  internal IReadOnlyCollection<IDirtiableViewModel> GetDirtiableViewModels(ObservableModelNode node)
91  {
92  return dirtiableViewModels[(ObservableModelNode)node.Root];
93  }
94 
95  /// <inheritdoc/>
96  public override IEnumerable<IDirtiableViewModel> Dirtiables { get { return Enumerable.Empty<IDirtiableViewModel>(); } }
97 
98  /// <summary>
99  /// Gets the root node of this observable view model.
100  /// </summary>
101  public IObservableNode RootNode { get { return rootNode; } private set { SetValueUncancellable(ref rootNode, value); } }
102 
103  /// <summary>
104  /// Gets or sets a function that will generate a message for the action stack when a single node is modified. The function will receive
105  /// the modified node and the new value as parameters and should return a string corresponding to the message to add to the action stack.
106  /// </summary>
107  public Func<SingleObservableNode, object, string> FormatSingleUpdateMessage { get { return formatSingleUpdateMessage; } set { if (value == null) throw new ArgumentException("The value cannot be null."); formatSingleUpdateMessage = value; } }
108 
109  /// <summary>
110  /// Gets or sets a function that will generate a message for the action stack when combined nodes are modified. The function will receive
111  /// the modified combined node and the new value as parameters and should return a string corresponding to the message to add to the action stack.
112  /// </summary>
113  public Func<CombinedObservableNode, object, string> FormatCombinedUpdateMessage { get { return formatCombinedUpdateMessage; } set { if (value == null) throw new ArgumentException("The value cannot be null."); formatCombinedUpdateMessage = value; } }
114 
115  /// <summary>
116  /// Gets the <see cref="ObservableViewModelService"/> associated to this view model.
117  /// </summary>
118  public ObservableViewModelService ObservableViewModelService { get { return observableViewModelService; } }
119 
120  /// <summary>
121  /// Gets an identifier for this view model.
122  /// </summary>
123  public ObservableViewModelIdentifier Identifier { get; private set; }
124 
125  /// <summary>
126  /// Gets the <see cref="ModelContainer"/> used to store Quantum objects.
127  /// </summary>
128  internal ModelContainer ModelContainer { get { return modelContainer; } }
129 
130  public event EventHandler<NodeChangedArgs> NodeChanged;
131 
133  {
134  var members = path.Split('.');
135  if (members[0] != RootNode.Name)
136  return null;
137 
138  var currentNode = RootNode;
139  foreach (var member in members.Skip(1))
140  {
141  currentNode = currentNode.Children.FirstOrDefault(x => x.Name == member);
142  if (currentNode == null)
143  return null;
144  }
145  return currentNode;
146  }
147 
148  [Pure]
149  public ObservableModelNode ResolveObservableModelNode(string path, IModelNode rootModelNode)
150  {
151  var members = path.Split('.');
152  if (members[0] != RootNode.Name)
153  return null;
154 
155  var currentNode = RootNode;
156  var combinedNode = currentNode as CombinedObservableNode;
157  if (combinedNode != null)
158  {
159  currentNode = combinedNode.CombinedNodes.OfType<ObservableModelNode>().Single(x => x.MatchNode(rootModelNode));
160  }
161  foreach (var member in members.Skip(1))
162  {
163  currentNode = currentNode.Children.FirstOrDefault(x => x.Name == member);
164  if (currentNode == null)
165  return null;
166  }
167  return (ObservableModelNode)currentNode;
168  }
169 
170  private bool MatchModelRootNode(IModelNode node)
171  {
172  return RootNode is ObservableModelNode && ((ObservableModelNode)RootNode).MatchNode(node);
173  }
174 
175  internal bool MatchCombinedRootNode(IModelNode node)
176  {
177  return RootNode is CombinedObservableNode && ((CombinedObservableNode)RootNode).CombinedNodes.OfType<ObservableModelNode>().Any(x => x.MatchNode(node));
178  }
179 
180  internal bool MatchRootNode(IModelNode node)
181  {
182  return MatchModelRootNode(node) || MatchCombinedRootNode(node);
183  }
184 
185  internal void NotifyNodeChanged(string observableNodePath)
186  {
187  var handler = NodeChanged;
188  if (handler != null)
189  {
190  handler(this, new NodeChangedArgs(this, observableNodePath));
191  }
192  }
193 
194  internal void RegisterAction(string displayName, ModelNodePath nodePath, string observableNodePath, object index, IReadOnlyCollection<IDirtiableViewModel> dirtiables, object newValue, object previousValue)
195  {
196  singleNodeActionRegistered = true;
197  var actionItem = new ValueChangedActionItem(displayName, observableViewModelService, nodePath, observableNodePath, Identifier, index, dirtiables, modelContainer, previousValue);
198  ActionStack.Add(actionItem);
199  NotifyNodeChanged(observableNodePath);
200  }
201 
202  internal void BeginCombinedAction()
203  {
204  ActionStack.BeginTransaction();
205  singleNodeActionRegistered = false;
206  }
207 
208  internal void EndCombinedAction(string displayName, string observableNodePath, object value)
209  {
210  bool shouldDiscard = true;
211  foreach (var singleNode in dirtiableViewModels.Keys)
212  {
213  if (singleNode.Owner.singleNodeActionRegistered)
214  shouldDiscard = false;
215 
216  singleNode.Owner.singleNodeActionRegistered = false;
217  }
218 
219  if (shouldDiscard)
220  {
221  ActionStack.DiscardTransaction();
222  }
223  else
224  {
225  ActionStack.EndTransaction(displayName, x => new CombinedValueChangedActionItem(displayName, observableViewModelService, observableNodePath, Identifier, x));
226  }
227  }
228  }
229 }
An interface that represents an object which can be in a dirty state (modified since last save)...
A container used to store models and resolve references between them.
A class that provides various services to ObservableViewModel objects
static ObservableViewModel CombineViewModels(IViewModelServiceProvider serviceProvider, ModelContainer modelContainer, IEnumerable< ObservableViewModel > viewModels)
ObservableModelNode ResolveObservableModelNode(string path, IModelNode rootModelNode)
This class is an implementation of the DispatcherViewModel class that supports undo/redo of property ...
IObservableNode Root
Gets the root of this node.
ObservableViewModel(IViewModelServiceProvider serviceProvider, ModelContainer modelContainer, IModelNode modelNode, IEnumerable< IDirtiableViewModel > dirtiables)
Initializes a new instance of the ObservableViewModel class.
The IModelNode interface represents a node in a model object. A model object is represented by a grap...
Definition: IModelNode.cs:16
A class describing the path of a node, relative to a root node. The path can cross references...