WPF Observable Collection. Count == 0 on refresh but has many items - wpf

Good morning,
Apologies for the mass of text im about to provide but...
I have a WPF ListView with its ItemsSource bound to an ObservableCollection in it's respective ViewModel. When the window loads, the observable collection is populated from a web service by means of a Command. However, as the program is running, this collection is periodically updated by a BackgroundWorker thread in order to add new items to the ObservableCollection.
This mechanism works fine. The ListView is updated without issue on both the UI thread and the background thread. However, when an item in the ListView is double clicked, a new window is opened to display details of the Ticket object contained within the aforementioned ObservableCollection.
I have a private method which fires each time the ObservableCollection's set method is called which serves to find the Ticket item from within the collection which has been opened in the new window and update its properties according to the items in the newly updated ObservableCollection. Before doing this update, I check to ensure the ObservableCollection.Count is greater than 1, there is no point doing an update if there is nothing to update from!
My issue is that the ObservableCollection.Count property ALWAYS equates to 0. But I know this not to be true as the ListView is still updating its items with new Ticket objects added to this collection, if the count of this collection really was 0, then this would be reflected by the ListView also having no items in it as it is bound to this collection.
So what is going on here ? Im wondering maybe because the BackgroundWorker is calling;
myCollection = new ObservableCollection();
on a different thread to the UI that when I check the count on the UI thread, the wrong collection object is actually tested for 'Count'. But this still doesn't explain why the ListView reflects the contents of the ObservableCollection without issue.
Again, apologies for the wall-o-text but I wanted to explain this issue fully.
Thank you for your time and any input you may give.
EDIT FOR MORE DETAIL
The list view section of user control
<ListView x:Name="lvTicketSummaries" ItemsSource="{Binding Path=TicketSummaries}" Grid.Row="1" Width="Auto" Height="Auto" SizeChanged="lvTicketSummaries_SizeChanged" SelectionMode="Single" Foreground="Black" Background="#3BFFFFFF" ItemContainerStyle="{DynamicResource ListViewItem}">
<ListView.View>
<GridView AllowsColumnReorder="True">
<GridViewColumn Header="ID"
DisplayMemberBinding="{Binding ID}"
Width="25"/>
<GridViewColumn Header="Status"
DisplayMemberBinding="{Binding Status}"
Width="25"/>
<GridViewColumn Header="Subject"
DisplayMemberBinding="{Binding Subject}"
Width="25"/>
<GridViewColumn Header="Requester"
DisplayMemberBinding="{Binding Owner.Name}"
Width="25"/>
</GridView>
</ListView.View>
</ListView>
The view model of the above user control
Here you see the TicketSummaries collection which the list view is bound to as well as the refreshOpenTicket() method used to update the Ticket property in a child view model which the new instance of itself in the newly refreshed collection.
public class MainWindowViewModel : ViewModelBase
{
private DispatcherTimer timer;
private BackgroundWorker worker_TicketLoader;
private ObservableCollection<Ticket> ticketSummaries;
public ObservableCollection<Ticket> TicketSummaries
{
get { return ticketSummaries; }
set
{
ticketSummaries = value;
this.RaisePropertyChanged(p => p.TicketSummaries);
refreshOpenTicket();
}
}
private void refreshOpenTicket()
{
// Check there are actually some tickets to refresh
if (TicketSummaries.Count < 1)
return;
// Check we have created the view model
if (TicketDetailsViewModel != null)
{
// Check the ticket loaded correctly
if (TicketDetailsViewModel.Ticket != null)
{
// Find a ticket in the collection with the same id
Ticket openTicket = TicketSummaries.Where(
ticket => ticket.ID == TicketDetailsViewModel.Ticket.ID
).First();
// Make sure we are not going to overrite with a null reference
if (openTicket != null)
TicketDetailsViewModel.Ticket = openTicket;
}
}
}
This collection is updated from various sources via the following command
private void Execute_GetAgentsTickets(object agent)
{
TicketSummaries = new ObservableCollection<Ticket>();
var agentsTickets = ticketService.GetAgentsTickets((Agent)agent);
agentsTickets.ForEach(
ticket => TicketSummaries.Add(ticket)
);
AppSettings.LoggedAgent = (Agent)agent;
RequeryCommands();
}
But occasionally this collection will be modified off-thread by the background worker
void worker_TicketLoader_DoWork(object sender, DoWorkEventArgs e)
{
State = "Loading Tickets";
IsLoadingTickets = true;
var agentsTickets = ticketService.GetAgentsTickets(AppSettings.LoggedAgent);
TicketSummaries = new ObservableCollection<Ticket>();
foreach (Ticket ticket in agentsTickets)
{
TicketSummaries.AddOnUIThread<Ticket>(ticket);
}
refreshOpenTicket();
lastRefresh = DateTime.Now;
}
Just in case it makes a difference, the TicketSummaries.AddOnUIThread(ticket); is a solution I found on StackOverflow to trying to add items to a collection which is a binding source to UI controls off-thread and is as;
public static void AddOnUIThread<T>(this ICollection<T> collection, T item)
{
Action<T> addMethod = collection.Add;
Application.Current.Dispatcher.BeginInvoke(addMethod, item);
}
I hope this helps shed some more light on the situation.
Thanks again for your time.

You haven't provided enough information to diagnose, but I'm guessing the ListView is bound to a property and when you replace the ObservableCollection, you're not updating that property. Hence, the ListView is still attached to the original collection whilst your VM code is working with a new one.
Why replace the OC at all? Why not just update it as items come through from the middle tier? If you really must replace it, be sure to go via a property and raise change notification for that property.

Related

When does a ComboBox receive its Items if it is bound to ObservableCollection?

I am attempting a save/load mechanism for re-use in a business application. I have the groundwork laid to read/write ObservableCollection<> to/from xml, using attributes to describe my class properties. That part is working. I can save an ObservableCollection to XML, then load the XML back into an ObservableCollection the next time I run the program.
Here's my problem. I have a ComboBox whose ItemsSource.DataContext = ObservableCollection<Flag>;
When I run the program, it accepts the binding just fine, but the ComboBox itself does not populate itself until later. I want to set the SelectedItem to be the first item in the ObservableCollection<Flag> that I have loaded from XML. Nothing happens though, because as the program is executing it's startup methods, the Items.Count remains 0. I'm guessing the ComboBox doesn't populate itself until it gets focus. How do I work around this? Can I force the ComboBox to populate itself? I've tried cb_ARDAR_ARFlag.Items.Refresh();
XAML:
<ComboBox Name="cb_ARDAR_ARFlag"
ItemsSource="{Binding}"
SelectionChanged="cb_ARDAR_ARFlag_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Flag_Desc}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Relevant Code:
public MainWindow()
{
InitializeComponent();
setDataBinding();
loadSavedData();
}
private void setDataBinding()
{
//Returns ObservableCollection<Flag>
cb_ARDAR_ARFlag.DataContext = Flag.getOCAvailableFlags();
}
private void loadSavedData()
{
//When it gets here the ItemCount is 0 so nothing happens.
//Refresh didn't help
cb_ARDAR_ARFlag.Items.Refresh();
Flag f = Enforcement_Save.loadOCARFlag().First();
cb_ARDAR_ARFlag.SelectedItem = f;
}
At this point I'm still not sure the code at the end will successfully identify the correct 'flag' item to be selected, or if I'll end up using Linq. Which, by the way, leads me to another question. Can you Linq to ComboBox.Items somehow?
I have recreated your issue, and your are correct, the items count is = 0 in the loadSavedData method. The combobox doesn't seem to be populated until after the constructor has fully executed.
In the meantime I found you can use the ItemsSource property to load the combobox at the time you want it loaded:
cb_ARDAR_ARFlag.ItemsSource = Flag.getOCAvailableFlags();

Databinding not updating ListBox completely

I'm using the MVVM pattern and have a databound Listbox that isn't updating completely.
There is a modelview that contains an Observable collection of Machines which is bound to the list:
<ListBox Name="MachinesList"
Height="300"
Width="290"
DataContext="{Binding Path=AllMachines}"
SelectionMode="Single"
ItemsSource="{Binding}" SelectionChanged="MachinesList_SelectionChanged"
HorizontalAlignment="Right"
>
The collection AllMachines Contains an observable collection of MachineModelViews which are in turn bound to a MachineView that presents the name and location of the machine:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Label Name="NameLabel" Content="{Binding Path=Name}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Width="50" />
<Label Content="Location:" Width="120"
HorizontalAlignment="Right"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Target="{Binding ElementName=locationLabel}"
/>
<Label Content="{Binding Path=Location.Name}" Name="locationLabel" HorizontalContentAlignment="Center" Width="60"/>
</StackPanel>
The problem:
When values are added to the collection things update okay. When a machine is deleted however only the Label bound to Location.Name updates the other two remain in the listbox. I've confirmed that the collection is actually updating and removing the MachineModelView Correctly but some how the label with it's name and the "label label" with "Location:" continues to exist until the application is restarted:
before:
after delete:
after app restart:
The delete button calls a command which removes the item from the private member that backs the AllMachines property and from the repository (which ultimately plugs into a database via Entity Framework):
RelayCommand _deleteCommand;
public ICommand DeleteCommand
{
get
{
if (_deleteCommand == null)
{
_deleteCommand = new RelayCommand(
param => this.Delete(),
null
);
}
return _deleteCommand;
}
}
void Delete()
{
if (_selectedMachine != null && _machineRepository.GetMachines().
Where(i => i.Name == _selectedMachine.Name).Count() > 0)
{
_machineRepository.RemoveMachine(_machineRepository.GetMachines().
Where(i => i.Name == _selectedMachine.Name).First());
_allMachines.Remove(_selectedMachine);
}
}
Note: There can only be one item with a name in AllMachines (this is handled by the add logic in the repository and command itself) so removing the "First" one should be fine.
The AllMachines property:
public ObservableCollection<MachineViewModel> AllMachines
{
get
{
if(_allMachines == null)
{
List<MachineViewModel> all = (from mach in _machineRepository.GetMachines()
select new MachineViewModel(mach, _machineRepository)).ToList();
_allMachines = new ObservableCollection<MachineViewModel>(all);
}
return _allMachines;
}
private set
{
_allMachines = value;
}
}
The selection changed event handler:
private void MachinesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0 && e.AddedItems[0] is MachineViewModel)
((MachinesViewModel)this.DataContext).SelectedMachine = (MachineViewModel)e.AddedItems[0];
}
If 'AllMachines is your actual observable collection, I would bind the itemssource to that instead of binding it to ur datacontext, and then bind ur viewmodel as your datacontext. It may also be helpful to call the updatesourcetrigger after your itemssource bind.
DataContext="{Binding Path=YourViewModel}"
SelectionMode="Single"
ItemsSource="{Binding AllMachines, UpdateSourceTrigger="PropertyChanged"}"
And you can use that updatesourcetrigger on some of your labels as well where you are binding. Other than that, you xaml looks ok, hard for anyone to really say until they see everything.
_machineRepository.RemoveMachine(_machineRepository.GetMachines().
Where(i => i.Name == _selectedMachine.Name).First());
AllMachines.Remove(_selectedMachine);
Instead of field _allMachines.Remove remove directly from the property AllMachines like
AllMachines.Remove
I hope this will help.
if you code the way you said here, then all should work. pls post the missing code pieces:
your AllMaschines Property
your MachinesList_SelectionChanged event <-- when doing MVVM you almost not need this
pls debug your Delete() method just to be sure that: _allMachines.Remove(_selectedMachine); is hit and the machine is really removed from your collection.
I found my problem, really stupid: I have an event that fires when the repository updates. Before I only had an add command and I was adding the ability to delete when this problem came up. Turns out the event was being handled by the MachinesModelView to update it's internal variable that is the source for the AllMachines property:
void OnMachineAddedToRepository(object sender, MachineAddedOrRemovedEventArgs e)
{
var viewModel = new MachineViewModel(e.NewMachine, _machineRepository);
this.AllMachines.Add(viewModel);
}
I modified the event arguments to toggle between add and delete but forgot to update the event handler. Now it does work:
void OnMachineAddedOrRemovedFromRepository(object sender, MachineAddedOrRemovedEventArgs e)
{
if (e.Added)
{
var viewModel = new MachineViewModel(e.NewMachine, _machineRepository);
this.AllMachines.Add(viewModel);
}
else if (AllMachines.Where(i => i.Name == e.NewMachine.Name).Count() > 0)
AllMachines.Remove(AllMachines.Where(i => i.Name == e.NewMachine.Name).First());
}
What made this so hard to track down is that the extra item only lived very briefly until the AllMachines.Remove part of the Delete command ran. So checking the pre-delete count, and the post delete count looked right it was the guts in the middle when the item is being deleted from the repository that fired off an event that added the item back and left it in this weird state. Kind of odd that it consistently only had the location part of the MachineModelView floating around. I now just let the event handler add or remove the items from the _allMachines variable and don't touch it explicitly in the delete command (the delete command just deletes it from the repository and lets the UI variables catch up in the fraction of a second it takes for the event to trigger). Thanks a bunch guys for your help.

How to pass the selectedItem of a listbox to the View Model

This is a running question that I have updated to hopefully be a little more clear.
In short what I am trying to accomplish is pass a property from a listbox selected item to the viewmodel so that this property can be used within a new query. In the code below the Listbox inherits databinding from the parent object. The listbox contains data templates (user controls) used to render out detailed results.
The issue I am having is that within the user control I have an expander which when clicked calls a command from the ViewModel. From what I can see the Listbox object is loosing it's data context so in order for the command to be called when the expander is expanded I have to explicitly set the datacontext of the expander. Doing this seems to instantiate a new view model which resets my bound property (SelectedItemsID) to null.
Is there a way to pass the selected item from the view to the viewmodel and prevent the value from being reset to null when a button calls a command from within the templated listbox item?
I realize that both Prism and MVVMLite have workarounds for this but I am not familiar with either framework so I don't know the level of complexity in cutting either of these into my project.
Can this be accomplished outside of Prism or MVVMLite?
original post follows:
Within my project I have a listbox usercontrol which contains a custom data template.
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding SearchResults[0].Results,
Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionChanged="ResultListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<dts:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch">
<!-- CFS Template -->
<dts:TypeTemplateSelector.CFSTemplate>
<DataTemplate>
<qr:srchCFS />
</DataTemplate>
</dts:TypeTemplateSelector.CFSTemplate>
<!-- Person Template -->
<dts:TypeTemplateSelector.PersonTemplate>
<DataTemplate>
<qr:srchPerson />
</DataTemplate>
</dts:TypeTemplateSelector.PersonTemplate>
<!-- removed for brevity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectionChanged calls the following method from the code behind
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);//binds to VM
}
Within the ViewModel I have the following property
public string SelectedItemID
{
get
{
return this._SelectedItemID;
}
set
{
if (this._SelectedItemID == value)
return;
this._SelectedItemID = value;
}
}
the listbox template contains a custom layout with an expander control. The expander control is used to display more details related to the selected item. These details (collection) are created by making a new call to my proxy. To do this with an expander control I used the Expressions InvokeCommandAction
<toolkit:Expander Height="auto"
Margin="0,0,-2,0"
Foreground="#FFFFC21C"
Header="View Details"
IsExpanded="False"
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
Style="{StaticResource DetailExpander}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<i:InvokeCommandAction Command="{Binding GetCfsResultCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Within the ViewModel the delegate command GetCFSResultCommandExecute which is called is fairly straight forward
private void GetCfsResultCommandExecute(object parameter)
{
long IdResult;
if (long.TryParse(SelectedItemID, out IdResult))
{
this.CallForServiceResults = this._DataModel.GetCFSResults(IdResult);}
The issue I am experiencing is when selecting a listbox Item the selectionchanged event fires and the property SelectedItemID is updated with the correct id from the selected item. When I click on the expander the Command is fired but the property SelectedItemID is set to null. I have traced this with Silverlight-Spy and the events are consistent with what you would expect when the expander is clicked the listbox item loses focus, the expander (toggle) gets focus and there is a LeftMouseDownEvent but I cannot see anything happening that explains why the property is being set to null. I added the same code used in the selection changed event to a LostFocus event on the listboxt item and still received the same result.
I'd appreciate any help with understanding why the public property SelectedItemID is being set to null when the expander button which is part of the listbox control is being set to null. And of course I would REALLY appreciate any help in learning how prevent the property from being set to null and retaining the bound ID.
Update
I have attempted to remove the datacontext reference from the Expander as this was suggested to be the issue. From what I have since this is a data template item it "steps" out of the visual tree and looses reference to the datacontext of the control which is inherited from the parent object. If I attempt to set the datacontext in code for the control all bindings to properties are lost.
My next attempt was to set the datacontext for the expander control within the constructor as
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
This approach does not seem to work as InvokeCommandAction is never fired. This command only seems to trigger if data context is set on the expander.
thanks in advance
With this line you create a new SearchViewModelDataSource using its default constructor.
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
I guess this is why you find null because this is the default value for reference type.
You can resolve the issue by setting DataContext to the same instance used to the main controll (you can do it by code after all components are initialized).
Hope this help!
Edit
I don't think that binding may be lost after setting datacontext from code. I do it every time I need to share something between two or more model.
In relation to the code you've written :
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
Instead of using this.cfsExpander you can try to use the FindName method. Maybe this will return you the correct instance.
object item = this.FindName("expander_name");
if ((item!=null)&&(item is Expander))
{
Expander exp = item as Expander;
exp.DataContext = this._ViewModel;
}
Try if its work for you.
Of course, this._ViewModel has to expose a property of type ICommand named GetCfsResultCommand but I think this has been already done.
While this was a hacky approach I found an intermediate solution to get the listbox item value to the view model. I ended up using the selection changed event and passing the value directly to a public property wihtin my view model. Not the best approach but it resolved the issue short term
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
MySelectedValue = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);
}
For this to fire I did have to also setup a property changed handler within the view to push the change to the VM. You can disregard the MySelectedValue line as it is secondary code I have in place for testing.
For those intereted the generic property changed handler
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

