如何防止 WPF DataGrid 在项目更新时取消选择 SelectedItem?
Posted
技术标签:
【中文标题】如何防止 WPF DataGrid 在项目更新时取消选择 SelectedItem?【英文标题】:How To Prevent WPF DataGrid From De-Selecting SelectedItem When Items Updated? 【发布时间】:2011-04-07 10:10:38 【问题描述】:我的场景:我有一个后台线程来轮询更改并定期更新 WPF DataGrid 的 ObservableCollection(MVVM 样式)。用户可以单击 DataGrid 中的一行,并在同一主视图上的相邻 UserControl 中显示该行的“详细信息”。
当后台线程有更新时,它会循环遍历 ObservableCollection 中的对象,并在个别对象发生变化时替换它们(换句话说,我不是将一个全新的 ObservableCollection 重新绑定到 DataGrid,而是替换集合;这允许 DataGrid 在更新期间保持排序顺序)。
问题在于,在用户选择了特定行并且详细信息显示在相邻的 UserControl 中之后,当后台线程更新 DataGrid 时,DataGrid 会丢失 SelectedItem(它被重置为索引 -1)。
如何在 ObservableCollection 更新之间保留 SelectedItem?
【问题讨论】:
【参考方案1】:如果您的网格是单选的,我的建议是您使用 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);
旧样本:
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);
【讨论】:
对我来说真的不起作用 KarmicPuppet。问题是我正在做 MVVM,这使它成为一个稍微不同的问题。我不能只处理按钮单击处理程序中的所有内容。 我并不是建议您在按钮单击处理程序中处理所有事情。那只是一个示例代码。这个想法是您在 ViewModel 中执行与我的示例中的 Button_Click 事件类似的操作。让我试着给你写一个 MVVM 例子。我会回复你的。 见编辑。我希望这能给你一个更好的主意。它包含一个 ListBox,其数据每 5 秒随机更新一次。您会看到更新后它仍然保留选择。【参考方案2】:我没有使用过 WPF DataGrid,但我会尝试这种方法:
向视图模型添加一个属性,该属性将保存当前选定项的值。
使用TwoWay
将SelectedItem
绑定到这个新属性。
这样,当用户选择一行时,它将更新视图模型,并且当ObservableCollection
更新时,它不会影响SelectedItem
绑定到的属性。受到约束,我不希望它会像你看到的那样重置。
【讨论】:
这是我首先想到的杰,但到目前为止还没有骰子。它仍在重置它。 @Chris 是所选项目本身正在更新,还是只是其他项目?【参考方案3】:您可以在更新 Collection 的逻辑中,将 CollectionView.Current 项目引用保存到另一个变量。然后,完成更新后,调用 CollectionView.MoveCurrentTo(variable) 来重置所选项目。
【讨论】:
【参考方案4】:它现在可能已经解决了,但这是我所做的一个示例,它适用于购物车网格。 我有一个带有 ObservableCollection 和 CollectionView 的数据网格,由包含购物车的局部变量填充:
_cartsObservable = new ObservableCollection<FormOrderCart>(_formCarts);
_cartsViewSource = new CollectionViewSource Source = _cartsObservable ;
CartsGrid.ItemsSource = _cartsViewSource.View;
稍后我在函数中更改了购物车的 Valid prop - 不是直接更改,但重要的是 ObservableCollection 中的项目发生了更改。为了反映更改和维护选择,我只需刷新 CollectionViewSource(注意内部视图):
var cart = _formCarts.ElementAt(index-1);
cart.Valid = validity;
_cartsViewSource.View.Refresh();
这样,如果购物车无效,我可以将网格中的行颜色更改为红色,但也可以保留我的选择。
编辑:拼写
【讨论】:
以上是关于如何防止 WPF DataGrid 在项目更新时取消选择 SelectedItem?的主要内容,如果未能解决你的问题,请参考以下文章
如何防止 ComboBox 中的 NewItemPlaceholder 行绑定到与 WPF 中的 DataGrid 相同的 DataTable
c# wpf datagrid 模板列修改某个单元格,更新所选行另一个单元格的值,如何做到呢?
WPF DataGrid如何获取ItemsSource更新时
如何自动更新 WPF DataGrid 和 xml 之间的绑定