2012-02-02 16 views
5

Possiedo un DataGrid a cui un utente può aggiungere elementi immettendo dati nell'ultima riga. Ho anche un pulsante che elimina l'elemento attualmente selezionato. Ma quando viene selezionata l'ultima riga (vuota, per l'aggiunta di nuovi elementi), qualunque sia l'ultimo elemento selezionato rimane in SelectedItem. Quindi, se apro la finestra, seleziona l'ultima riga e premo il pulsante Elimina, eliminerà la prima riga, poiché è selezionata per impostazione predefinita e selezionando l'ultima riga non è stato cambiato l'oggetto SelectedItem. Qualche buon modo per affrontare questo?WPF DataGrid SelectedItem

per chiarire: SelectedItem = "{Binding X}"

X nel ViewModel non cambia quando si seleziona l'ultima riga (il setter non viene richiamato a tutti). Non sono sicuro che la proprietà SelectedItem cambi di per sé, ma suppongo che non lo sia.

C'è anche un'eccezione quando seleziono l'ultima riga (bordo rosso), ma quando faccio nuovamente clic per iniziare a inserire dati, il bordo rosso scompare. Non sono sicuro se questi due sono correlati.

risposta

10

Esegui il seguente esempio e vedrai perché non funziona.

XAML:

<Window x:Class="DataGridTest.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"> 
    <DockPanel> 
     <TextBlock DockPanel.Dock="Bottom" Text="{Binding SelectedItem, ElementName=dataGrid}"/> 
     <TextBlock DockPanel.Dock="Bottom" Text="{Binding SelectedItem}"/> 
     <DataGrid x:Name="dataGrid" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" CanUserAddRows="True" CanUserDeleteRows="True" AutoGenerateColumns="False"> 
      <DataGrid.Columns> 
       <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/> 
       <DataGridTextColumn Header="Last Name" Binding="{Binding FirstName}"/> 
      </DataGrid.Columns> 
     </DataGrid> 
    </DockPanel> 
</Window> 

Codice-behind:

namespace DataGridTest 
{ 
    using System.Collections.Generic; 
    using System.Collections.ObjectModel; 
    using System.ComponentModel; 
    using System.Windows; 

    public partial class MainWindow : Window, INotifyPropertyChanged 
    { 
     private readonly ICollection<Person> items; 
     private Person selectedItem; 

     public MainWindow() 
     { 
      InitializeComponent(); 

      this.items = new ObservableCollection<Person>(); 
      this.items.Add(new Person 
       { 
        FirstName = "Kent", 
        LastName = "Boogaart" 
       }); 
      this.items.Add(new Person 
      { 
       FirstName = "Tempany", 
       LastName = "Boogaart" 
      }); 

      this.DataContext = this; 
     } 

     public ICollection<Person> Items 
     { 
      get { return this.items; } 
     } 

     public Person SelectedItem 
     { 
      get { return this.selectedItem; } 
      set 
      { 
       this.selectedItem = value; 
       this.OnPropertyChanged("SelectedItem"); 
      } 
     } 

     private void OnPropertyChanged(string propertyName) 
     { 
      var handler = this.PropertyChanged; 

      if (handler != null) 
      { 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 
    } 

    public class Person 
    { 
     public string FirstName 
     { 
      get; 
      set; 
     } 

     public string LastName 
     { 
      get; 
      set; 
     } 

     public override string ToString() 
     { 
      return FirstName + " " + LastName; 
     } 
    } 
} 

Come si può vedere quando si esegue, la selezione del "nuovo" fila provoca un valore sentinella da impostare come l'elemento selezionato nella DataGrid. Tuttavia, WPF non è in grado di convertire quell'elemento sentinella in un Person, pertanto il binding SelectedItem non riesce a convertire.

Per risolvere questo problema, è possibile inserire un convertitore sull'attacco che rileva la sentinella e restituisce null quando rilevato. Ecco un convertitore che fa così:

namespace DataGridTest 
{ 
    using System; 
    using System.Windows.Data; 

    public sealed class SentinelConverter : IValueConverter 
    { 
     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 
      return value; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 
      if (value != null && string.Equals("{NewItemPlaceholder}", value.ToString(), StringComparison.Ordinal)) 
      { 
       return null; 
      } 

      return value; 
     } 
    } 
} 

Come si può vedere, si tratta di una sfortunata necessità di testare contro il valore ToString() della sentinella, perché è un tipo interno. In alternativa (o in aggiunta) è possibile verificare che GetType().Name sia NamedObject.

+2

5 anni più tardi, ma potrebbe anche metterlo qui: è possibile confrontarlo con CollectionView.NewItemPlaceholder, che è la stessa cosa che si sta facendo, senza la necessità di masterizzare la stringa "{NewItemPlaceholder}" sul codice. –

0

Difficile dire senza codice, ma vorrei osservare quanto segue.

Assicurarsi che ogni volta che un elemento viene eliminato ed è anche l'elemento selezionato, impostare l'elemento selezionato associato alla proprietà nel ViewModel su null. Dovrai assicurarti che il tuo SelectedItem associato alla proprietà non sia vincolato oneway.

0

Sembra che tu abbia dimenticato di impostare la modalità di rilegatura e che il valore predefinito sia impostato su OneWay. Ciò significa che qualsiasi modifica apportata alla tua vista non si propagherà al tuo modello di vista.

E assicurarsi sempre di avere il giusto datacontext.

Spero che questo aiuti.