How do I pass a property changed event to an ancestor? - c#

I'm sure I am missing some of the basics here so please bear with me.
I have a two objects, a SalesOrder and a SalesOrderLineItem. The lineitems are kept in an Observablecollection<SalesOrderLineItem> of SalesOrder, and if I add line items my databound listView knows about the new items.
Now where I am having a problem is here: The SalesOrder has a read only property called "OrderTotal", which knows what the total price from all the line items combined are.
If I change the quantity, or price of a line item, I don't seem to know how to get change notification to bubble up to "OrderTotal".
Both classes inherit from, and fire INotifyPropertyChanged.
Wham am I missing?

The ObservableCollection only notifies when an Item is added, removed, or when the list is refreshed, not when a property of an Item within the collection changes.
The SalesOrder class needs to be listening to each SalesOrderLineItem's PropertyChanged event.
-
The OrderTotal property could also be dynamic, generating the total each time...

If you feel adventurous, you can try BindableLinq or something similar.
It allows you to expose the result of a query that listens to changes.
But it's not out of beta yet, and I'm not sure it will ever be...
ContinuousLinq is another one, but it does not support Silverlight at all yet.
Other than that, you'll have to listen to each child, and add/remove handlers whenever the collection changes.

public ObservableCollection<TestClass> List
{
get { return _List; }
set {
if (_List!= value)
{
_List = value;
raisePropertyChange("List");
List.ToList().ForEach(i=> i.PropertyChanged+= (o,e)=>
{
//PropertyChanged.Raise(() => List);
raisePropertyChange("List");
//or what ever you want
}
);
}
}
}

Related

BindingList vs List - WinForms Data Binding

