MVVM communication between viewmodels with different DTO's - c#

I currently have three models in EF (House,Room,Item).
public class House
{
property int ID { get; set; }
property string Name { get; set; }
property List<Room> Rooms { get; set; }
}
public class Room
{
property int ID { get; set; }
property string Name { get; set; }
property List<Item> Items { get; set; }
}
public class Item
{
property int ID { get; set; }
property string Name { get; set; }
}
In my UI I have a Treeview and a display area to show the items further details for modification when I double click an item in the treeview.
My treeview to improve performance calls a webservice which returns the following DTO/s
public class LayoutItemDTO
{
property int ID { get; set; }
property string Name { get; set; }
property List<LayoutItemDTO> Children { get; set; }
}
these DTO's are mapped built using a query to the database on the house, room, item models.
Now when the user double clicks a house item on the treeview it calls a webservice to return the house model with the rooms collection into a view for the user to add/remove rooms and when they close the view it prompts for a save.
The same happens when the user double clicks on a room (i.e add/remove items to a room).
This all works great apart from keeping the treeview and the opened view in sync so if they change the name of a room or add/remove an item I want it reflecting in the treeview in memory and reverting if they cancel the changes on close.
Currently I have done this using the event aggregator but it seems untidy calling events for each action, if I could just use WPF binding it would all work instantly.
The reason for not using the same models on the treeview is due to these models having a lot more information on them than is shown, when obtaining everything this causes a performance problem.

Databinding and INotifyPropertyChanged
If you want that changes on your objects to be reflected in the UI using data binding, you have to implement INotifyPropertyChanged interface in your model class (or in a ViewModel if you are using MVVM pattern).
By implementing the interface, an event (PropertyChanged) will be triggered each time a property value is modified, and the controls databinded to the property will refresh to show the new values.
You can find an example here: How to: Implement the INotifyPropertyChanged Interface
Collections
For the collection, WPF databinding will work if the collection implements INotifyCollectionChanged. The List<T> type does not implement this interface, so the TreeView won't reflect add/removes from the list. The type ObservableCollection<T> implements this interface, so you just have to change List<LayoutItemDTO> to ObservableCollection<LayoutItemDTO> and the changes should be reflected.
MVVM
As you mentioned using MVVM, I would add that I normally would have ObservableCollection and INotifyPropertyChanged implementations in my ViewModels. You may want to create a LayoutItemViewModel that would encapsulate a LayoutItemDTO.
I can also advise you to have a look at existing toolkits and frameworks that can help a lot for implementing "plumbing code" for MVVM (like INotifyPropertyChanged implementation). I use mainly MVVM Light, but there are a lot of other availabe depending on your needs.
Here is also a good link for implementing TreeView databinding in a MVVM manner: Simplifying the WPF TreeView by Using the ViewModel Pattern

Related

Is it acceptable to bind Entity Framework entities to Window Forms controls?

