WPF MVVM Update Model Using View Model - c#

I have a tree structure like this:
public class Node
{
public Node Parent { get; set; }
public List<Node> Children { get; set; }
public NodeValue Item { get; set; }
}
And a NodeViewModel like this:
public class NodeViewModel : INotifyPropertyChanged
{
public Node Node
{
get;
private set;
}
public NodeViewModel(Node node)
{
this.Node = node;
this._children = new ObservableCollection<NodeViewModel>();
}
public string Code {
get
{
return this.Item.Code;
}
set
{
this.Item.Code = value;
NotifyPropertyChanged("Code");
}
}
public Node Parent
{
get
{
return this.Node.Parent;
}
set
{
if (value != this.Node.Parent)
{
this.Node.Parent = value;
NotifyPropertyChanged("Parent");
}
}
}
public NodeValue Item
{
get
{
return Node.Item;
}
set
{
this.Node.Item = Item;
}
}
private ObservableCollection<NodeViewModel> _children;
public ObservableCollection<NodeViewModel> Children
{
get
{
_children.Clear();
foreach(var child in Node.Children)
{
_children.Add(new NodeViewModel(child));
}
return _children;
}
protected set
{
this._children = value;
NotifyPropertyChanged("Children");
}
}
The problem is the last property because when I want to update the model using view model, for example when I want to add a new node I must update _children ObservableCollection from NodeViewModel and also Children List<Node> from Node class.
If I update only the model the UI does not update because NotifyPropertyChanged isn't called and if I update only the view, the changes will be lost because the getter will create another ObservableCollection and also the changes are not reflected over the model.
How can I update the model through view model class?

Whichever way you slice it, the view model needs to fully encapsulate the model. If you had a "save" command you could just update/recreate the model's collection at that time.
Assuming you don't have a "save" command though, and the model should always reflect the current state of the view model, one option is to subscribe to the ObservableCollection<T>.CollectionChanged event and update the underlying collection on the fly.
A side note, you most likely also don't want to create a new collection every time Children_get is called, and are better off just lazy-loading one you keep around.

ObservableCollection already implements INotifyPropertyChanged.
However it will only work if the count of the collection changes.
Also why do you want a ViewModel Collection?
But I think you're looking for this implementation:
private ObservableCollection<Node> _children;
public ObservableCollection<Node> Children {
...code logic
}
Don't forget to handle the changed event

Related

How to access parent object in a tree structure

I am using MVVM and it is working all fine, except one thing, accessing parent model objects.
The goal is to access any model object's parent object directly, but I could not find a propper way to do that.
For example:
Grandparents
--- Parents
--- --- Children
--- --- --- Grandchildren
I have a reference to a Child, but I have to check some properties of Children and maybe Parents.
Currently the code is running through all higher level objects until there is a successful match in the Parent's Children's Grandchildren with my Grandchild object, and then it is possible to check the properties.
But this is kind of disgusting in terms of smart code and efficiency, independent of how this is done, I do not want to run through all my data for a lucky match. This is the current imoplementation, some other parts are done by using LINQ.
var someChild = calledChild;
foreach (Grandparent gParent in mainViewModel.SelectedEnvironment.GrandParents)
{
foreach (Parent parent in gParent.Parents)
{
foreach (Child child in parent.Children)
{
if (child.A == calledChild.A)
{
// Match
System.Diagnostics.Debug.WriteLine("CalledChilds grandparent is " + gParent.Name);
}
}
}
}
The model is set up in classes with definitions like this:
public class Parent : ObservableObject
{
public const string NamePropertyName = "Name";
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
public const string ChildrenPropertyName = "Children";
private ObservableCollection<Child> _children;
public ObservableCollection<Child> Children
{
get
{
return _children;
}
set
{
if (_children == value)
{
return;
}
_children = value;
RaisePropertyChanged(ChildrenPropertyName);
}
}
}
The model is saved in a json file and parsed back to the model's root object type for usage.
I can not just add a new reference "Parent" to the "Child" object, because it would end up in a loop, due to this concepts restrictions.
It would be great to get references instead of copies of the whole model branch.
Is there a way to access the parent objects directly?
Thank you all!
Easiest way is to store direct reference to parent node in child nodes:
public class ParentNode
{
private ObservableCollection<ChildNode> _children;
public ParentNode()
{
_children = new ObservableCollection<ChildNode>();
Children = new ReadOnlyObservableCollection<ChildNode>(_children);
}
public ReadOnlyObservableCollection<ChildNode> Children { get; }
public void AddChild(ChildNode item)
{
if (item.Parent != null) throw new InvalidOperationException("Item is already added to another node");
item.Parent = this;
_children.Add(item);
}
public void RemoveChild(ChildNode item)
{
if (item.Parent != this) throw new InvalidOperationException("Item is not direct child of this node");
item.Parent = null;
_children.Remove(item);
}
}
public class ChildNode
{
public ParentNode Parent { get; internal set; }
}
just be careful, because this introduces circular references - parent references children and vice versa. It is kind of violation of DRY principle, because the shape of the tree is defined twice and you could easily get out of sync (e.g. you set ChildNode.Parent property to something else than the actual parent).
There are ways to workaround it, but I think you could start with this.

MVVM: ViewModels for bidirectional model elements

Assume I have following Model structure:
class Team {
public string Name {get;set; }
public List<Player> players {get;set;}
}
class Player {
public int Age {get;set;}
public string Name {get;set;}
public Team Team {get;set;}
}
I wish to create Viewmodels for this model. However, I also would like to avoid duplicating all properties from Player in the TeamVM and vice versa (for this simple example this would be feasable, but in reality rather cumbersome).
Looking at the literature and online articles, it seems that the "Pure" way would be to create a ViewModel for each Model and to have a ViewModel only return other ViewModels and never Models. This is all fine, but my problem is: how do you create these viewmodels without getting into a recursion trap. Assume I do it like this:
public class TeamVM: ViewModel<Team> {
private ObservableCollection<PlayerVM> _players;
public TeamVM(Team t): base(t) {
_players = new ObservableCollection();
foreach (Player p in t.players) {
_players.Add(new PlayerVM(t));
}
}
public string Name {
get { return _modelElement.Name; }
set { _modelElement.Name = value; NotifyPropertyChanged(); }
}
public ObservableCollection<PlayerVM> Players {
get { return _players; }
}
}
and
public class PlayerVM : ViewModel<Player> {
private TeamVM _teamVM;
public PlayerVM(Player p): base(p) {
_teamVm = new TeamVM(p.Team);
}
public int Age {
get { return _modelElement.Age; }
set { _modelElement.Age = value; NotifyPropertyChanged(); }
}
public string Name {
get { return _modelElement.Name; }
set { _modelElement.Name = value; NotifyPropertyChanged(); }
}
public TeamVM Team {
get { return _teamVM; }
set { _teamVm = value; NotifyPropertyChanged(); }
}
}
Obviously, the above can never work, since it creates recursion: creation of a TeamVM results in the creation of PlayerVMs which in turn spawn TeamVMs again etc.
Right now, I have solved this, by adding an intermediate class as follows:
public class TeamMinimalVM: ViewModel<Team> {
public TeamVM(Team t): base(t) {
}
public string Name {
get { return _modelElement.Name; }
set { _modelElement.Name = value; NotifyPropertyChanged(); }
}
}
public class TeamVM: TeamMinimalVM {
private ObservableCollection<PlayerVM> _players;
public TeamVM(Team t): base(t) {
_players = new ObservableCollection();
foreach (Player p in t.players) {
_players.Add(new PlayerVM(t));
}
}
}
And then having PlayerVM depend on TeamMinimalVM instead of TeamVM. This means that in the views, you would be able to do: {Binding Player.Team.Name} but not {Binding Player.Team.Players.Name}, which is kind of ok for me I guess since I don't think it's a great idea to do this anyway.
My question now is: is there a better/more "standard" way to do "Pure" VMs of bidirectional model elements? I do not want to clone properties of one type in the other (there are too many), nor do I want to expose Model elements directly.
Finally, the ViewModel class I use is this one (just for completeness, but it is not essential to the question I think.)
public class ModelElementViewModel<T> : ObservableObject where T : class
{
private bool _modelElementChanged;
private T _modelElement;
public ModelElementViewModel(T element)
{
_modelElement = element;
}
/// <summary>
/// The underlying model element for this viewmodel. Protected as one should not bind directly to model elements from the gui.
/// </summary>
internal T ModelElement {
get { return _modelElement; }
set {
if (_modelElement != value)
{
_modelElement = value;
ModelElementChanged = false;
NotifyAllPropertiesChanged();
}
; }
}
/// <summary>
/// Property that can be used to see if the underlying modelelement was changed through this viewmodel (note that an external
/// change to the model element is not tracked!)
/// </summary>
public bool ModelElementChanged {
private set
{
if (_modelElementChanged != value)
{
_modelElementChanged = value;
NotifyPropertyChanged();
}
}
get
{
return _modelElementChanged;
}
}
protected override void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
ModelElementChanged = true;
base.NotifyPropertyChanged(propertyName);
}
}
Edit:
What wasn't clear from my original question is that Players are not used exclusively by teams. I want following three scenarios to work:
I want to be able to create a view for a single player that displays all player information
I want to be able to create a view for a team, displaying the information of that team and a table of all players with their statistics
I also want to be able, for example, to have a Playersbook view, which consists of a table displaying all known players with their teamname for example.
Your classes have a clear hierarchy: teams aggregate players. Teams are owners, players are owned. Therefore, when creating a player VM, you can pass team VM as a constructor argument.
The obvious limitation of this is now you can't have players without teams. Possible solutions are: enforcing players to always be owned by some team; supporting null as a team VM and setting a proper value later; creating a "null team" object and using it for team-less players.
In cases like these, when there's a clear aggregation hierarchy, I use my OwnedObservableCollection<T, TOwner>. With it, I can create create a collection _players = new OwnedObservableCollection<PlayerVM, TeamVM>(this) in a team, then just add and remove players to and from the teams by using just Add and Remove.

