当项目更新时,如何防止WPF DataGrid取消选择SelectedItem?

Chr*_*mes 9 c# wpf datagrid multithreading observablecollection

我的情况:我有一个后台线程来轮询更改并定期更新一个WPF DataGrid的的ObservableCollection(MVVM式).用户可以单击DataGrid中的一行,并在同一主视图上的相邻UserControl中显示该行的"详细信息".

当后台线程穿过的ObservableCollection对象的更新,它的周期,如果他们已经改变取代单个对象(换句话说,我不是重新绑定了一个全新的ObservableCollection到DataGrid,但在收集而不是取代单个项目,这允许DataGrid在更新期间维护排序顺序).

的问题是,用户已经选择了特定的行和所述详细信息显示在相邻的用户控件之后,当后台线程更新数据网格数据网格失去的SelectedItem(它被重置回-1的索引).

如何在ObservableCollection的更新之间保留SelectedItem?

ASa*_*nch 7

如果你的网格是单选的,我的建议是你使用CollectionView作为ItemsSource而不是实际的ObservableCollection.然后,确保Datagrid.IsSynchronizedWithCurrentItem设置为true.最后,在"替换项目逻辑"结束时,只需将CollectionView的CurrentItem移动到相应的新项目.

以下是演示此内容的示例.(我现在在这里使用ListBox.希望它与你的Datagrid一起正常工作).

编辑 - 使用MVVM的新示例:

XAML

<Window x:Class="ContextTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Name="window"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <ListBox x:Name="lb" DockPanel.Dock="Left" Width="200" 
                 ItemsSource="{Binding ModelCollectionView}"
                 SelectionMode="Single" IsSynchronizedWithCurrentItem="True">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=Name}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <TextBlock Text="{Binding ElementName=lb, Path=SelectedItem.Description}"/>

    </DockPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

代码隐藏:

using System;
using System.Windows;
using System.Windows.Data;
using System.Collections.ObjectModel;
using System.Windows.Threading;

namespace ContextTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new ViewModel();
        }
    }

    public class ViewModel
    {
        private DataGenerator dataGenerator;
        private ObservableCollection<Model> modelCollection;
        public ListCollectionView ModelCollectionView { get; private set; }

        public ViewModel()
        {
            modelCollection = new ObservableCollection<Model>();
            ModelCollectionView = new ListCollectionView(modelCollection);

            //Create models
            for (int i = 0; i < 20; i++)
                modelCollection.Add(new Model() { Name = "Model" + i.ToString(), 
                    Description = "Description for Model" + i.ToString() });

            this.dataGenerator = new DataGenerator(this);
        }

        public void Replace(Model oldModel, Model newModel)
        {
            int curIndex = ModelCollectionView.CurrentPosition;
            int n = modelCollection.IndexOf(oldModel);
            this.modelCollection[n] = newModel;
            ModelCollectionView.MoveCurrentToPosition(curIndex);
        }
    }

    public class Model
    {
        public string Name { get; set; }
        public string Description { get; set; }
    }

    public class DataGenerator
    {
        private ViewModel vm;
        private DispatcherTimer timer;
        int ctr = 0;

        public DataGenerator(ViewModel vm)
        {
            this.vm = vm;
            timer = new DispatcherTimer(TimeSpan.FromSeconds(5), 
                DispatcherPriority.Normal, OnTimerTick, Dispatcher.CurrentDispatcher);
        }

        public void OnTimerTick(object sender, EventArgs e)
        {
            Random r = new Random();

            //Update several Model items in the ViewModel
            int times = r.Next(vm.ModelCollectionView.Count - 1);
            for (int i = 0; i < times; i++)
            {   
                Model newModel = new Model() 
                    { 
                        Name = "NewModel" + ctr.ToString(),
                        Description = "Description for NewModel" + ctr.ToString()
                    };
                ctr++;

                //Replace a random item in VM with a new one.
                int n = r.Next(times);
                vm.Replace(vm.ModelCollectionView.GetItemAt(n) as Model, newModel);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

旧样本:

XAML:

<Window x:Class="ContextTest.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">
    <StackPanel>
        <ListBox x:Name="lb" SelectionMode="Single" IsSynchronizedWithCurrentItem="True" SelectionMode="Multiple">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=Name}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <TextBlock Text="{Binding ElementName=lb, Path=SelectedItem.Name}"/>
        <Button Click="Button_Click">Replace</Button>


    </StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

代码隐藏:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace ContextTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        ObservableCollection<MyClass> items;
        ListCollectionView lcv;

        public MainWindow()
        {
            InitializeComponent();

            items = new ObservableCollection<MyClass>();
            lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(items);
            this.lb.ItemsSource = lcv;
            items.Add(new MyClass() { Name = "A" });
            items.Add(new MyClass() { Name = "B" });
            items.Add(new MyClass() { Name = "C" });
            items.Add(new MyClass() { Name = "D" });
            items.Add(new MyClass() { Name = "E" });

        }

        public class MyClass
        {
            public string Name { get; set; }
        }

        int ctr = 0;
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MyClass selectedItem = this.lb.SelectedItem as MyClass;
            int index = this.items.IndexOf(selectedItem);
            this.items[index] = new MyClass() { Name = "NewItem" + ctr++.ToString() };
            lcv.MoveCurrentToPosition(index);
        }

    }
}
Run Code Online (Sandbox Code Playgroud)