In order to get data binding in WinForms (to a DataGridView, for instance) to work anything like you'd hope and add/delete rows as the collection changes, you have to use a BindingList (or DataTable) instead of a generic List. The problem is, almost nobody has the first instinct to code with a BindingList instead of a List in their libraries.
The BindingList implements two events that the List doesn't have and these must be the difference in data binding action (also, a property to suppress the second event):
AddingNew
ListChanged
RaiseListChangedEvents
Similarly, the DataTable has two events which probably enable similar functionality:
RowDeleted
TableNewRow
EDIT: As the helpful SO community pointed out here and in another article, a List can be converted (maybe more accurately encapsulated?) by calling the correct BindingList constructor:
BindingList<MyType> MyBL = new BindingList<MyType>();
MyList.ForEach(x => MyBL.Add(x));
My situation is a little more complicated as illustrated by the code below.
EDIT Added INotifyPropertyChanged stuff that must exist in the real library.
public class RealString : INotifyPropertyChanged
{
private int _KnotCount = 0;
private List<KnotSpace> _KnotSpacings = new List<KnotSpace>();
public RealString()
{
KnotSpacings.Add(new KnotSpace());
}
public int KnotCount
{
get { return _KnotCount; }
set
{
int requiredSpacings = 0;
_KnotCount = value;
// Always one more space than knots
requiredSpacings = _KnotCount + 1;
if (requiredSpacings < KnotSpacings.Count)
{
while (requiredSpacings < KnotSpacings.Count)
{
KnotSpacings.Add(new KnotSpace());
}
}
else if (requiredSpacings > KnotSpacings.Count)
{
while (requiredSpacings > KnotSpacings.Count)
{
KnotSpacings.Remove(KnotSpacings.Last());
}
}
this.OnPropertyChanged(this, "KnotCount");
}
}
public List<KnotSpace> KnotSpacings { get => _KnotSpacings; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(object sender, string PropertyName)
{
if (this.PropertyChanged == null) return;
this.PropertyChanged(sender, new PropertyChangedEventArgs(PropertyName));
}
}
public class KnotSpace
{
private double _Spacing = 10;
public double Spacing { get => _Spacing; set => _Spacing = value; }
}
The things in the list are displayed in the UI, and the properties of the things in the list are modified in the UI, but the UI doesn't directly add/remove things from the list except by changing the KnotCount property. Wrapping the KnotSpacings property in a BindingList doesn't result in the BindingList updating when KnotSpacings is updated by changing the KnotCount property.
EDIT OK, more clarification...
BindingList BL = new BindingList<KnotSpace>(MyRealString.KnotSpacings);
DataGridView1.AutoGenerateColumns = true;
DataGridView1.DataSource = BL;
NumericUpDown1.DataBindings.Add("Value", MyRealString, "KnotCount", false, DataSourceUpdateMode.OnPropertyChanged);
The BindingList has no more success tracking the changes to the underlying List property (KnotSpacings) than the Windows controls. So data binding the controls to the BindingList doesn't accomplish much. BindingList works great if UI adds/removes items from the BindingList because it does the same operations in the underlying List. But then I would need to replicate the add/remove action and logic of the library in my UI and that's a breaking change in waiting.
EDIT Major changes made to my original post attempting to: (1) Clarify the problem. (2) Distinguish it as not a duplicate question (although one of the several questions was a dup). (3) Acknowledge the helpful efforts of others that would be lost if I deleted the post.
First Off, there is a better way to pass a List<T> to a BindingList<T>. BindingList<T> has a constructor that accepts a List<T> which copies the List's elements into the BindingList, like so:
List<int> myList = new List<int>();
BindingList<int> myBindingList = new BindingList<int>(myList);
But that's not your question, really. To answer your question simply - Correct, List<T> is not a good choice for two-way binding in WinForms. As List<T> does not have any events notifying for elements added, you can really only guarantee a one-way binding - data entry may work, but things break down when trying to refresh on, say, items being added to the List.
That said, you mention that these libraries are modifying a List<T> that you have access to during the modifications. I would argue that a good Library would use the Interface pattern to use, modify, and pass collections. Although List<T> and BindingList<T> are very different classes, they both implement IList<T>, ICollection<T>, and IEnumerable<T>. So any function which accepts any of those interfaces as a parameter would accept either a List<T> or a BindingList<T> (for example: public void DoSomethingWithCollection(IEnumerable<int> collection) could accept List<int>, BindingList<int>, or any other collection that implements IEnumerable<int>). The Interface pattern is a well-known standard at this point in C#'s lifespan, and though nobody's first instinct would be to use a BindingList<T> over a List<T>, their first instinct should absolutely be to use an IEnumerable<T> (or IList<T> or ICollection<T>) over a List<T>.
Where possible, it would be better for binding to pass your List to the BindingList's constructor, then never use the List again - instead, use the Add and Remove methods of the BindingList to manage it's internal collection.
If you use the BindingList<T> constructor that accepts an instance of IList<T>, then that instance is used to back the BindingList<T>, and changes in the IList<T> are reflected in the BindingList.
That's not the end of the story, however. WinForms databinding is structured in such a way that, the further away you get from simple, single-property 2-way binding, the more things you have to cover yourself.
For example, the INotifyPropertyChanged interface is implemented by classes that are used as a data source to notify of a change in a child property (like your KnotCount property).
For more complex scenarios, one would not use BindingList<T>, but would derive a class from it and override one or more of the data binding mechanisms. Ditto for the BindingSource class.
There is a lot of boilerplate behind the data binding mechanism, but almost every portion of it is open to derivation in order to customize the behavior. It is sometimes useful to draw out an object graph of the classes and interfaces used in data binding (lots of reading the documentation involved) to give yourself a good mental overview of the whole process.

updating datagrid with BindingList

I've been looking at the difference between a BindingList and an observablecollection and List. From what I've read, it seems like the BindingList is the only collection type that will notify if an object in it has one of its properties changed. I cannot get this to work.
I have a property on a ViewModel called Matches, which returns a BindingList created out of a list of CarMatch objects in another class. (Cars m_Cars = new Cars();) My DataGrid on the View is bound to this Matches property in the VM.
public BindingList<CarMatch> Matches
{
get
{
Return new BindingList<CarMatch>(m_Cars.Matches);
}
}
Now, in the code I change one of the CarMatch object's properties, say.. automaticTrans = true from false. Matches[0].automaticTrans = true. I want to see that change in the DataGrid. Without implementing INotifyPropertyChanged inside of the CarMatch class, is there a way to update the datagrid from the viewmodel? Using INotifyPropertyChanged on Matches does not seem to do it. There is something about this I just don't understand, and could use an example to look at.
CarMatch (not Matches) has to implement INotifyPropertyChanged. But consider using ObservableCollection unless you really need some of the additional scenarios offered by BindingList: with ObservableCollection, INotifyPropertyChanged comes for free. And, more importantly, BindingList doesn't scale well.
try
dataGrid.Items.Refresh();
but keep in mind that is a expensive call if you have lots of data and you call it several times in a short period of time.

ObservableCollection and CollectionView

I'm writing an application that reads data from a local db and display them in a listbox(I'm working in WPF).
I've a DVD object, where its properties are the columns of the db. This DVD object also implements INotifyPropertyChanged. "MyDVDs" is the table that refers to db. Once created these object, I create a class that inherits from ObservableCollection and takes data from "MyDVDs" in the constructor. However I don't need only to add, remove and update data from the listbox, but I also need to sort and filter them. Here is the code for ObservableCollection:
class ObservableDVD : ObservableCollection<DVD>
{
private ICollectionView collection;
public ObservableDVD(MyDVDs e)
{
foreach (DVD d in e.DVDs)
{
this.Add(d);
}
Collection = CollectionViewSource.GetDefaultView(this);
}
public ICollectionView Collection
{
get { return collection; }
private set { collection = value; }
}
}
I wanted to know, this is a good way?? Or can I do better?
In the MainWindow of the project, when Load_Window event fires, I assign the Collection property to listbox.ItemSource(in MainWindow code-behind I declare a private field that obviously refers to an ObservableDVD Object). I have some buttons that allow me to do the operations I tell you before.In the event headler of the buttons, I directly update and modify the ObservableDVD Object, not its property Collection. However, the Collection property also reflects those changes.
Why this behavior occurs?
It's ok for me, but I can't understand why it's happens. Is because of the notifications?
The property Collection has a reference to the view of the ObservableDVD. Being a reference means pointing to the same data in memory.
ObservableCollection Class Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
The Collection you are specifying is just a "view" of the ObservableDVD collection. Which means that both are really pointing to the same data in the memory, they're not 2 separate things. A "view" can be a subset of items when you apply filters to a collection, for instance.
Otherwise said, your ObservableDVD contains your "Data Table" for the entire dataset while the ICollectionView lets you manipulate which records/objects are visible to the user through custom logic.

