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

问题描述 投票:9回答:4

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

[当后台线程进行更新时,它将在ObservableCollection中的对象之间循环,并在单个对象已更改的情况下替换它们(换句话说,我不是将整个新的ObservableCollection绑定到DataGrid,而是替换集合中的单个项目;这允许DataGrid在更新期间保持排序顺序。

问题是,在用户选择了特定的行并在相邻的UserControl中显示了详细信息之后,当后台线程更新DataGrid时,DataGrid会丢失SelectedItem(将其重置为索引-1)。

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

c# wpf multithreading datagrid observablecollection
4个回答
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>

隐藏代码:

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);
            }
        }
    }
}

OLD SAMPLE:

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>

隐藏代码:

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);
        }

    }
}

3
投票

我还没有使用WPF DataGrid,但是我会尝试这种方法:

向视图模型添加将保存当前所选项目的值的属性。

使用SelectedItemTwoWay绑定到此新属性。

这样,当用户选择一行时,它将更新视图模型,并且当ObservableCollection更新时,它不会影响SelectedItem绑定的属性。受到束缚,我不希望它会以您看到的方式重置。


1
投票

您可以在更新Collection的逻辑中,将CollectionView.Current项目引用保存到另一个变量。然后,在完成更新后,调用CollectionView.MoveCurrentTo(variable)以重置所选项目。


0
投票

现在可能已经解决了,但这是我所做的一个示例,它适用于一排推车。我有一个带ObservableCollection和CollectionView的数据网格,该网格由包含购物车的局部变量填充:

        _cartsObservable = new ObservableCollection<FormOrderCart>(_formCarts);
        _cartsViewSource = new CollectionViewSource { Source = _cartsObservable };
        CartsGrid.ItemsSource = _cartsViewSource.View;

稍后更改函数中的有效购物车属性-不是直接更改,但重要的是ObservableCollection中的项已更改。为了反映更改并保持选择,我只需刷新CollectionViewSource(注意内部视图):

        var cart = _formCarts.ElementAt(index-1);
        cart.Valid = validity;
        _cartsViewSource.View.Refresh();

这样,如果购物车无效,我可以将网格中的行颜色更改为红色,但也可以保留我的选择。

编辑:拼写

© www.soinside.com 2019 - 2024. All rights reserved.