2010-09-02 253 views
7

我的场景:我有一个后台线程轮询更改并定期更新WPF DataGrid的ObservableCollection(MVVM样式)。用户可以点击DataGrid中的一行,然后在相同的主视图上的相邻UserControl中显示该行的“详细信息”。如何防止WPF DataGrid从项目更新时取消选择SelectedItem?

当后台线程有更新时,它循环访问ObservableCollection中的对象,并在单个对象发生更改时替换它们(换句话说,我没有将全新的ObservableCollection重新绑定到DataGrid,而是将集合;这允许DataGrid在更新期间保持排序顺序)。

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

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

回答

6

如果您的网格是单选,我的建议是您使用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示例:

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

    } 
} 
+0

真的不适合我KarmicPuppet。问题是我在做MVVM,这使得它有点不同的问题。我不能只处理按钮点击处理程序中的所有内容。 – 2010-09-02 18:51:30

+0

我并不是建议您在按钮点击处理程序中处理所有内容。这只是一个示例代码。这个想法是,你在ViewModel中做了一些与我的例子中的Button_Click事件类似的东西。让我试着给你写一个MVVM的例子。我会尽快给您回复。 – ASanch 2010-09-02 19:53:11

+0

请参阅编辑。我希望这给你一个更好的主意。它包含一个ListBox,其数据每5秒随机更新一次。你会看到它仍然保持更新后的选择。 – ASanch 2010-09-02 20:27:10

3

我没有与WPF DataGrid的工作,但我想尝试这种方法:

一个属性添加到视图模型将保存当前选定项的值。

将此物业绑定至TwoWay

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

+0

那是我第一次想到周杰伦,但至今没有骰子。它仍然重置它。 – 2010-09-02 16:15:54

+0

@Chris选定的项目本身是否正在更新,或者只是其他项目? – Jay 2010-09-02 16:32:17

+0

所有项目正在更新。 – 2010-09-02 17:44:59

1

你可以,在更新集合的逻辑,节省掉CollectionView.Current项目引用另一个变量。然后,在完成更新后,调用CollectionView.MoveCurrentTo(variable)重置所选项目。