I have two entities: the first is SalesOrder and the second is SalesOrderDetails. In the SalesOrder entity, I have an ObservableListSource list type that keeps track of SalesOrderDetails. The entities look something like this:
public class SalesOrder{
public int Id {get; set;}
public DateTime Date {get; set;}
...
public virtual ObservableListSource<SalesOrderDetails> OrderDetails { get; set; }
publi SalesOrder()
{
OrderDetails = new ObservableListSource<SalesOrderDetails>()
}
}
public class SalesOrderDetails{
public int Id { get; set; }
public int Quantity { get; get; }
public decimal Price { get; set; }
...
}
ObservableListSource extends ObservableCollection and implements IListSource. The GetList method returns a bindingList that stays in sync with the ObservableCollection. The GetList method is an extension method defined in the System.Data.Entity assembly. ObservableListSource looks like this:
public class ObservableListSource<T> : ObservableCollection<T>, IListSource
where T : class
{
private IBindingList _bindingList;
bool IListSource.ContainsListCollection { get { return false; } }
IList IListSource.GetList()
{
return _bindingList ?? (_bindingList = this.ToBindingList());
}
}
To bind the SalesOrder and SalesOrderDetails entities to my form, I use two binding source controls: salesOrderBindingSource and salesOrderDetailsBindingSource. The binding looks like this:
salesOrderBindingSource.DataSource = SalesOrder;
salesOrderDetailsBindingSource.DataSource = salesOrderBindingSource;
salesOrderDetailsBindingSource.DataMember = OrderDetails;
I bind every entity that needs change tracking the same way I bind SalesOrder and SalesOrderDetails. I've been reading that it's not recommended to bind the entities to the UI, that I should use view models and bind those to the UI instead. But that means that I would have to write the change tracking code or find a library that does change tracking.
What do you think?
If it's acceptable for your application to bind to DataSet and DataTable, then it's OK to bind to EF models.
In general, it depends to the requirements of your application; For example, usually for a small-scale or data-centric forms application it makes sense to bind to DataTable or Entity models, but usually in a large-scale application, you may want to consider better patterns for separation of concern.
If databinding to EF models makes sense to your application, then to do it in a correct way, follow this Microsoft Docs article:
EF 6 Fundamentals - Databinding with WinForms
Some notes about the IBindingList, IListSource
The interface that you need in Windows Forms to support databinding to lists, is IBindingList. BindingList<T> is a good implementation of that.
IListSource provides functionality to an object to return a list that can be bound to a data source. So while the object doesn't implement IBindingList, but it can return a bindable list from its GetList method. It's very well supported and used in Windows Forms. For example, DataGridView, ComboBox, ListBox, BindingSource check if the data source implemented IListSource and in this case, they get the data source by calling GetList method. That's why DataTable supports databinding without implementing IBindingList, instead, it returns a DataView in GetList, which implements IBindingList.

How to implement MVVM with the WPF treeview?

I haven't worked with WPF or the MVVM pattern before.
However I want to create a simple document management system and would like to do so using the aforementioned technologies.
I've modeled a hierarchical file system in my Database and want to display it in a treeview.
EER-Diagramm
As you can see each directory can have multiple sub-directories and multiple files in it.
I've read some tutorials on the topic and if I understood them correctly then I should create model classes for directory and file in which the data from the database is stored directly.
Example:
public class Directory
{
private int id;
public int Id
{
get { return id; }
set { id = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int parent;
public int Parent
{
get { return parent; }
set { parent = value; }
}
private DateTime dateCreatedOn;
public DateTime DateCreatedOn
{
get { return dateCreatedOn; }
set { dateCreatedOn = value; }
}
}
Then each model class should have an associated view-model class which implements additional properties which are only relevant for describing the UI element to which objects of this class will be bound.
In this case the view-model class should have the isExpanded and isSelected Property of the treeviewitem.
Then I would need another view-model class for the entire treeview which would contain the collection of directorys and files which should be displayed.
My questions are:
Have I understood the mvvm concept correctly?
Which class, the model or the view-model, of directory should implement the iNotifyPropertyChanged interface?
Should the view-model class of directory contain the same properties as the model class or is a reference to a model-object in the view-model class sufficient?
If the view-model class should contain the same properties of the model class again, then whats the best way to make sure that model-objects and the associated view-model objects always stay synchronized?
I hope this question is understandable and thanks for all help.
Andahari
answer 1) yes.
answer 2) view-model should have the iNotifyPropertyChanged.
answer 3) yes. and they should be explicitly mapped. i.e.:
this.property1 = model.property1
answer 4) use the same names, and see answer 3.
If you use a private-public property pair in the view-model, use iNotifyPropertyChanged in the view-Model, and map the properties of the model to the corresponding properties in the view-model, you should be set.
Model also can implement the iNotifyPropertyChanged, you no need to repeat the properties in View Model again.
https://msdn.microsoft.com/en-us/library/gg405484(PandP.40).aspx
"The model may also support data validation and error reporting through the IDataErrorInfo (or INotifyDataErrorInfo) interfaces. The IDataErrorInfo and INotifyDataErrorInfo interfaces allow WPF data binding to be notified when values change so that the UI can be updated. They also enable support for data validation and error reporting in the UI layer."

