Aggregate detail values in Master-Detail view - c#

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...

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.

MVVM Refresh Datagrid from ViewModel even when collection doesn't change

I'm writing an application to read and analyze some logs my company software uses. There are multiple types of logs, but let's take only two for the purpose of describing my problem. Namely, logs of TypeA and TypeB. I have designed one class to hold a single line of log data, named LogLine which looks like below.
public class LogLine
{
public long LineNum { get; set; }
public string Msg { get; set; }
}
So here's my problem/requirement.
In my main ViewModel, I'd like to read logs of each type only once when the application loads. Read TypeA logs one time, and store in an ObservableCollection of LogLine instances, do the same for TypeB. Then depending on my choice the DataGrid displays logs from one type, and if I click a button at any time, the same DataGrid should display logs from the other type. Note that my logs data doesn't change, I simply want to display my choice of logs.
For this I created three classes, namely, ControllerMain, ControllerA, and ControllerB. The last two derive from the former like so:
public class ControllerMain
{
public ControllerMain()
{
LogLineList = new ObservableCollection<LogLine>();
}
private ObservableCollection<LogLine> logLineList;
public ObservableCollection<LogLine> LogLineList
{
get { return logLineList; }
set { logLineList = value; }
}
}
public class ControllerA : ControllerMain
{
public ControllerA() { }
// More stuff here
}
public class ControllerB : ControllerMain
{
public ControllerB() { }
// More stuff here
}
As you can guess ControllerA is intended to hold logs of TypeA, and associated properties and methods unique to those logs. Same goes for TypeB logs.
In my ViewModel, I have instances of each of the classes above like so, and at application load I read log data and store in appropriate class object.
public ControllerMain COMMON_LOG { get; set; }
public ControllerA A_LOG { get; set; }
public ControllerB B_LOG { get; set; }
public ViewModelMain()
{
isAType = true;
ClickCommand = new CustomCommand(ClickCmd, CanClickCmd);
A_LOG = new ControllerA
{
// This simulates reading logs from files - done only once
LogLineList = DataService.GetAData()
};
B_LOG = new ControllerB
{
// This simulates reading logs from files - done only once
LogLineList = DataService.GetBData()
};
// This simulates switching to already loaded logs.
// When I do this the log lines don't change, but I want to refresh the datagrid and display correct info.
LoadAppropriateLog();
}
private void LoadAppropriateLog()
{
if (isAType)
{
COMMON_LOG = A_LOG;
isAType = false;
}
else
{
COMMON_LOG = B_LOG;
isAType = true;
}
}
My View binds to the COMMON_LOG instance like below:
<DataGrid Grid.Row="0" Margin="5"
Name="dgLogs"
AutoGenerateColumns="False" SelectionUnit="CellOrRowHeader"
ItemsSource="{Binding COMMON_LOG.LogLineList}">
Then at the click of a button, I call the above LoadAppropriateLog() method, so it will simply assign the instance of appropriate type to COMMON_LOG which is the instance I've used to data bind.
The problem is that when I do so, since the actual data in each instance's LogLineList doesn't change, the DataGrid doesn't automatically update to reflect my choice of logs.
Is there a way to manually refresh the DataGrid from my ViewModel after every time I switch the type of log?
If you'd like to run the project and see, here's a download link:
Download the VS Project
If you're binding to a property of a class in XAML, either
The property should never change its value after a binding would first see it, and should usually be readonly just to avoid mishaps -- or
The class should implement INotifyPropertyChanged and the property should raise PropertyChanged in its setter.
In your case, you're changing the value of COMMON_LOG, and you're never changing the value of its LogLineList.
tl;dr: So your main viewmodel needs to implement INotifyPropertyChanged, and raise PropertyChanged in the setter for COMMON_LOG. Anything that doesn't do those things isn't a viewmodel.
LogLineList being an ObservableCollection won't accomplish anything: What that class does is raise notifications when items are added, removed or replaced. That doesn't happen at any time after the binding sees it. Those instances of ObservableCollection don't even know that the main viewmodel even exists, so they certainly can't be expected to raise notification events when its properties change. Nor should they: Everybody is responsible for exactly his own notifications.
In fact, if you've made a design decision that those collections never change after initialization, use ReadOnlyCollection instead of ObservableCollection. Creating one is easy: Call List<T>.AsReadOnly<T>(). For any IEnumerable<T>, just call e.ToList().AsReadOnly(). ObservableCollection signals "you can add stuff to this". But nobody should. So don't give them ideas.

