2010-09-02 21 views
7

Mein Szenario: Ich habe einen Hintergrundthread, der nach Änderungen fragt und regelmäßig die ObservableCollection (MVVM-Stil) von WPF DataGrid aktualisiert. Der Benutzer kann auf eine Zeile im DataGrid klicken und die "Details" dieser Zeile in einem benachbarten UserControl in derselben Hauptansicht anzeigen.Verhindern, dass WPF DataGrid SelectedItem deaktiviert, wenn Elemente aktualisiert wurden?

Wenn der Hintergrundthread aktualisiert wird, durchläuft er die Objekte in der ObservableCollection und ersetzt einzelne Objekte, wenn sie sich geändert haben (mit anderen Worten, ich binde keine neue ObservableCollection an das DataGrid, sondern stattdessen einzelne Elemente in die Sammlung; dies ermöglicht dem DataGrid, während Aktualisierungen die Sortierreihenfolge beizubehalten).

Das Problem ist, dass nachdem ein Benutzer eine bestimmte Zeile ausgewählt hat und die Details im angrenzenden UserControl angezeigt werden, wenn der Hintergrund Thread das DataGrid aktualisiert das DataGrid das SelectedItem verliert (es wird zurück auf den Index von -1 zurückgesetzt).

Wie kann ich das SelectedItem zwischen Aktualisierungen der ObservableCollection beibehalten?

Antwort

6

Wenn Ihr Raster eine einzelne Auswahl ist, ist mein Vorschlag, dass Sie das CollectionView als ItemsSource anstelle der tatsächlichen ObservableCollection verwenden. Stellen Sie dann sicher, dass Datagrid.IsSynchronizedWithCurrentItem auf True festgelegt ist. Schließlich, am Ende Ihrer "Item-Logik ersetzen", verschieben Sie einfach das CurrentItem der CollectionView auf das entsprechende neue Element.

Unten finden Sie ein Beispiel, das dies veranschaulicht. (Ich benutze hier allerdings eine ListBox. Ich hoffe, es funktioniert gut mit Ihrem Datagrid).

EDIT - NEW PROBE UNTER VERWENDUNG 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> 

-Code-Behind:

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 BEISPIEL:

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> 

-Code-behind:

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

Funktioniert nicht wirklich für mich KarmicPuppet. Problem ist, ich mache MVVM und das macht es ein etwas anderes Problem. Ich kann nicht alles in einem Button-Click-Handler behandeln. –

+0

Ich schlage nicht vor, dass Sie alles in einem Button-Klick-Handler behandeln. Das war nur ein Beispielcode. Die Idee ist, dass Sie in Ihrem ViewModel etwas tun, das in meinem Beispiel dem Ereignis Button_Click ähnelt. Lassen Sie mich versuchen, Ihnen ein MVVM-Beispiel zu schreiben. Ich melde mich bei dir. – ASanch

+0

Siehe Bearbeiten. Ich hoffe, das gibt dir eine bessere Idee. Es enthält eine ListBox, deren Daten alle 5 Sekunden zufällig aktualisiert werden. Sie werden feststellen, dass die Auswahl nach den Aktualisierungen beibehalten wird. – ASanch

3

Ich habe nicht mit der WPF-Datagrid gearbeitet, aber ich würde versuchen, diesen Ansatz:

eine Eigenschaft hinzufügen zu dem View-Modell, das den Wert des aktuell ausgewählten Elements halten wird.

Bindung SelectedItem an diese neue Eigenschaft mit TwoWay.

Auf diese Weise, wenn der Benutzer eine Zeile auswählt, wird das Ansichtsmodell aktualisiert, und wenn die ObservableCollection aktualisiert wird, hat dies keine Auswirkungen auf die Eigenschaft, an die SelectedItem gebunden ist. Wenn ich gefesselt bin, würde ich nicht erwarten, dass es auf die Art und Weise zurückgesetzt wird, wie du es siehst.

+0

Das war mein erster Gedanke Jay, aber bisher keine Würfel.Es setzt es immer noch zurück. –

+0

@Chris Wird das ausgewählte Element selbst aktualisiert oder nur andere Elemente? – Jay

+0

Alle Artikel werden aktualisiert. –

1

Sie könnten in der Logik, die die Sammlung aktualisiert, die CollectionView.Current Artikel Verweis auf eine andere Variable sparen. Wenn Sie mit dem Aktualisieren fertig sind, rufen Sie CollectionView.MoveCurrentTo (Variable) auf, um das ausgewählte Element zurückzusetzen.

Verwandte Themen