observablecollection question

I have a class/structure like the one given below
public class FileDetails
{
public FileDetails()
{
}
public PrintFile PrintFileDetails { get; set; }
public Boolean IsSelected { get; set; }
public DateTime UploadTime { get; set; }
public long FileSize { get; set; }
public UploadTypes TypeOfUpload { get; set; }
public DateTime DownloadStartTime {get;set;}
public DateTime DownloadEndTime {get;set;}
public bool ShouldDownload{get;set;}
}
In the above snippet PrintFile is defined in a XSD. I am planning to deploy this structure inside a ObservableConnection. If I implement NotifypropertychangedFileDetails will the items under PrintFileDetails also able to reap benefits of INotifypropertychanged. I believe i cannot implement the INotifyPropertyChanged as it is shared among other programmers.
No, each object must implement INotifyPropertyChanged itself. The PrintFile object does not benefit from the fact that the FileDetails object implements an interface.
Also, if you are generating these classes from an XSD, you can tell the generator to generate the classes with INotifyPropertyChanged implementation automatically using the /enableDataBinding command line switch on XSD.EXE.
Footnote: Putting objects that implement INotifyPropertyChanged into an ObservableCollection won't have any magical effects. Changes made to the objects in the collection will not fire the collection's PropertyChanged event (unless you write the code to do so). The collection's PropertyChanged event only fires if a property of the collection object changes.
In most cases, you're using an observable collection because you want to data bind it to WPF or Silverlight UI elements and you want the UI to update itself automatically when the data changes. The data binding system will notice if the objects in the collection implement IPropertyNotifyChanged and will attach to the PropertyChanged events automatically so that the UI will know when the data changes.

How to update MVVM nested ViewModels when Model changes and vice versa?

I'm looking for some advice on how to solve a problem which is bugging us at the moment.
Let's say we have a couple of business objects (POCOs) like
public class UserGroup
{
public virtual ICollection<Person> People { get; set; }
}
public class Person
{
public virtual ICollection<Adress> Adresses { get; set; }
}
public class Adress
{
public virtual string StreetName { get; set; }
}
This is a bit simplistic but I hope it's enough so that you get the idea. UserGroup has a collection of Person instances and each Person instance has a collection of Address instances.
The ViewModel for the UserGroup POCO could possibly look like this:
public class UserGroupViewModel
{
private UserGroup userGroupModel;
public UserGroup UserGroupModel
{
get { return this.userGroupModel; }
set
{
this.userGroupModel = value;
this.PeopleViewModelCollection =
new ObservableCollection<PeopleViewModel>();
foreach (Person p in this.UserGroupModel.People)
{
var personViewModel = new PersonViewModel();
personViewModel.PersonModel = p;
this.PeopleViewModelCollection.Add(personViewModel);
}
}
}
public ObservableCollection<PersonViewModel> PersonViewModelCollection
{
get;
set;
}
}
Where as the ViewModel for the Person POCO could look like this:
public class PersonViewModel
{
private Person personModel;
public Person PersonModel
{
get { return this.personModel; }
set
{
this.personModel = value;
this.AddressViewModelCollection =
new ObservableCollection<AddressViewModel>();
foreach (Address a in this.PersonModel.Adresses)
{
var addressViewModel = new AddressViewModel();
addressViewModel.AddressModel = a;
this.AdressViewModelCollection.Add(addressViewModel);
}
}
}
public ObservableCollection<AddressViewModel> AddressViewModelCollection
{
get;
set;
}
}
Again it is overly simplistic but what I want to show is that ViewModels have ObserableCollections of other ViewModels nested inside them.
The setter of the respective Model property, e.g. PersonViewModel.PersonModel does create ViewModels for all the adresses of the Person and adds them to the ObservableCollection<AdressViewModels> AdressViewModelCollection property of the PersonViewModel.
At this point we could use this code to display these ViewModels in a View. We can e.g. display the StreetName of a Person's Adress.
Now what should you do when you delete an Adress of a Person? Removing the AdressViewModel from the PersonViewModel.AdressViewModelCollection will update the GUI but it actually does not allow to update the underlying models.
Similar to that if you add another Adress to a Person Model the existing ViewModels are not going to reflect this change since the PersonViewModel.AdressViewModelCollection would only be rebuilt once the PersonViewModel.PersonModel property is set again.
Is it there a (preferably easy) way to achieve this two-way update between ViewModel and Model? Or maybe this is not advisable and it's better to handle this problem in a completely different way?
I'm keen to hear different opinions on this problem!
EDIT:
I would like to state that our model classes are generated by the Entity Framework 4.0 and are POCOs (see example of business objects above). I'm not sure if that was clear enough in the question itself. We would like to expose navigational properties (e.g. Person.Addresses) as ICollection<T>.
You could simplify things using dynamic proxies (e.g Castle.Proxy), take a look at this post for an idea how to accomplish this.
It sounds like it's not the cascading that is the issue. It's the synchronization between the model and view model.
You will need to listen to the observable collections collectionchanged event and an implementation is provided here.
I think your only option is to implement the INotifyPropertyChanged in your model since the entity framework doesn't provide a way to update your ViewModel.

