MVVM: ViewModels for bidirectional model elements - c#

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.

Related

Getting an object's list from inside another object

This is the layout
House -> HouseDef -> Room -> Door
L---> Windows
The problem is that any class may or may not have lists and nested classes like HouseDefinition does. The point is it should be flexible to handle any of these three cases for class variations:
1. hasList,
2. hasNestedObject with List inside that Nested Object
3. Has neither a List nor Nested class
Example of 1 being a Room class which contains a Window List
Example of 2 like House Class
Example of 3 like a Window Class
I have these two classes that I want to access generically from another class. I want to be able to get the Rooms List in House Definition by access of House class stored as an object in MyTreeNode. How can I do this not bound by types, or polymorphic to support a deeper hierarchy level in the future?
public class House
{
string name;
HouseDefinition definition;
public string Name() { return name; }
public HouseDefinition Definition {get {return definition;}}
public House(string name,HouseDefinition definition)
{
this.name = name;
this.definition = definition;
}
}
public class HouseDefinition
{
private List<Room> rooms = new List<Room>();
string type;
public List<Room> Rooms { get { return rooms; } }
public Room this[int i] { get { return rooms[i]; } }
public HouseDefinition(string type)
{
DefaultLayout();
this.type = type;
}
}
public class MyTreeNode : TreeNode
{
string label;
IEnumerable items;
bool hasList;
object item;
public string Label { get {return label; } }
public IEnumerable Items { get { return items;} }
public object Item { get { return item; } }
public bool HasList { get { return hasList; } }
public MyTreeNode(object item)
{
this.item = item;
label = item.ToString();
hasList = false;
}
public MyTreeNode(object item, IEnumerable Items)
{
this.item = item;
label = item.ToString();
hasList = true;
}
}
I think your classes look fine, but I would remove HouseDefinition, since those properties just seem like House to me. If you want to practice inheritance, you could create an IBuilding interface that forces House, Mansion, Shack to implement GetRooms() or something like that. A couple other things:
Your TreeNode class should be using generic types like public MyTreeNode(T item)
You should check out autoimplemented properties - the public fields automatically create private backing fields, so you don't need to create a private field and a getter like you did here: public object Item { get { return item; } }
it's considered bad practice to use the "object" type, so you should convert those to the generic types mentioned above
Good luck, your code is looking good so far!

Serializer Error

I have custom class with following properties:
Class Person
readonly public string Name;
readonly public string FamilyName;
readonly public string UserName;
private List<Person> Team = new List<Person>();
public Person Leader { get; private set; }
public bool HasTeam { get; private set; }
I am getting error on serializer because "Object has Leader property that has no public set." However I need to keep it private, as change of Leader will cause errors. Do you know any way around? Or I need to make it public and keep in mind that I cannot set it?
Thank you,
Michael
So, as mentioned, you could make it so, that it's it can be set only once, but i wouldn't see it as a good option (maybe you should rather rethink how you would like to store this information?)
public class Person
{
private Person leader;
public Person Leader
{
get
{
return leader;
}
set
{
if (Object.Equals(leader, value))
{
return;
}
if (leader != null)
{
throw new InvalidOperationException("Leader can be set only once!");
}
leader = value;
}
}
}
this would allow you to save/load the values, and it wouldn't allow it to be set afterwards. However, this is just working around the problem.
In case you don't have to save it specifically to XML, you could use a binary formatter, that saves the entire Person object (no matter if it contains private fields / properties)

WPF MVVM Update Model Using View Model

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

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.

c# design question - standalone GUI application

It's a pleasure to see how much knowledge people have on here, it's a treasure of a place.
I've seen myself writing code for DataGridView events - and using DataSource to a backend prepared DataTable object.
Sometimes the user can remove rows, update them etc. and the underlying data will need validation checks again.
Let's assume we have a person class
class Person {
public string FirstName { get; set; }
}
Let's say some other part of the code deals with creating an array of Person.
class Processor {
public static Person[] Create()
{
....
....
return person[];
}
}
And this information would appear on a DataGridView for user viewing.
I've tried something like this:
public static DataTable ToTable(List<Person> list)
{ ... }
And had this method in the Person class .. which I would think it'd belong to. Then I would bind the DataGridView to that DataTable and the user will then see that data and do their tasks.
But I've thought of using BindingList<> which I'm not so educated on yet.. would I still have the same capability of sorting the DataGridView like it does with DataTable as a DataSource? Would BindingList be implemented by a container class like "PersonCollection" or would the Person class implement itself? I would like to fire some events to be able to modify the collection in a clean way without having to reset datasources, etc. Where the user experience could really be affected.
I understand that modifying the DataSource DataTable is the good way. But sometimes I need to fire methods in the corresponding class that that specific row refers to, and had an ugly extra hidden column which would hold a reference to the existing object somewhere else (the Person reference).
If you guys know a better design solution, I would be more than happy to hear it.
Thanks in advance,
PS. After reading "The Pragmatic Programmer", I just can't stop thinking critically about code!
Leo B.
Create a business object class. Implement INotifyPropertyChanged. Look at the code below:
public class Employee:INotifyPropertyChanged
{
public Employee(string Name_, string Designation_, DateTime BirthDate_)
{
this.Name = Name_;
this.Designation = Designation_;
this.BirthDate = BirthDate_;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
[DisplayName("Employee Name")]
public string Name
{
get { return this._Name; }
set
{
if (value != this._Name)
{
this._Name = value;
NotifyPropertyChanged("Name");
}
}
}
private string _Name = string.Empty;
[DisplayName("Employee Designation")]
public string Designation
{
get { return this._Designation; }
set
{
if (value != this._Designation)
{
this._Designation = value;
NotifyPropertyChanged("Designation");
}
}
}
private string _Designation = string.Empty;
public DateTime BirthDate
{
get { return this._BirthDate; }
set
{
if (value != this._BirthDate)
{
this._BirthDate = value;
NotifyPropertyChanged("BirthDate");
}
}
}
private DateTime _BirthDate = DateTime.Today;
[DisplayName("Age")]
public int Age
{
get
{
return DateTime.Today.Year - this.BirthDate.Year;
}
}
}
Create your custom collection:
public class EmployeeCollection:BindingList<Employee>
{
public new void Add(Employee emp)
{
base.Add(emp);
}
public void SaveToDB()
{
//code to save to db
}
}
Set the data source:
_employeeStore = new EmployeeCollection();
this.dataGridView1.DataBindings.Add("DataSource", this, "EmployeeStore");
Now if you want to add an employee to your datagridview,
Employee employee = new Employee(textBoxName.Text, textBoxDesignation.Text, dateTimePicker1.Value);
_employeeStore.Add(employee);
This is very clean. You just play with business object and don't touch the UI.
Havent read you question fully, bbut you might want to take a look at my Project ModelShredder, which provides a convinient and fast ToDataTable method

Categories