Specify the full binding path from the nested ItemsControls

I have a problem with Nested controls with ItemSources when changing an external DataContext control. Internal controls are updated to reflect the new DataContext, but this is similar to the "Ghost" binding, which is still bound to the old DataContext.

I suspect that having Nested controls that have DataTemplates prevents the internal control bindings from being updated when the external control of the DataContext changes. I read somewhere that only binding responds only to PropertyChanged events that arise from an object that is definitely defined in PATH.

My question is: how do you fully define the PATH binding from neighboring controls using ItemsSources? In my case:

<DataGrid name="OuterGrid" ItemsSource={Binding SelectedSchool.Classes}"> <ItemsControl ItemsSource={Binding Students}"> <ComboBox SelectedItem={Binding Grade}" /> </ItemsControl> </DataGrid> 

I would like to completely specify the internal ComboBox SeletedItem PATH, something like the following, but I need it to bind to a specific element in the collection (and not just to index 0).

 <ComboBox SelectedItem="{Binding ElementName=OuterGrid, Path=DataContext.SelectedSchool.Classes[0].Students[0].Grade}" /> 

I have a more detailed example of my problem below, I can’t post the actual code or describe the ACTUAL objects I work with (security reasons), so I tried to describe this in the easiest way to understand.


MODEL:

I have a rather complicated Biz object that has a collection of other objects. Items in the collection also have collections within them.

  • There are many classes in schools.
  • Classes have many students.
  • Each student has a class for a class.
  • The list of possible letter ratings for each school is different.

Each class (including my ViewModel) implements INotifyPropertyChanged, and each collection is an ObservableCollection.


ViewModel:

My ViewModel has the following features:

  • An observable set of schools ... (AllSchools).
  • Selected School
  • Logical (IsEditing)
  • An observable set of possible classes (which is updated when IsEditing changes and is based on the selected school).

It is important to note that different schools may have different possible classes (for example, someone has A +, A and A-, while the other only has A).


XAML:

I have a Datagrid binding on my ViewModel AllSchools collection and the SelectedSchool property of my ViewModel. When the user double-clicks the line, the event handler opens the editing panel for the selected school, changing the ViewModel IsEditing property (the Visibily editing panel is attached to the IsEditing property). Inside the editing panel I have a Datagrid (Bound to collection of Classes for the selected school), inside the Datagrid I have a TemplatedColumn with ItemsControl (binding to the collection from current class students). For each student, there is a ComboBox class for the student class in the classroom. The ItemsSource element for the ComboBox is a ViewModel PossibleGrades collection.


PROBLEM:

The problem is that when a SelectedSchool changes, any student in a previously selected school with a letter class that does not exist for the recently selected school suddenly has its own letter class equal to null (since the ItemsSource for ComboBox no longer has a grade).

Visually, everything is working fine. The editing panel correctly displays the properties of the selected school and is updated when the SelectedSchool property changes. But if I open the editing panel for the first school again, none of the drop-down lists will have the values ​​selected more, because they were all set to zero when I selected the second school.

It, like the old ComboBoxes, still has Bindings, although they no longer appear on the screen. But if it affects only the previously selected school (and not on it before).

+7
wpf mvvm binding xaml datagrid
source share
2 answers

Thanks to @OmegaMan for describing what was happening behind the scenes with binding.

I basically solved this by creating an interface that cascades PropertyChanged events.

 public interface ICascadePropertyChanged: INotifyPropertyChanged { void CascadePropertyChanged(); } 

Then I modified my ModelBase and CollectionBase classes to implement the specified interface, using Refection to recursively call CascadePropertyChanged () for auxiliary properties.

 public class ModelCollection<M> : ObservableCollection<M>, ICascadePropertyChanged where M: ModelBase { ... public void CascadePropertyChanged() { foreach (M m in this) { if (m != null) { m.CascadePropertyChanged(); } } } } public abstract class ModelBase: ICascadePropertyChanged { ... public void CascadePropertyChanged() { var properties = this.GetType().GetProperties() .Where( p => HasInterface(p.PropertyType, typeof(ICascadePropertyChanged)); // Cascade the call to each sub-property. foreach (PropertyInfo pi in properties) { ICascadePropertyChanged obj = (ICascadePropertyChanged)pi.GetValue(this); if (obj != null) { obj.CascadePropertyChanged(); } } RaisePropertyChanged(); } } 

I had to retype this from memory, so please excuse the typos.

+2
source share

Like old ComboBoxes, they have Bindings

You are getting warm.

but it looks like some "Ghost" binding, which is still bound to the old DataContext.

More like a zombie, or really an orphan. Let me explain.

At the end of the day, the binding is just an xaml compiler that reflects the link with the instance name of the instance , and if applicable, messages from InotifyPropertyChange also viewed. Keep this in mind, just one control point.

Now we know that this data is hierarchical, but the bindings, like logic, are a cruel Mistress; it does not bother. Let's look at the top-level binding target of your example:

  <DataGrid name="OuterGrid" ItemsSource={Binding SelectedSchool.Classes}"> 

The problem is that when SelectedSchool changes, any student in a previously selected school with a letter class that does not exist for a newly selected school,

The school has changed, but you are not attached to the School, but used the former SelectedSchool.Classes link . Consequently, the top changes do not flock down, and the link is actually still valid and has not changed . But visually, you changed the comboboxes ... that affected the old data.


I recommend that you take a look at the bindings, remove xxxx.yyyyy and focus only on providing xxxx or yyyy when a hierarchical change in expectation occurs; then implement a system in which both property notifications will be changed and notified at the same time; bearing in mind the corresponding xaml binding to the upper and direct sub-levels.

So maybe create a wrapper that implements InotifyPropertyChange that identifies the current, in your faux example, school, as well as the sitelinks and when the top changes are wrapped smart enough to change the sitelinks to match the top change and cascade the notification in top-level network device:

  class MyWrapper : INotifyPropertyChange { public TheXXX XXXX { get { return _xxxx; } set { _xxxx = value; NotifyChange("XXXX"); _yyyy = _XXXX.YYYY; NotifyChange("YYYY"); _zzzz = _XXXX.ZZZZ; NotifyChange("ZZZZ"); ... } ... } 
+2
source share

All Articles