Changing CollectionViewSource Source in a MVVM world - c#

Edited: I created a new VS2010 WPF appilication with just 3 files MainWindow.xaml, MainWindow.xaml.cs, and MainWindowViewModel.cs (Listed Below). If someone feels really helpful you can recreate the problem in seconds (copy/paste). When you run the app the DataGrid will display string "OldCollection" which is wrong. If you change the ItemsSource binding to MyCollection it displays "NewCollection" which is correct.
Full Description:
At first I had a DataGrid with its ItemsSource bound to MyCollection. I have/need a method UpdateCollection that assigns a new ObservableCollection<> to MyCollection. With the addition of NotifyPropertyChange to MyCollection the UI updates.
Next it became necessary to introduce a CollectionViewSource to enable grouping. With the UI bound to MyCollectionView, calls to UpdateCollection now have no effect. The debugger confirms that MyCollectionView always contains the initial MyCollection. How can I get my NewCollection to be reflected in the View? I have tried View.Refresh(), Binding CollectionViewSource, and countless other strategies.
Note: Primarily others are concerned with the changes to Collection items not updating the view (grouping/sorting) without calling Refresh. My problem is I am assigning a brand new collection to CollectionViewSource and the view/UI never changes.
// MainWindow.xaml
<Window x:Class="CollectionView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid Name="grid" ItemsSource="{Binding MyCollectionView}" />
</Grid>
</Window>
//MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace CollectionView
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
//MainWindowViewModel.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.ComponentModel;
namespace CollectionView
{
class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
MyCollection = new ObservableCollection<MyObject>() { new MyObject() { TestString = "OldCollection" } };
MyCollectionViewSource = new CollectionViewSource();
// Bind CollectionViewSource.Source to MyCollection
Binding MyBind = new Binding() { Source = MyCollection };
BindingOperations.SetBinding(MyCollectionViewSource, CollectionViewSource.SourceProperty, MyBind);
// The DataGrid is bound to this ICollectionView
MyCollectionView = MyCollectionViewSource.View;
// This assignment here to demonstrate that View/UI does not update to show "NewCollection"
MyCollection = new ObservableCollection<MyObject>() { new MyObject() { TestString = "NewCollection" } };
}
// Collection Property
// NotifyPropertyChanged added specifically to notify of MyCollection re-assignment
ObservableCollection<MyObject> _MyCollection;
public ObservableCollection<MyObject> MyCollection
{
get { return _MyCollection; }
set
{
if (value != _MyCollection)
{
_MyCollection = value;
NotifyPropertyChanged("MyCollection");
}
}
}
public CollectionViewSource MyCollectionViewSource { get; private set; }
public ICollectionView MyCollectionView { get; private set; }
// Method updates MyCollection itself (Called via ICommand from another ViewModel)
public void UpdateCollection(ObservableCollection<MyObject> NewCollection)
{
MyCollection = NewCollection;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
class MyObject
{
public string TestString { get; set; }
}
}
Thanks,

I would choose one of the two following solutions.
First, you could take your ObservableCollection and create an ICollectionView (grouping, sorting) once. Instead of replacing the ObservableCollection you can use .Clear() and add the items from the new Collection. This has the additional bonus of not breaking your grouping and sorting.
Second approach: whenever you replace your ObservableCollection you have to create a new ICollectionView for sorting and grouping.
this._view = (ICollectionView)CollectionViewSource.GetDefaultView(this.MyCollection);
You can simply bind to your collection if you take the DefaultView
<DataGrid Name="grid" ItemsSource="{Binding MyCollection}" />
and you can throw away your CollectionViewSource code binding stuff.

The problem is definitely the fact that you aren't binding the source to your MyCollection property, you're assigning it once and it never gets updated again.
You should be doing something like the following:
MyCollectionViewSource = new CollectionViewSource();
Binding binding = new Binding();
binding.Source = MyCollection;
BindingOperations.SetBinding( MyCollectionViewSource ,
CollectionViewSource.SourceProperty,
binding );
I apologize if the above doesn't work right away without tweaking - this is usually the type of thing I set up in xaml, because it's much easier there.
<CollectionViewSource Source="{Binding MyCollection}" />
See Also: How do I change the binding of a CollectionView.Source?

