views:

263

answers:

3

I have a ListView that is bound to an ObservableCollection which is itself derived from an IQueryable. When I refresh the collection, after items are added, edited or a button is clicked (to see if the database has new items from other users). The listbox refreshes and selects the first item in the collection.

I want to keep the same selected item (If it still exists) after a refresh but because the Collection has been replaced I cannot seem to compare equality between an item in the old collection and the new one. All attemtps never have a match.

How should this be done?

Below are some relevant code snipets if anyone wants to see them:

The ListView, MouseDoubleClick Event opens an edit window

<ListView x:Name="IssueListView"
          ItemsSource="{Binding Issues}"
          ItemTemplate="{StaticResource ShowIssueDetail}" 
          IsSynchronizedWithCurrentItem="True"
          MouseDoubleClick="IssueListView_MouseDoubleClick" />

It's DataTemplate

<DataTemplate x:Key="ShowIssueDetail">
    <Border CornerRadius="3" Margin="2" MinWidth="400" BorderThickness="2" 
        BorderBrush="{Binding Path=IssUrgency, 
                     Converter={StaticResource IntToRYGBBorderBrushConverter}}">
          <StackPanel Orientation="Vertical">
             <TextBlock Text="{Binding Path=IssSubject}" 
                        Margin="3" FontWeight="Bold" FontSize="14"/>

          <!--DataTrigger will collapse following panel for simple view-->
          <StackPanel Name="IssueDetailPanel" Visibility="Visible" Margin="3">
            <StackPanel Width="Auto" Orientation="Horizontal">
                <TextBlock Text="Due: " FontWeight="Bold"/>
                <TextBlock Text="{Binding Path=IssDueDate, StringFormat='d'}"
                         FontStyle="Italic" HorizontalAlignment="Left"/>
            </StackPanel>
            <StackPanel Width="Auto" Orientation="Horizontal">
               <TextBlock Text="Category: " FontWeight="Bold"/>
               <TextBlock Text="{Binding Path=IssCategory}"/>
            </StackPanel>
          </StackPanel>

        </StackPanel>
        </Border>

        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding Path=StatusBoardViewModel.ShowDetailListItems, 
                         RelativeSource={RelativeSource FindAncestor,
                         AncestorType={x:Type Window}}}" Value="False">
                <Setter TargetName="IssueDetailPanel" 
                        Property="Visibility" Value="Collapsed"/>
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>

The Issues collection is refreshed by executing a QueryIssues Method that builds a Linq To SQL query programatically based on bound controls. IssuesQuery is an IQueryable property

public void QueryIssues()
{
    // Will show all items
    IssuesQuery = from i in db.Issues
             orderby i.IssDueDate, i.IssUrgency
             select i;

    // Filters out closed issues if they are not to be shown
    if (includeClosedIssues == false)
    {
        IssuesQuery = from i in IssuesQuery
                 where i.IssIsClosed == false
                 select i;
    }

    // Filters out Regular Tasks if they are not to be shown
    if (showTasks == false)
    {
        IssuesQuery = from i in IssuesQuery
                 where i.IssIsOnStatusBoard == true
                 select i;
    }

    // Filters out private items if they are not to be shown
    if (showPrivateIssues == false)
    {
        IssuesQuery = from i in IssuesQuery
                 where i.IssIsPrivate == false
                 select i;
    }

    // Filters out Deaprtments if one is selected
    if (departmentToShow != "All")
    {
        IssuesQuery = from i in IssuesQuery
                 where i.IssDepartment == departmentToShow
                 select i;
    }

    Issues = new ObservableCollection<Issue>(IssuesQuery);
}
A: 

When faced with this issue in the past i have wrapped the items i want to display in a small view model class, then given each an extra property IsSelected and then bound this property to the ListBoxItem using an ItemContainerStyle. That way i keep track of what is selected.

Aran Mulholland
I am not sure I follow. When the CollectionViewSource is replaced the object that was selected is gone. Are you saying I need to keep sufficient information on the selected item to be able to find a match by iterating all the items on the new list? I was hoping for a cleaner solution.
Mike B
+1  A: 

Because you are getting completely new objects there is no other way to match equality than to find the item that matches the old (if it exists) and select it. You are replacing your collection which means that you have to track the selected item yourself.

Other options:

You could keep a collection and manually add/remove items based on the query results (ie don't destroy the current item).

Aran Mulholland
A: 

One way to do this in your view model:

// The ListView's SelectedItem is bound to CurrentlySelectedItem
var selectedItem = this.CurrentlySelectedItem;

// ListView is bound to the Collection property
// setting Collection automatically raises an INotifyPropertyChanged notification
this.Collection = GetIssues(); // load collection with new data

this.CurrentlySelectedItem = this.Collection.SingleOrDefault<Issue>(x => x.Id == selectedItem.Id);
Ben Gribaudo