Asvaldr Asvaldr - 1 month ago 13
C# Question

CollectionViewSource Not Updating with PropertyChanged

I'm having an enormous amount of trouble with ComboBoxes in a data grid.
And really would like some help, i think i've gotten confused by the amount of research and things i've tried. This really should be simple so i must be missing something.

SIMPLIFIED PROBLEM

I use a CollectionViewSource in xaml, the C# sets the source of that CollectionViewSource to an ObservableCollection in a class that is the Page's datacontext.
Adding items to the collection does not update the DataGridComboBox column containing that displays the view source.
See below the line for more detail




OVERVIEW

I have a WPF Page with a datagrid on it.
The page has its data context set to a view model.
The viewModel contains two observable collections. One for Equips and One for Locations.
Each Equip has a Location.
These are populated from a code first EF database but i believe that this problem is above that level.

The datagrid is one row per Equip. The Location column needs to be a selectable combobox that allows the user to change Location.

The only way i could get the location combobox to populate at all is by binding it to a separate collection view source.

PROBLEM

It seems that if the Page loaded event occurs prior to the ViewModel populating the ObservableCollection then the locationVwSrc will be empty and the property changed event doesn't get this to change.

IMPLEMENTATION SHORT VERSION
Page has a collection viewSource defined in the xaml.

Loaded="Page_Loaded"
Title="EquipRegPage">
<Page.Resources>
<CollectionViewSource x:Key="locationsVwSrc"/>
</Page.Resources>


The datagrid is defined with xaml.

<DataGrid x:Name="equipsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" Margin="10,10,-118,59"
ItemsSource="{Binding Equips}" EnableRowVirtualization="True" AutoGenerateColumns="False">


The combobox column defined in xaml

<DataGridComboBoxColumn x:Name="locationColumn" Width="Auto" MaxWidth="200" Header="Location"
ItemsSource="{Binding Source={StaticResource locationsVwSrc}, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
SelectedValueBinding="{Binding Location}"


The page context set to the view model

public partial class EquipRegPage : Page
{
EquipRegVm viewModel = new EquipRegVm();

public EquipRegPage()
{
InitializeComponent();
this.DataContext = viewModel;
}


Loaded event setting the context

private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Locations View Source
System.Windows.Data.CollectionViewSource locationViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
locationViewSource.Source = viewModel.Locations;
// Above does not work if the viewmodel populates these after this call, only works if its populated prior.
//TODO inotifypropertychanged not correct? This occurs before the viewmodels loads, and doesn't display.
// Therefore notify property changes aren't working.

// Using this as cheat instead instead works, i beleive due to this only setting the source when its full
//viewModel.Db.Locations.Load();
//locationViewSource.Source = viewModel.Db.Locations.Local;
//locationViewSource.View.Refresh();
}


The ViewModel class and how it loads

public class EquipRegVm : DbWrap, INotifyPropertyChanged
{
/// <summary>
/// Event triggered by changes to properties. This notifys the WPF UI above which then
/// makes a binding to the UI.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

/// <summary>
/// Notify Property Changed Event Trigger
/// </summary>
/// <param name="propertyName">Name of the property changed. Must match the binding path of the XAML.</param>
void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}


public ObservableCollection<Equip> Equips { get; set; }

public ObservableCollection<Location> Locations { get; set; }

public EquipRegVm() : base()
{
Load();
}

/// <summary>
/// Load the data from the Model.
/// </summary>
public async void Load() //TODO async an issue?
{
// EQUIPMENT
ObservableCollection<Equip> eqList = new ObservableCollection<Equip>();
var eqs = await (from eq in Db.Equips
orderby eq.Tag
select eq).ToListAsync();
foreach(var eq in eqs)
{
eqList.Add(eq);
}
Equips = eqList;
RaisePropertyChanged("Equips");


// LOCATIONS
ObservableCollection<Location> locList = new ObservableCollection<Location>();
var locs = await (from l in Db.Locations
orderby l.Name
select l).ToListAsync();
foreach (var l in locs)
{
locList.Add(l);
}
Locations = locList;
RaisePropertyChanged("Locations");
}
}

Answer

Set a Binding like below :

System.Windows.Data.CollectionViewSource locationViewSource =
           ((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
// locationViewSource.Source = viewModel.Locations;

Binding b = new Binding("Locations");
b.Source = viewModel;
b.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(locationViewSource, CollectionViewSource.SourceProperty, b);

This is all you need.

Comments