Updating UI when a model property changes in an ObservableCollection?

I have a view that has a group of images I get from a web service
I receive them in a list of this class:
public class ImageModel
{
public int Id { get; set; }
public string Name { get; set; }
public string imageUrl { get; set; }
}
under each image I show an up-vote button, so I added another bool property to the model above:
public bool UpVoted { get; set; }
the ListView that shows these images is bound to an ObservableCollection<ImageModel > , I want to change the voting icon through a converter that convert the value of UpVoted to the corresponding icon, when the user click the voting icon: a command execute this method:
private void OnVoting(ImageModel image)
{
Images.Single(x => x.id == image.id).UpVoted = !image.UpVoted;
}
the problem is that the UI is not updated, and to make sure that I understood the problem I turned the model to a View model and made the required changes to the UpVoted property (I'm using MVVM light library)
bool upVoted;
public bool UpVoted
{
get { return upVoted; }
set
{
Set(ref upVoted, value);
}
}
and it works now,
so I need to bind the UpVoted to the UI, so it's updated whenever it changed
first
your model class must inherit from MvxNotifyPropertyChanged
public class ImageModel : MvxNotifyPropertyChanged
{
public int Id { get; set; }
public string Name { get; set; }
private bool upVoted ;
public bool UpVoted
{
get { return upVoted ; }
set { upVoted = value; RaisePropertyChanged(() => UpVoted ); }
}
}
then with MvxValueConverter you ready to go
Mustafa's answer mentions a class that is specific to MvvmCross library.
Another alternative is TinyMvvm.
If you wish to write your own MVVM (or understand how MVVM works),
the general pattern is to implement INotifyPropertyChanged: Implement Property Change Notification, which I discuss here.
A convenient way to implement INotifyPropertyChanged, is to make a base class that does that implementation, then inherit from that base class. You can use the code in that sample as your base class. Or use a slightly different implementation, that avoids having to manually pass the property name as a string:
using System.ComponentModel;
using System.Runtime.CompilerServices;
// Use this as base class for all your "view model" classes.
// And possibly for your (domain) model classes.
// E.g.: "public class MyLoginViewModel : HasNotifyPropertyChanged".
// OR "public class MyLoginModel : HasNotifyPropertyChanged".
// Give it whatever name you want, for ViewModels I suggest "ViewModelBase".
public class HasNotifyPropertyChanged : INotifyPropertyChanged
{
// --- This is pattern to use to implement each property. ---
// This works for any property type: int, Color, etc.
// What's different from a standard c# property, is the "SetProperty" call.
// You will often write an IValueConverter (elsewhere) to use in XAML to convert from string to your property type,
// or from your property type to a type needed in your UI.
// Comment out this example property if you don't need it.
/// <summary>
/// Set to "true" at end of your initialization.
/// Then can use Property Trigger on Ready value=true in XAML to do something when your instance is ready for use.
/// For example, load something from web, then trigger to update UI.
/// </summary>
private bool _ready;
public bool Ready
{
get => _ready;
set => SetProperty(ref _ready, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (property == null || !property.Equals(value))
{
property = value;
RaisePropertyChanged(propertyName);
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Again, an alternative to the above code is to use an existing MVVM library.
For another alternative, that doesn't require writing "SetProperty(..)" or "OnPropertyChanged(..)" in all of your property setters, google for info about using Fody/PropertyChanged. Then you wouldn't need any of the above code; your class would simply inherit from INotifyPropertyChanged. (And in app startup, you call a method that "injects" the needed logic into all properties of all INotifyPropertyChanged classes.)
Acknowledgement: The code pattern in example above is based on one of the open source libraries. It might be from TinyMvvm.
you do not say which sort of container that you are using but not all controls are set to support two way notification by default. so you may have to add a
Mode=TwoWay
to get notifications from the back end that data has changed. Or as the previous answer by Mustafa indicated you may need to verify that your class is implementing the InotifyPropertyChanged event with mvvm light.

MVVM communication between viewmodels with different DTO's

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

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.

Categories