Notify Property Changed Event without setter

I am developing a WPF application using MVVM architect, and as a common scenario using properites to Notify Changes like
public List<EmployeeInfo> Employees
{
get
{
return _employees;
}
set
{
_employees = value;
NotifyPropertyChanged(() => Employees);
}
}
My only issue is that i am using property setter to notify application about the changes made to some value, and according to FxCop this is a bad practice and 'CollectionPropertiesShouldBeReadOnly'. So i want to improve a little bit on that, so tell me some mechanism with which i could use Notify Property changed without using setter.
If your collection property is read-only, you don't need to notify anything that the entire collection has changed to a different one - instead, the event handlers on the collection will be notified of changes within the collection (the addition of items etc).
If you need to be able to change which collection the property refers to within the view model, you could always make the setter private and keep the existing notification mechanism.
The ObservableCollection itself informs about changes happened. So you don't need to raise the PropertyChanged Event. If you think, that it's necessary tho change the collection, then you can delete and add items. Due to the observable pattern, the changes will be anounced.
The fact that you are using a setter means you're trying to replace the instance of the collection with a new object instance. If you just are worried about changes to items in the collection, that's already built into the observablecollection. FxCop is going to complain about the setter whether you had the notifypropertychanges call or not.

Add Items to ComboBox from an IEnumerable object

I have a System.Timers.Timer that updates my win form application components in every 5 seconds.
I have a comboBox and global IEnumerable<Person> list that updated also in everty 5 seconds.
I need to add persons name to combobox. If the name is already in the list, i should not add.
How can I proceed?
Here is the code inside the timer event. This adds multiple times and i am not sure to do that with foreach, maybe IEnumareble interface has an easier way.
foreach (Persons person in personsList)
{
comboBox.Items.Add(person.Name);
}
This is one of the simpler solutions to this problem, assuming you're using .NET 3.5 or greater:
foreach(Person person in personsList)
{
if(!comboBox.Items.Cast<string>().Contains(person.Name))
{
comboBox.Items.Add(person.Name);
}
}
If you're using 3.0 or earlier, you'll have to do the search yourself:
foreach(Person person in personsList)
{
bool contains = false;
foreach(string item in comboBox.Items)
{
contains = string.Equals(item, person.Name);
if(contains) break;
}
if(!contains) comboBox.Items.Add(person.Name);
}
If possible using DataBinding is usually good. WPF has even nicer binding allowing for MVVM. WPF would actually make modifications as you modify the original collection (realtime) and don't have to readd all at every pass.
Readding all items at every pass is a bad approach, but its the easy way out. It would be better to either modify the listbox directly if the code allows it (not too many updates, not too time critical) or to make a copy of the list and perform only differences. (Pass 1: Remove any items in combobox that doesn't exist in new list. Pass 2: Add any items in new list that doesn't exist in combobox)
A couple approaches could be to walk all the items in the combobox, or you could keep track of a List of names that you've already added. Do you have any performance requirements?
Easier would be to just bind directly to the list of Persons and set your DisplayMember appropriately...
If I bind the data cmb.DataSource = personsList; cmb.DisplayMember = "Subject"; This wont work
It didn't work for me also. After some trying found this solution, maybe it will help someone:
IEnumerable<ICustomer> customers = GetCustomers(); //fill Ienumerable object list
_comboBox.DataSource = customers.ToList(); //convert it to list and it binds fine
_comboBox.DisplayMember = "Name"; // field Name from ICustomer
_comboBox.ValueMember = "CustomerID"; // field CustomerID from ICustomer
A more simpler approach is:
comboBox.Items.Clear();
comboBox.Items.AddRange(personsList.Select(p => p.Name));
All that does is clears the comboBox and adds the entire list again. Or if you don't like clearing the comboBox:
comboBox.Items.AddRange(personsList.Where(p => !comboBox.Items.Cast<string>().Contains(p.Name)).Select(p => p.Name));
You don't need the foreach anymore. Simply replace all your code with this!

Categories