MVVM from List in Model to Hierarchy List in ViewModel

I have a model that contains a flat list with person objects containing a parent ID. This list is not ordered, so children could come before their parent.
class PersonModel
{
int ID;
int Parent;
string Name;
}
I want to display this in a Treeview and therefore in the VM I need to define the Children & Parent as a reference to other VM's. SO I have setup my PersonViewModel like so;
class PersonVM
{
readonly PersonModel = _Person;
PersonVM Parent;
List<PersonVM> Children;
public Name {
get { return Person.Name; }
}
public PersonVM(PersonModel Person)
{
_Person = Person;
}
}
In my TreeView VM I have a constructor that creates PersonVM's from a list of PersonModels;
class TreeViewVM
{
public List<PersonVM> PersonList;
TreeViewVM(List<PersonModel> Persons)
{
foreach (PersonModel Person in Persons)
{
PersonList.Add(new PersonVM(Person ));
}
}
}
I am now struggling to figure out the proper way of creating the heirarchy between the PersonVM's. I cannot access the Model from my Main VM, and in my thinking it should be like that. However that is where the ID is stored.
So what is the proper way of doing this?

Displaying/Editing complex objects in the WPF Toolkit DataGrid using MVVM

I have a collection of complex models each containing a collection of interface instances to other complex models and I need to display these parent and child complex models, allowing all properties of the parent and child complex models to be edited.
How can I best display this data and allow editing of the parent and child object's properties individually as well as through a combination of the selection of multiple cells and a context menu click (i.e. change the same property value on a child model across multiple parents)? I also need to be able to perform actions like setting model property values to some other complex model instance via search from within the editing mechanism (DataGrid cell currently)?
Below is a generic example of classes that approximates what I am working with in the application.
enum ChildType
{
One,
Two,
Three
}
class ComplexType
{
public long ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
class IChildModel
{
ChildType Type { get; set; }
string Name { get; set; }
}
class ChildModel1 : IChildModel
{
public ChildType Type { get; set; }
public string Name { get; set; }
public string Property1 { get; set; }
public decimal Property2 { get; set; }
public ComplexType Property3 { get; set; }
}
class ChildModel2 : IChildModel
{
public ChildType Type { get; set; }
public long Property1 { get; set; }
public string Property2 { get; set; }
}
class Parent
{
public long ID { get; set; }
public string Name { get; set; }
public CustomObservableCollection<IChildModel> Children { get; set; }
}
class ViewModel
{
public CustomObservableCollection<Parent> Parents { get; set; }
}
Thus far I have implemented the application using a DataGrid and dynamically generated the columns in the View code-behind using reflection. The binding of the columns for the child complex object instances uses a subscript on the CustomObservableCollection<> (custom collection allowing indexing by an generic value [enum ChildType] in this case). The binding in particular has made it difficult to properly set a value on a same property across multiple parent's child instances (via multi-select on a column and a context menu click to set a value). Again, I am handling these sort of mass changes in the code-behind on the View, using reflection a binding path parsing to set the property values (it feels wrong; hate doing it that way). I would like to be able to set the selected children on the ViewModel and pass the property name and new value for the property to a command in the ViewModel to make the changes. Even being able to pass the command the child type, property and new value would be nice (I think).
My research through Google, stackoverflow, Code Project, etc. has pointed me toward my current solution but I feel I am thinking about the problem incorrectly and there should be a better MVVM approach to this.
EDIT
The primary focus for this application is to allow the editing of multiple parent and child model instances in a view where the user can compare values of several instances and be allowed to set the value of a parent or child property across multiple objects of the same type to the same value (i.e. Parent1 and Parent2 both have a ChildModel1 and user wants to set the Name on Property3 of both parent objects' ChildModel1 to "X"). Although, the application still must allow individual edits of properties on parent and child objects (DataGrid does seem to fill the requirement nicely). In meeting these requirements, I implemented dynamic column creation in the view. Below is a generic example of what this logic looks like.
private void DataGrid_TargetUpdated(object sender, DataTransferEventArgs e)
{
var vm = DataContext as ViewModel;
if (vm != null && vm.Parents != null) {
List<ChildType> processedChildTypes = new List<ChildType>();
foreach (var parent in vm.Parents) {
for (int childIndex = 0; childIndex < parent.Children.Count; ++childIndex) {
var child = vm.Children[childIndex];
if (!processedChildTypes.Contains(child.Type)) { // Ensure each child type is only processed once
processedChildTypes.Add(child.Type);
CreateChildPropertyColumns(processedChildTypes, child);
}
}
}
}
private void CreateChildPropertyColumns(List<ChildType> processedChildTypes, IChildModel child)
{
PropertyInfo[] childProperties = child.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); // Only use properties declared on the child type
Type childInterfaceType = typeof(IChildModel);
foreach (PropertyInfo childProperty in childProperties) {
// Only create a column if the property is editable
if (childProperty.CanWrite) {
if (childInterfaceType.IsAssignableFrom(childProperty.PropertyType)) {
var subChild = childProperty.GetValue(child, null) as IChildModel;
if (subChild != null && !processedChildTypes.Contains(subChild.Type)) {
processedChildTypes.Add(subChild.Type);
CreateChildPropertyColumns(processedChildTypes, subChild);
}
}
else
dataGrid.Columns.Add(CreateChildPropertyColumn(child.Type, childProperty));
}
}
}
private DataGridColumn CreateChildPropertyColumn(ChildType childType, PropertyInfo propertyInfo)
{
DataGridColumn column = null;
var binding = new Binding(string.Format("Children[{0}].{1}", childType, propertyInfo.Name));
/* Create column based on PropertyInfo here */
/* Default case is a text column */
column = new DataGridTextColumn() { Binding = binding };
column.Header = propertyInfo.Name;
return column;
}
I think it's not a good idea to use DataGrid in this situation. Most of the time, users rarely view/edit MULTIPLE Parent, ChildModel2, and the ComplexType at once.
You have to think about how users are going to view/edit the data and come up with a simpler UI. For example, if users view/edit Parent and ChildModels most of the time and rarely view/edit ComplexType then you can put textboxes to edit the parent and a DataGrid to edit its ChildModels.
This way, you have simpler UI and a lot easier to write code. I think it's much more complicate to write code that save multiple Parent as in this example.

IDataErrorInfo calling bound object rather than DataContext

I have a model:
public class Product
{
public int Rating { get; set; }
...
}
and a View Model:
public class ProductViewModel: IDataErrorProvider
{
public int Temperature { get; set; }
public Product CurrentProduct { get; set; }
public string this[string columnName]
{
get
{
if (columnName == "Rating")
{
if (CurrentProduct.Rating > Temperature)
return "Rating is too high for current temperature";
}
return null;
}
}
}
My view has an instance of ProductViewModel as the DataContext. The view has the field:
<TextBox Text={Binding Path=CurrentProduct.Rating, ValidatesOnDataErrors=True} .../>
By default, validation occurs on the IDataErrorProvider of the bound object (Product), not the DataContext (ProductViewModel). So in the above instance, ProductViewModel validation is never called. This is just a simple example but illustrates the problem. The model doesn't (and shouldn't) know about Temperature, so the design dictates that the VM should perform the validation on that field.
Yes, I could hack it and replicate the bound properties of the model directly in the ViewModel, but I would have thought there must be an easier way to redirect the call to the VM rather than the model?
If you want your viewmodel to validate a property named "Rating" by IDataErrorInfo, then your viewmodel must actually have a property called Rating and you must bind to it, which would mean to replicate the bound properties of the model in the viewmodel.
Anyway this blog article could be interesting for you (Validating Business Rules in MVVM). The author adds a Validation delegate to the model that the viewmodel can set. This allows you to validate your model using data that it does not known, like the Temperature in your example.
I've encountered that problem before, and my solution is to expose a validation delegate from my Models which is checked when validating the class, and the ViewModel can use this to hook addition validation to the class that is unrelated to Model itself
For example, I would use code that looked something like this from the ViewModel to attach a validation delegate to the Model anytime its set
public class ProductViewModel
{
public int Temperature { get; set; }
private product _currentProduct;
public Product CurrentProduct
{
get { return _currentProduct; }
set
{
if (value != _currentProduct)
{
if (_currentProduct != null)
_currentProduct.RemoveValidationDelegate(ValidateProduct);
_currentProduct = value;
if (_currentProduct != null)
_currentProduct.AddValidationDelegate(ValidateProduct);
RaisePropertyChanged("CurrentProduct");
}
}
}
// Product Validation Delegate to verify temperature
private string ValidateProduct(object sender, string propertyName)
{
if (propertyName == "Rating")
{
if (CurrentProduct.Rating > Temperature)
return "Rating is too high for current temperature";
}
return null;
}
}
The actual code that adds the ValidationDelegate to the Model is pretty generic, so I typically have it in a BaseViewModel so all Models can have this functionality without me having to type it out for each one
#region IDataErrorInfo & Validation Members
#region Validation Delegate
public delegate string ValidationDelegate(
object sender, string propertyName);
private List<ValidationDelegate> _validationDelegates = new List<ValidationDelegate>();
public void AddValidationDelegate(ValidationDelegate func)
{
_validationDelegates.Add(func);
}
public void RemoveValidationDelegate(ValidationDelegate func)
{
if (_validationDelegates.Contains(func))
_validationDelegates.Remove(func);
}
#endregion // Validation Delegate
#region IDataErrorInfo for binding errors
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public string GetValidationError(string propertyName)
{
string s = null;
foreach (var func in _validationDelegates)
{
s = func(this, propertyName);
if (s != null)
return s;
}
return s;
}
#endregion // IDataErrorInfo for binding errors
#endregion // IDataErrorInfo & Validation Members
I also have this approach outlined in my blog post here if you want to see another example.

Categories