The fact that the binding isn't working is incredibly weird. I encountered the exact same problem with the Xceed DataGridCollectionViewSource - but I just assumed it was because Xceed was screwed up.
My solution was to actually create a brand new DataGridCollectionViewSource every time the underlying collection was replaced and to reconfigure it programatically, and then update the myDataGrid.Source property to point to the new DataGridCollectionViewSource. That workaround certainly will work, but that completely defeats the purpose of bindings, which should be working:
void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Xceed DataGridCollectionView are terrible and do not update when the bound table changes, so we need to replace them each change.
if (e.PropertyName == "MyCollection")
{
DataGridCollectionView GridView = new DataGridCollectionView(MyViewModel.MyCollection.DefaultView);
GridView.SortDescriptions.Add(new SortDescription("DataSetID", ListSortDirection.Ascending));
uiMyCollectionGrid.ItemsSource = GridView;
}
}
Maybe this brute force solution can solve your problem? I understand if you don't even want to consider this yet since it's so dirty.

Related

LiveCharts2 with WPF & MVVM: How to bind a dynamically calculated property to a chart series?

Adhering to the MVVM pattern, how do I bind a chart to dynamically calculated property data and make the chart reflect changes?
I'm working on a GUI application where certain parameters are adjustable via controls and the function values of a computational model dependent on these parameters should be plotted in real time when any parameters are adjusted.
I'm programming in C# using WPF and want/need to use the MVVM pattern, which I have only recently learned (I'm also relatively new to C#). I chose LiveCharts2 for plotting because it can be used with the MVVM pattern; a different library that does the job (X-Y-diagrams that automatically update based on data binding) would be fine, too.
The following code is a simplification of my actual application, which is much more complex. The model is a simple parabola function y=a*x² where a is the parameter that is adjusted via a TextBox in the GUI. When adjusted, the parabola function should be updated in the diagram.
MainWindow.xaml:
<Window x:Class="TestLvc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestLvc"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainViewModel}"
Title="MainWindow" Height="600" Width="800">
<Grid>
<StackPanel>
<Grid Name="DrawingGrid" Width="400" Height="400">
<lvc:CartesianChart Series="{Binding DataPointCalculator.SeriesBindable}" />
</Grid>
<TextBox Text="{Binding DataPointCalculator.ParabolaFactor}" Width="30" />
<Button>Lose Focus from TextBox in order to update binding</Button>
<Label Name="LabelBoundValue" Content="{Binding DataPointCalculator.ParabolaFactor}" />
<Label Content="^- Bound value" />
</StackPanel>
</Grid>
</Window>
Notes:
The button doesn't do anything on its own, it just makes the TextBox lose focus and thereby apply the input value.
The Label below the button has the same binding as the TextBox in order to validate that the binding is working.
MainWindow.xaml.cs:
// Choose one:
#define use_dependent_property // Preferred solution: Doesn't work.
#define recalculate_points_in_setter // Works.
#define recalculate_property_in_setter // Should be similar to recalculate_points_in_setter and therefore working, but doesn't.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
namespace TestLvc;
public partial class MainWindow : Window
{
public MainViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new MainViewModel();
DataContext = vm;
}
}
public class MainViewModel
{
public MainViewModel()
{
DataPointCalculator = new ParabolaCalculator(5);
}
public ParabolaCalculator DataPointCalculator { get; set; }
}
public class ParabolaCalculator : INotifyPropertyChanged
{
private static readonly double[] XValues =
Enumerable.Range(-20, 41).Select(stepIndex => (double) stepIndex * 1).ToArray();
private double _parabolaFactor;
public ParabolaCalculator(double parabolaFactor = 1)
{
ParabolaFactor = parabolaFactor;
#if use_dependent_property
LineSeriesSeries.Values = DataPointsDependent;
#elif recalculate_points_in_setter
LineSeriesSeries.Values = DataPointsCalculated;
#elif recalculate_property_in_setter
LineSeriesSeries.Values = DataPointsCalculated;
#endif
SeriesBindable = new List<ISeries> {LineSeriesSeries};
}
public ObservableCollection<ObservablePoint> DataPointsCalculated { get; set; }
public ObservableCollection<ObservablePoint> DataPointsDependent => CalculateDataPoints();
public List<ISeries> SeriesBindable { get; set; }
public LineSeries<ObservablePoint> LineSeriesSeries { get; set; } = new();
public double ParabolaFactor
{
get => _parabolaFactor;
set
{
_parabolaFactor = value;
OnPropertyChanged(nameof(ParabolaFactor));
#if use_dependent_property
// Don't do calculations manually, because the data points are dynamically calculated -- but how is the chart informed that the underlying parameter has changed?
// Raising property-changed events doesn't help.
OnPropertyChanged(nameof(SeriesBindable));
OnPropertyChanged(nameof(DataPointsDependent));
OnPropertyChanged(nameof(LineSeriesSeries));
#elif recalculate_points_in_setter
RecalculateDataPoints();
#elif recalculate_property_in_setter
DataPointsCalculated = CalculateDataPoints();
#endif
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void RecalculateDataPoints()
{
DataPointsCalculated ??= CalculateDataPoints();
foreach (var point in DataPointsCalculated) point.Y = ParabolaFunction((double) point.X);
}
public ObservableCollection<ObservablePoint> CalculateDataPoints()
{
ObservableCollection<ObservablePoint> points = new();
foreach (var x in XValues) points.Add(new ObservablePoint(x, ParabolaFunction(x)));
return points;
}
private double ParabolaFunction(double x)
{
return ParabolaFactor * (x * x);
}
}
I included three compiler constants that lead to different implementations:
use_dependent_property would be the way I want it to work: The model data depends on the parameter value, so it makes sense to implement the model data as the dependent property DataPointsDependent. The chart data can be bound to this property, but the chart is not updated when the parameter is changed. How can this be achieved?
recalculate_points_in_setter: Here, the chart is bound to the property DataPointsCalculated which is a ObservableCollection<ObservablePoint> and whose ObservablePoints are updated in the parameter's setter. This works and would be OK for a simple case like this, but considering a larger number of parameters and a more complex view model, it does not seem like an option to me. It also looks to me like this solution works around the MVVM pattern, because with MVVM and data binding, you should not have to deal with manual updates, should you?
recalculate_property_in_setter: Bonus question. The chart is again bound to ObservableCollection<ObservablePoint> DataPointsCalculated and the model data are updated in the parameter's setter, but here the whole DataPointsCalculated is replaced instead of it's ObservablePoints. Given that the second approach works, why won't this one work? Based on the documentation on automatic updating, I think it should, because:
// since valuesCollection is of type ObservableCollection
// LiveCharts will update when you add, remove, replace or clear the collection
var valuesCollection = new ObservableCollection();
var lineSeries = new LineSeries();
lineSeries.Values = valuesCollection;
Note on LiveCharts2: This is still a pre-release, but I tried the same with LiveCharts0 with the same outcome.

WPF Listbox not populated with items from ObservableCollection

The main-window is listening for plugging in/out USB-devices. If it is an usb-key/disk it collects a file-list from that device and show that list in a second window.
While debugging I can see that the NewUsbFiles observablecollection get's populated with 117 items. I see that the property UsbFile (before calling the showdialog) has 117 items, but nevertheless the listbox is empty.
Any thoughts ?
The method to populate / create that second window:
NewUsbFiles = new ObservableCollection<UsbFile>();
UpdateNewUsbFiles(driveName);
Application.Current.Dispatcher.Invoke(delegate
{
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles = NewUsbFiles
};
usbFileSelector.ShowDialog();
});
The UsbFile-class:
public class UsbFile
{
public string UsbFileName { get; set; }
public string OnTableFileName { get; set; }
public bool Ignored { get; set; } = false;
public UsbFile(string fileName)
{
var fileInfo = new FileInfo(fileName);
UsbFileName = fileInfo.FullName;
OnTableFileName = $"{fileInfo.CreationTime:yyMMddHHmmsss}_{fileInfo.Name}";
}
}
The XAML of the second window :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:MainWindow="clr-namespace:PartyPictures.WPF.MainWindow" x:Name="wUsbFileSelector"
x:Class="PartyPictures.WPF.UsbFileSelector"
mc:Ignorable="d"
Title="USB" HorizontalAlignment="Center" VerticalAlignment="Center" WindowStyle="ToolWindow" ScrollViewer.VerticalScrollBarVisibility="Auto" SizeToContent="WidthAndHeight">
<StackPanel x:Name="spUsbFileList">
<ListBox x:Name="ImageListbox"
DataContext="{Binding ElementName=wUsbFileSelector}"
ItemsSource="{Binding UsbFiles}"
Background="AliceBlue" ScrollViewer.HorizontalScrollBarVisibility="Disabled" MinWidth="200" MinHeight="200">
</ListBox>
</StackPanel>
</Window>
The code-behind of the second window :
public partial class UsbFileSelector : Window
{
public ObservableCollection<UsbFile> UsbFiles { get; set; } = new ObservableCollection<UsbFile>();
public UsbFileSelector()
{
InitializeComponent();
}
}
Inside the window you can see InitializeComponent method. It creates all of the stuff defined in XAML and applies all bindings. After binding has been appplied with your empty collecton (that you have created with default property value) the binding will not know about any change of that property, that was the right answer.
But implementing INotifyPropertyChanged is more about viewmodel instances, not visual.
I really suggest you use Dependency Property for windows and controls if you want to bind. There are some reasons for that:
Dependency property setter has built-in notify mechanism.
If you bind one DP to another DP, value is shared in between.
After all, it is WPF approach =)
Here is how your window will look like after change
public partial class UsbFileSelector : Window
{
public static readonly DependencyProperty UsbFilesProperty =
DependencyProperty.Register("UsbFiles", typeof(ObservableCollection<UsbFile>), typeof(UsbFileSelector));
public ObservableCollection<UsbFile> UsbFiles
{
get { return (ObservableCollection<UsbFile>) GetValue(UsbFilesProperty); }
set { SetValue(UsbFilesProperty, value); }
}
public UsbFileSelector()
{
InitializeComponent();
}
}
Also I strongly recommend you to use some WPF inspector tool while developing for the WPF, for example, snoop. You can navigate through the controls and properties while app is running and find issues much quickly you can from the code or from stackoverflow =)
In
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles = NewUsbFiles
};
you are assigning a new value to the UsbFiles property without firing a property change notification for that property.
You could either implement INotifyPropertyChanged and fire the PropertyChanged event or make UsbFiles a dependency property.
Or you pass NewUsbFiles as constructor argument and assign it before calling InitializeComponent
public UsbFileSelector(ObservableCollection<UsbFile> usbFiles)
{
UsbFiles = usbFiles;
InitializeComponent();
}
and call it like this:
var usbFileSelector = new UsbFileSelector(NewUsbFiles)
{
Owner = this
};
Note that if you always pass a new collection, using ObservableCollection isn't actually necessary. You never add or remove elements to/from the collection, so there is no need for a change notification.
Someone posted (and deleted the comment) that I should add
DataContext = this;
To
public UsbFileSelector()
{
InitializeComponent();
DataContext = this;
}
Someone else mentioned (that comment too was deleted) that this was not necessary because of the
DataContext="{Binding ElementName=wUsbFileSelector}"
in the XAML.
BUT it turned out that removing the DataContext line from the XAML and setting it to this in code was the sollution. No idea why but that did it.
EDIT just to make clear that this is not a good solution and works only by accident, try the following:
// this works
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();
// this does not
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
await Task.Delay(10);
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();
All the answers already given are correct, the heart of your problem is the
UsbFiles = NewUsbFiles
which causes the binding to "break" - UsbFiles is no longer pointing to the collection that is bound to the Listbox.
Another possible way to solve this would be to simply leave the bound collection alone and just repopulate the contents.
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles.Clear();
foreach (var uf in NewUsbFiles) {
UsbFiles.Add(uf);
}
};

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");
}
}
It seems you haven't been able to break the problem down into small enough problems. The question seems to be a mix of ComboBoxes in Datagrid, Asynchronously setting CollectionViewSource source, loading data from a database.
I suggest that it would be beneficial to either consider
recreating the problem (or soultion) with the minimum moving parts i.e. a XAML file and a ViewModel with pre canned data.
or decoupling your existing code. It appears that Page knows about your ViewModel explicitly (EquipRegVm viewModel = new EquipRegVm();), and you ViewModel knows explicitly about Databases and how to load itself. Oh snap, now our views are coupled to your database? Isn't that the point of patterns like MVVM so that we are not coupled?
Next I look at some the code and see some more (of what I would call) anti-patterns.
Settable collection properties
code behind for the page (all could live in the XAML)
But I think basically if you just changed your code in 3 places you should be fine.
Change 1
/*foreach(var eq in eqs)
{
eqList.Add(eq);
}
Equips = eqList;
RaisePropertyChanged("Equips");*/
foreach(var eq in eqs)
{
Equips.Add(eq);
}
Change 2
/*foreach (var l in locs)
{
locList.Add(l);
}
Locations = locList;
RaisePropertyChanged("Locations");*/
foreach (var l in locs)
{
Locations.Add(l);
}
Change 3
Either just remove the usage of the CollectionViewSource (what does it offer you?) or use binding to set the source. As you are currently manually setting the Source (i.e. locationViewSource.Source = viewModel.Locations;) you have opted out of getting that value updated when the PropertyChanged event has been raised.
So if you just delete the CollectionViewSource, then you just have to bind to the Locations property. If you decide to keep the CollectionViewSource then I would suggest deleting the page codebhind and just changing the XAML to
<CollectionViewSource x:Key="locationsVwSrc" Source="{Binding Locations}" />
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.