WPF DataGrid: How do I databind the properties of the SelectedItem to trigger INotifyPropertyChangedEvents?

I'm trying to do this as MVVM as possible:
My Model (InterestTypeEntity) implements INotifyPropertyChanged.
My ViewModel (InterestTypeAllViewModel) has an ObservableCollection that binds to a DataGrid. When changes are made to it, it sends those changes (add/remove) to the Database.
the problem is, I want to also be able to update the database when the properties of the objects within the collection change. I'm not sure how to do that? Here's my code so far...
XAML:
<DataGrid Name="TestGrid" Grid.Row="3" Grid.ColumnSpan="2" AutoGenerateColumns="False"
ItemsSource="{Binding IntTypes}" SelectedItem="{Binding CurrentIntType}">
<DataGrid.Columns>
<DataGridTextColumn Header="Interest ID" Binding="{Binding IntType}" />
<DataGridTextColumn Header="Interested Parties Description" Binding="{Binding Description}" MaxWidth="500" />
</DataGrid.Columns>
</DataGrid>
ViewModel Code:
public ObservableCollection<InterestTypeEntity> IntTypes
{
get { return DataRepository.InterestTypeEntities; }
}
public InterestTypeEntity CurrentIntType { get; set; }
public Int16 IntType
{
get { return CurrentIntType.IntType; }
set
{
if (value != CurrentIntType.IntType)
{
CurrentIntType.IntType = value;
OnPropertyChanged("IntType");
}
}
}
public String Description
{
get { return CurrentIntType.Description; }
set
{
if (value != CurrentIntType.Description)
{
CurrentIntType.Description = value;
OnPropertyChanged("Description");
}
}
}
Don't create a collection of model objects, and don't implement IntType and Description properties on your (current) view model. And unless you have some other reason to do so, don't implement property-change notification in your model.
Instead, make IntTypes a collection of InterestTypeEntityViewModel objects.
This class wraps InterestTypeEntity. It exposes IntType and Description properties that a) wrap the underlying InterestTypeEntity properties and b) performs property change notification. If you make its constructor take an InterestTypeEntity argument, it's easy to populate in your view model:
IntTypes = new ObservableCollection<InterestTypeEntityViewModel>(
DataRepository.InterestTypeEntities.Select(x => new InterestTypeEntityViewModel(x));
Bind the ItemsSource to this collection. (Also, make CurrentIntType a property of type InterestTypeEntityViewModel and raise PropertyChanged when it changes.)
Edit:
If the owning view model needs to be notified when properties change on the items in its collection, it's pretty simple to make it handle the PropertyChanged events they're raising. In your constructor, add:
foreach (InterestTypeEntityViewModel vm in IntTypes)
{
vm.PropertyChanged += InterestTypeEntityViewModel_PropertyChanged;
}
and this method:
private void InterestTypeEntityViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
InterestTypeEntityViewModel vm = (InterestTypeEntityViewModel) sender;
// check e.PropertyName and do whatever you need to do here.
}
Don't forget to unregister the event handler if you remove an object from your collection; otherwise, the child view model objects won't get disposed until the parent one does.
Note, by the way, that by implementing the view models this way, you can exercise a lot of control over your updates to the underlying entity model. For instance, you can implement a command in your parent view model that does all of the updates in a single operation, and another one that lets the user discard all of the changes they've made in the UI without performing an update. And all of this logic is very nicely decoupled from the actual presentation layer.
Please see my answer here. It will give you an observable collection that tells you when the collection changes, or when an item within the collection changes.
General strategy:
Add TwoWay to your binding:
SelectedItem="{Binding CurrentIntType,Mode=TwoWay}"
And then subscribe to the changed event of the observable collection in your ViewModel. When the collection changes, send it to your model/DAL and persist it.