Aggregate detail values in Master-Detail view

I have a master-detail relationship in some custom entities. Say I have the following structure:
class Master : INotifyPropertyChanged
{
public int Id { get; set; } // + property changed implementation
public string Name { get; set; } // + property changed implementation
public ObservableCollection<Detail> Details { get; }
}
class Detail : INotifyPropertyChanged
{
public int Id { get; private set; }
public string Description { get; set; }
public double Value { get; set; } // + property changed implementation
}
My goal is to have a ListView, using a GridView, showing a list of Master objects. When I select a specific Master, I'll have a separate ListView for the details, allowing editing. Basically, a fairly standard Master-Detail view.
However, I also want the GridView for the Master to show the Sum of all of that master's Detail elements, ie: Details.Select(d => d.Value).Sum();
This is fairly easy to display using a custom IValueConverter. I can convert from the details collection directly to a double displaying sum, and bind a TextBlock's Text to the Details OneWay, via the IValueConverter. This will work, and show the correct values when I open the window.
However, if I change one of the detail members, this will not update (even though detail implements INotifyPropertyChanged), since the collection itself is still the same (the ObservableCollection reference hasn't changed).
I want to have an aggregated value in a master list, showing the sum (or average/count/etc) within the detail list, and have this stay up to date when the user changes properties in details. How can I go about implementing this?
Edit:
Ideally, I would prefer if there is a means of accomplishing this that doesn't involve changing the Master class directly. The application in question is using the MVVM pattern, and I'd really prefer to not change my Model classes in order to implement a specific View. Is there a way to do this without introducing custom logic into the model?
I was considering possibilities with the UI where you'd make the binding explicit and perform binding/updates from a command... but it seems that the easiest way to do it would be to extend the ObservableCollection to add/remove listeners to each Detail instance as its added/removed, then just fire CollectionChanged when any of them change. Call it DeeplyObservableCollection<T>.
class Master : INotifyPropertyChanged
{
public int Id { get; set; } // + property changed implementation
public string Name { get; set; } // + property changed implementation
public double Sum {get {return Details.Sum(x=>x.Value);}}
public DeeplyObservableCollection<Detail> Details { get; }
// hooked up in the constructor
void OnDOCChanged(object sender, CollectionChangedEventArgs e)
{ OnPropertyChanged("Sum"); }
}
Worst case you'd have to wrap an ObservableCollection in another type if you can't properly override all the methods you need...

Categories