Gridview doesn't change when data changes

I've added an observable data and bound it to my data grid as follows.
private ObservableCollection<Order> _allOrders;
public ObservableCollection<Order> AllOrders
{
get { return _allOrders;}
set { _allOrders = value; OnPropertyChanged(); }
}
public Presenter() { _allOrders = new ObservableCollection<Order>(...); }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When I set breakpoint on the event that is supposed to filter the data, I set the property AllOrder to null. I can verify using the watch that it's set to that. However, the view isn't updated, so I'm guessing that I forgot something. The view model class Presenter implements INotifyPropertyChanged interface, of course.
What's missing?
Edit
The XAML code for the grid looks as follows.
<DataGrid x:Name="dataGrid"
ItemsSource="{Binding AllOrders}"
AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn" ...>
Assuming that you set DataContext accordingly and AllOrders binding works initially if you want to filter items in the UI, without change collection, it's much easier when you use ListCollectionView with a Filter. WPF does not bind directly to collection but to a view - MSDN.
private readonly ObservableCollection<Order> _allOrders;
private readonly ListCollectionView _filteredOrders;
public ICollectionView FilteredOrders
{
get { return _filteredOrders; }
}
public Presenter()
{
_allOrders = new ObservableCollection<Order>(...);
_filteredOrders = new ListCollectionView(_allOrders);
_filteredOrders.Filter = o => ((Order)o).Active;
}
and in XAML
<DataGrid ... ItemsSource="{Binding FilteredOrders}">
when you want to manually refresh UI just call Refresh
_filteredOrders.Refresh();
Apart from that nothing changes in the view model. You still add/remove items to _allItems and changes should be picked up automatically by UI
Do you set the property AllOrders only in the constructor? If so, then do not set the field _allOrders but the property AllOrders. If you set the field then notification is never raised.

Strange behavior binding WPF combobox

I'm having trouble binding to the text property of a combobox. It seems like it doesn't bind until I select something in the combobox. Then it works fine.
Here is the code straight from a test app:
View
<ComboBox ItemsSource="{Binding ListItems}"
Text="{Binding Test}" />
ViewModel
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> ListItems { get; set; }
public ViewModel()
{
ListItems = new ObservableCollection<string>();
ListItems.Add("Southwest");
ListItems.Add("South");
}
public string Test
{
get { return "South"; }
set { PropertyChanged(this, new PropertyChangedEventArgs("Test")); }
}
}
However, when I reverse the order of the observable collection items, everything works fine.
ListItems.Add("South");
ListItems.Add("Southwest");
What's going on here?
The text property doesn't work like this.
Read this document:
http://msdn.microsoft.com/en-us/library/system.windows.controls.combobox.text.aspx
Like suggested by hameleon86 use the selecteditem instead.
I think it Works if you reverse the order of your collection maybe because the Text property take the first item of the collection by default
I think you might want to do :
PropertyChanged(this, new PropertyChangedEventArgs("ListItems"));
After you inserted the element.

Categories

Resources