WPF Bindings won't update unless I recreate the entire list?

Background:
I have a List<T> in my ViewModel defined as...
private List<FooBar> _fooBars;
public List<FooBar> FooBars
{
get { return _fooBars; }
set
{
if (value == _fooBars) return;
_fooBars = value;
OnPropertyChanged("FooBars");
}
}
FooBar is defined as...
public class FooBar
{
public string FooBarProperty { get; set; }
// more properties here
}
Meanwhile, I have a GridView that binds to this list.
<ListView
ItemsSource="{Binding FooBars}">
<ListView.View>
<GridView
<GridViewColumn
Header="Foo Bar Prop"
DisplayMemberBinding={Binding FooBarProperty} />
<!--more columns here-->
</GridView>
</ListView.View>
</ListView>
I run my app and all this works great. My FooBarProperties fill as expected.
The Problem:
In response to some user action. I edit one of the FooBar objects in FooBars, and call OnPropertyChanged() to let WPF know I want the bindings to update.
FooBars[2].FooBarProperty = "Some new text here";
OnProperChanged("FooBars"); // WPF is not listening to this :(
Only one problem: this completely doesn't work. The GridView never udpates.
A Workaround:
After much head scratching (and table banging and swearing), I came up with this replacement for the above:
FooBars[2].FooBarProperty = "Some new text here";
FooBars = FooBars.Select(fb => fb).ToList(); // this works but why is it necessary?
Works like a charm, but why do I need to do this?
NOTE: I tried switching from List<FooBar> to ObservableCollection<FooBar>, but that made absolutely no difference.
My Questions:
Very simply, why do I need this crazy code that basically copies the list into itself to get my bindings to update?
Is there a better way to force an update?
Thanks.
EDIT -- The Final Solution:
Thanks to #Botz3000's answer below, I switched FooBars back to ObservableCollection<FooBar>, and I modified FooBar so it implements INotifyChanged. Everything works as it's supposed to and no weird list copying required.
If you are not changing the object that is stored in the ObservableCollection itself, but rather a property of it, then the CollectionChanged event won't be triggered.
Your FooBarProperty needs a PropertyChanged("FooBarProperty"), too. Since the list items bind to this property, they'll need notifications for that.

Resources