Note that there are some explanatory texts on larger screens.

plurals
  1. POWPF Observable Collection. Count == 0 on refresh but has many items
    text
    copied!<p>Good morning,</p> <p>Apologies for the mass of text im about to provide but...</p> <p>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.</p> <p>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. </p> <p>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!</p> <p>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.</p> <p>So what is going on here ? Im wondering maybe because the BackgroundWorker is calling;</p> <pre><code>myCollection = new ObservableCollection(); </code></pre> <p>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.</p> <p>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.</p> <p><strong>EDIT FOR MORE DETAIL</strong></p> <p><em>The list view section of user control</em></p> <pre><code>&lt;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}"&gt; &lt;ListView.View&gt; &lt;GridView AllowsColumnReorder="True"&gt; &lt;GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" Width="25"/&gt; &lt;GridViewColumn Header="Status" DisplayMemberBinding="{Binding Status}" Width="25"/&gt; &lt;GridViewColumn Header="Subject" DisplayMemberBinding="{Binding Subject}" Width="25"/&gt; &lt;GridViewColumn Header="Requester" DisplayMemberBinding="{Binding Owner.Name}" Width="25"/&gt; &lt;/GridView&gt; &lt;/ListView.View&gt; &lt;/ListView&gt; </code></pre> <p><em>The view model of the above user control</em></p> <p>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.</p> <pre><code> public class MainWindowViewModel : ViewModelBase { private DispatcherTimer timer; private BackgroundWorker worker_TicketLoader; private ObservableCollection&lt;Ticket&gt; ticketSummaries; public ObservableCollection&lt;Ticket&gt; TicketSummaries { get { return ticketSummaries; } set { ticketSummaries = value; this.RaisePropertyChanged(p =&gt; p.TicketSummaries); refreshOpenTicket(); } } private void refreshOpenTicket() { // Check there are actually some tickets to refresh if (TicketSummaries.Count &lt; 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 =&gt; ticket.ID == TicketDetailsViewModel.Ticket.ID ).First(); // Make sure we are not going to overrite with a null reference if (openTicket != null) TicketDetailsViewModel.Ticket = openTicket; } } } </code></pre> <p>This collection is updated from various sources via the following command</p> <pre><code>private void Execute_GetAgentsTickets(object agent) { TicketSummaries = new ObservableCollection&lt;Ticket&gt;(); var agentsTickets = ticketService.GetAgentsTickets((Agent)agent); agentsTickets.ForEach( ticket =&gt; TicketSummaries.Add(ticket) ); AppSettings.LoggedAgent = (Agent)agent; RequeryCommands(); } </code></pre> <p>But occasionally this collection will be modified off-thread by the background worker </p> <pre><code>void worker_TicketLoader_DoWork(object sender, DoWorkEventArgs e) { State = "Loading Tickets"; IsLoadingTickets = true; var agentsTickets = ticketService.GetAgentsTickets(AppSettings.LoggedAgent); TicketSummaries = new ObservableCollection&lt;Ticket&gt;(); foreach (Ticket ticket in agentsTickets) { TicketSummaries.AddOnUIThread&lt;Ticket&gt;(ticket); } refreshOpenTicket(); lastRefresh = DateTime.Now; } </code></pre> <p>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;</p> <pre><code>public static void AddOnUIThread&lt;T&gt;(this ICollection&lt;T&gt; collection, T item) { Action&lt;T&gt; addMethod = collection.Add; Application.Current.Dispatcher.BeginInvoke(addMethod, item); } </code></pre> <p>I hope this helps shed some more light on the situation. Thanks again for your time.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload