2017-06-19 56 views
3

我是继问题:当DataGrid中前两行的选择和第一行被删除,所选择的行下面成为第一行被取消。如果我对任何列进行排序,则该行的选择会返回。或者,如果我关闭了窗口,我会得到实际选定的取消选定行的信息(查询UsersViewModel中的底层绑定属性SelectedUsers的内容 - 在OnClosing方法中完成)。有没有人可以帮助或解释我,如果我做错了什么或者这可能是一个错误。我在下面提供了完整的源代码。感谢帮助。DataGrid的选择问题

MainWindow.xaml

<Window x:Class="DeleteFirstRowIssue.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:local="clr-namespace:DeleteFirstRowIssue" 
     Title="MainWindow" Height="350" Width="400"> 
    <Window.Resources> 
     <Style x:Key="CustomDataGridCellStyle" TargetType="{x:Type DataGridCell}"> 
       <Style.Triggers> 
        <Trigger Property="IsSelected" Value="True"> 
         <Setter Property="Background" Value="Red"/> 
         <Setter Property="FontWeight" Value="Bold"/> 
         <Setter Property="Foreground" Value="Black"/> 
        </Trigger> 
       </Style.Triggers> 
      </Style> 
    </Window.Resources> 
    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="25"/> 
      <RowDefinition Height="*"/> 
      <RowDefinition Height="30"/> 
      <RowDefinition Height="30"/> 
      <RowDefinition Height="30"/> 
     </Grid.RowDefinitions> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="*"/> 
     </Grid.ColumnDefinitions> 
     <Label Content="Users:" Grid.Row="0" Grid.Column="0"/> 
     <local:CustomDataGrid x:Name="UsersDataGrid" ItemsSource="{Binding UsersViewSource.View}" SelectionMode="Extended" AlternatingRowBackground="LightBlue" AlternationCount="2" 
           SelectionUnit="FullRow" IsReadOnly="True" SnapsToDevicePixels="True" AutoGenerateColumns="False" Grid.Row="1" Grid.Column="0" CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" 
           SelectedItemsList="{Binding SelectedUsers, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="False" CellStyle="{StaticResource CustomDataGridCellStyle}"> 
      <local:CustomDataGrid.Columns> 
       <DataGridTextColumn Header="Nickname:" Width="*" Binding="{Binding Nickname}"/> 
       <DataGridTextColumn Header="Age:" Width="*" Binding="{Binding Age}"/> 
      </local:CustomDataGrid.Columns> 
     </local:CustomDataGrid> 
     <Button Grid.Row="2" Grid.Column="0" Margin="5" MaxWidth="80" MinWidth="80" Content="Delete 1st row" Command="{Binding DeleteFirstUserCommand}"/> 
     <Button Grid.Row="3" Grid.Column="0" Margin="5" MaxWidth="80" MinWidth="80" Content="Delete last row" Command="{Binding DeleteLastUserCommand}"/> 
     <Button Grid.Row="4" Grid.Column="0" Margin="5" MaxWidth="80" MinWidth="80" Content="Initialize Grid" Command="{Binding InitializeListCommand}"/> 
    </Grid> 
</Window> 

MainWindow.xaml.cs

using System; 
using System.Collections; 
using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.Text; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Input; 

namespace DeleteFirstRowIssue 
{ 
    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 
      DataContext = new UsersViewModel(); 
     } 

     protected override void OnClosing(CancelEventArgs e) 
     { 
      UsersViewModel uvm = (UsersViewModel)DataContext; 
      if (uvm.SelectedUsers.Count > 0) 
      { 
       StringBuilder sb = new StringBuilder(uvm.SelectedUsers.Count.ToString() + " selected user(s):\n"); 
       foreach (UserModel um in uvm.SelectedUsers) 
       { 
        sb.Append(um.Nickname + "\n"); 
       } 
       MessageBox.Show(sb.ToString()); 
      } 
      base.OnClosing(e); 
     } 
    } 

    public class UsersViewModel : INotifyPropertyChanged 
    { 
     private IList selectedUsers; 
     public IList SelectedUsers 
     { 
      get { return selectedUsers; } 
      set 
      { 
       selectedUsers = value; 
       OnPropertyChanged("SelectedUsers"); 
      } 
     } 

     public CollectionViewSource UsersViewSource { get; private set; } 
     public ObservableCollection<UserModel> Users { get; set; } 
     public ICommand DeleteFirstUserCommand { get; } 
     public ICommand DeleteLastUserCommand { get; } 
     public ICommand InitializeListCommand { get; } 
     public event PropertyChangedEventHandler PropertyChanged; 
     public void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 

     public UsersViewModel() 
     { 
      SelectedUsers = new ArrayList(); 
      Users = new ObservableCollection<UserModel>(); 
      UsersViewSource = new CollectionViewSource() { Source = Users }; 
      InitializeListCommand = new RelayCommand(p => Users.Count == 0, p => InitializeList()); 
      InitializeListCommand.Execute(null); 
      DeleteFirstUserCommand = new RelayCommand(p => Users.Count > 0, p => DeleteFirstUser()); 
      DeleteLastUserCommand = new RelayCommand(p => Users.Count > 0, p => DeleteLastUser()); 
     } 

     private void InitializeList() 
     { 
      Users.Add(new UserModel() { Nickname = "John", Age = 35 }); 
      Users.Add(new UserModel() { Nickname = "Jane", Age = 29 }); 
      Users.Add(new UserModel() { Nickname = "Mark", Age = 59 }); 
      Users.Add(new UserModel() { Nickname = "Susan", Age = 79 }); 
      Users.Add(new UserModel() { Nickname = "Joe", Age = 66 }); 
      Users.Add(new UserModel() { Nickname = "Nina", Age = 29 }); 
      Users.Add(new UserModel() { Nickname = "Selma", Age = 44 }); 
      Users.Add(new UserModel() { Nickname = "Katrin", Age = 24 }); 
      Users.Add(new UserModel() { Nickname = "Joel", Age = 32 }); 
     } 

     private void DeleteFirstUser() 
     { 
      ListCollectionView lcw = (ListCollectionView)UsersViewSource.View; 
      lcw.RemoveAt(0); 
     } 

     private void DeleteLastUser() 
     { 
      ListCollectionView lcw = (ListCollectionView)UsersViewSource.View; 
      lcw.RemoveAt(lcw.Count - 1); 
     } 
    } 

    public class UserModel 
    { 
     public string Nickname { get; set; } 
     public int Age { get; set; } 
    } 

    public class CustomDataGrid : DataGrid 
    { 
     public static readonly DependencyProperty SelectedItemsListProperty = DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(CustomDataGrid), new PropertyMetadata(null)); 
     public IList SelectedItemsList 
     { 
      get { return (IList)GetValue(SelectedItemsListProperty); } 
      set { SetValue(SelectedItemsListProperty, value); } 
     } 

     public CustomDataGrid() { SelectionChanged += CustomDataGrid_SelectionChanged; } 
     void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) { SelectedItemsList = SelectedItems; } 
    } 

    public class RelayCommand : ICommand 
    { 
     private Predicate<object> canExecute; 
     private Action<object> execute; 

     public RelayCommand(Predicate<object> canExecute, Action<object> execute) 
     { 
      this.canExecute = canExecute; 
      this.execute = execute; 
     } 

     public event EventHandler CanExecuteChanged 
     { 
      add { CommandManager.RequerySuggested += value; } 
      remove { CommandManager.RequerySuggested -= value; } 
     } 

     public bool CanExecute(object parameter) { return canExecute(parameter); } 
     public void Execute(object parameter) { execute(parameter); } 
    } 
} 
+0

有趣......如果您选择前3行,则在删除第1行后,前3行(现在2)将显示选中状态,但新行1不会。 – grek40

+0

有趣的事实:用Snoop检查它...... DataGridRow.IsSelected与第一行中的DataGridCell.IsSelected属性不同步。这就是为什么基于单元格的样式不再正确触发,但基于行的选择列表包含项目。然而,我不知道*为什么这个desync会发生在细节上。 – grek40

回答

1

我遇到执行SelectedItemsList类似的问题。主要问题在于,当选择被更新时,备份列表会尝试删除不在选择中的项目。但是,如果项目发生更改(或不再存在),则比较失败,并且项目保留在列表中,导致此行为。

您可以通过添加一个CollectionChanged事件处理保持同步列表。

public UsersViewModel() 
{ 
    ... 

    Users.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChanged); 
} 

private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
{ 
    if (e.Action == NotifyCollectionChangedAction.Remove) 
     foreach (UserModel item in e.OldItems) SelectedUsers.Remove(item); 
} 
+0

非常感谢您提供非常快速的解决方案和解释。 – Cobek

1

作为评论的,以某种方式,当第一行被删除,新的第一行IsSelected性变得与它的细胞同步财产。我不知道为什么会这样,但它表示替代方法如果你的主要兴趣在保持风格的工作:只需使用行属性的触发

<DataTrigger Binding="{Binding IsSelected,RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}" Value="True"> 
    <Setter Property="Background" Value="Red"/> 
    <Setter Property="FontWeight" Value="Bold"/> 
    <Setter Property="Foreground" Value="Black"/> 
</DataTrigger> 
+0

非常感谢您提供非常快速的帮助。很好的解决方法。希望你不介意我是否给Funk提供信用来解释真正的原因。 – Cobek