Results 1 to 2 of 2

Thread: How to preserve selection in an MVVM-bound WPF ListView?

  1. #1
    Herman is online now VB.NET Forum Idol
    .NET Framework
    .NET 4.0
    Join Date
    Oct 2011
    Location
    Montreal, QC, CA
    Posts
    866
    Reputation
    1213

    How to preserve selection in an MVVM-bound WPF ListView?

    I have a stupid problem, which seems it should have a solution implemented in the control itself, but I cannot figure out how to do it.

    I have the following ListView bound to the window's view model:

            <ListView x:Name="lvItems" Grid.Row="0" Margin="0" FontSize="10" FontWeight="SemiBold"
    SelectionMode="Single" ItemsSource="{Binding CurrentSnapshot}">

    <ListView.Resources>
    <ContextMenu x:Key="cmItemsContextMenu">
    <MenuItem x:Name="miManageAlerts" Header="Manage Alerts..." />
    </ContextMenu>
    </ListView.Resources>

    <ListView.ItemContainerStyle>
    <Style TargetType="{x:Type ListViewItem}">
    <Setter Property="ContextMenu" Value="{StaticResource cmItemsContextMenu}" />
    </Style>
    </ListView.ItemContainerStyle>

    <ListView.View>
    <GridView>
    <GridViewColumn Width="Auto" DisplayMemberBinding="{Binding MachineName}" Header="Machine Name " />
    <GridViewColumn Width="Auto" DisplayMemberBinding="{Binding CpuUsagePercent}" Header="CPU Usage (%) " />
    <GridViewColumn Width="Auto" DisplayMemberBinding="{Binding DiskUsagePercent}" Header="HDD Usage (%) " />
    <GridViewColumn Width="Auto" DisplayMemberBinding="{Binding MemoryAvailableMB}" Header="Available Memory (MB) " />
    <GridViewColumn Width="Auto" DisplayMemberBinding="{Binding NetUsageInKBytesSec}" Header="Network In (KB/s) " />
    <GridViewColumn Width="Auto" DisplayMemberBinding="{Binding NetUsageOutKBytesSec}" Header="Network Out (KB/s) " />
    <GridViewColumn Width="Auto" DisplayMemberBinding="{Binding TerminalServicesSessions}" Header="# of Sessions " />
    </GridView>
    </ListView.View>
    </ListView>



    Everything is bound nicely and everything works up to this point. However the view model has a timer running that periodically refreshes the CurrentSnapshot object. When it does, because the ViewModel implements INotifyPropertyChanged, the ItemsSource for the listview is refreshed, the new items are bound, and the existing selection is lost. Without MVVM I would likely have something like this:

    Code:
        Private Sub lvItems_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
            _selectedSample = lvItems.SelectedItem
    
            If _selectedSample IsNot Nothing Then
                _lastSelectedMachine = _selectedSample.MachineName
            Else
                _lastSelectedMachine = ""
            End If
        End Sub
    And then I would RemoveHandler and AddHandler before and after assigning to the ItemsSource. I cannot see how I can do this while respecting the MVVM pattern however. I have seen some examples online, that suggest using an attached behavior and whatnot. Isn't there a simple and easy way to handle this without having to go dig through dependency properties?


    Thank you.

  2. #2
    Herman is online now VB.NET Forum Idol
    .NET Framework
    .NET 4.0
    Join Date
    Oct 2011
    Location
    Montreal, QC, CA
    Posts
    866
    Reputation
    1213
    So I finally figured it out, and while it's simple and works well, it's rather poorly documented. Here is what I did:

     <ListView Grid.Row="0" Margin="0" FontSize="10" FontWeight="SemiBold" ItemsSource="{Binding CurrentSnapshotView}" 
    SelectionMode="Single" IsSynchronizedWithCurrentItem="True" >


    In the view model:

    Code:
        Private _currentSnapshot As New ObservableCollection(Of KeyedPerformanceSample)
        Public Property CurrentSnapshot As ObservableCollection(Of KeyedPerformanceSample)
            Get
                Return _currentSnapshot
            End Get
            Set(value As ObservableCollection(Of KeyedPerformanceSample))
                _currentSnapshot = value
    
                RemoveHandler CurrentSnapshotView.CurrentChanged, AddressOf CurrentSnapshotView_CurrentChanged
                _currentSnapshotView = CollectionViewSource.GetDefaultView(_currentSnapshot)
                AddHandler CurrentSnapshotView.CurrentChanged, AddressOf CurrentSnapshotView_CurrentChanged
    
                NotifyPropertyChanged("CurrentSnapshot")
                NotifyPropertyChanged("CurrentSnapshotView")
            End Set
        End Property
    
        Private _currentSnapshotView As ICollectionView = CollectionViewSource.GetDefaultView(CurrentSnapshot)
        Public ReadOnly Property CurrentSnapshotView As ICollectionView
            Get
                Return _currentSnapshotView
            End Get
        End Property
    
        Public Sub CurrentSnapshotView_CurrentChanged(sender As Object, e As EventArgs)
            SelectedSample = CurrentSnapshotView.CurrentItem
        End Sub
    
        Private Sub ViewModel_PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Handles Me.PropertyChanged
            If e.PropertyName = "CurrentSnapshot" Then
                If SelectedSample IsNot Nothing Then
                    Dim newSelection = CurrentSnapshot.Where(Function(x) x.Key = SelectedSample.Key).FirstOrDefault
                    If newSelection IsNot Nothing Then CurrentSnapshotView.MoveCurrentTo(newSelection)
                Else
                    CurrentSnapshotView.MoveCurrentTo(Nothing)
                End If
            End If
        End Sub
    So this way everything is in the ViewModel and nothing bleeds out!

    Hopefully this will help someone else.

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •