在 DataTable 中显示已删除的行

Display deleted rows in a DataTable

所以我有一个 DataTable 绑定到 XAML 中的 DataGrid。 允许用户添加、修改和删除 table 的行。我想根据用户所做的操作用特定颜色标记行。例如,如果用户添加一行,该行将被标记为绿色。如果用户修改了一行,该行将被标记为橙色。如果用户删除该行,该行将被标记为红色。 我遇到的问题是,一旦我从视图模型调用 row.Delete();,删除的行就不再可见。

有没有办法在 DataGrid 中保留标记为删除的 DataRow?我知道如何根据用户操作实现行背景效果。唯一的问题是我不知道如何让删除的行可见。这个想法是用户可以还原更改或应用它们,这就是应该实际删除待删除行的时间。

编辑(添加了关于如何更新行背景颜色的示例):

<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding Path=Row.RowState}" Value="{x:Static data:DataRowState.Deleted}" />
        <Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsSelected}" Value="False" />
    </MultiDataTrigger.Conditions>
    <MultiDataTrigger.Setters>
        <Setter Property="Background" Value="IndianRed" TargetName="DGR_Border"/>
        <Setter Property="Foreground" Value="Black"/>
        <Setter Property="FontWeight" Value="Bold"/>
    </MultiDataTrigger.Setters>
</MultiDataTrigger>

我认为,当用户标记要删除的行时 - 您应该将其保存在某个地方的索引(例如 int[] 数组或 List<int>),然后为其中的每个元素调用 yourDataGridView.Rows.RemoveAt(index)用户完成使用 table.

时的集合

可能是这样的:

//Collection for rows indexes which will be deleted later
List<int> rowsToDelete = new List<int>();

//Call this when user want to delete element
private void MarkElementsToRemove()
{
   if (yourDataGrid.SelectedItems.Count > 0)
   {
       //Get selected by user rows
       for (int i = 0; i < yourDataGrid.SelectedItems.Count; ++i)
       {
           DataGridRow row = (DataGridRow)yourDataGrid.SelectedItems[i];

           //Fill rows background with some color to mark them visually as "deleted"
           row.Background = new SolidColorBrush(Color.FromRgb(255, 0, 0));

           //Get row index to remove it later and add it to collection
           rowsToDelete.Add(row.GetIndex());                        
        }
    }
}

// Call this when user finished work with DataGrid and items may be removed
private void RemoveMarkedElements()
{
   foreach (int index in rowsToDelete)
   {
      yourDataGrid.Items.RemoveAt(index);
   }
   
   rowsToDelete.Clear();
}

您可以保存整个 DataGridRow 并调用 yourDataGrid.Remove(wholeRow); 而不是索引。 对于反向删除,您只需通过删除颜色并从集合中删除行索引或整行来取消标记。

如果我没理解错的话,您需要使用 Delete 键不是删除行,而是在行上添加标记。 在 DataGrid 中,您需要用颜色突出显示标有此标记的行。 你还没有展示你的table,所以我将在我的简单调解中展示。

示例使用 .

除了他们,还用到了命令扩展方法:

using System.Windows.Input;

namespace Simplified
{
    public static class CommandExtensionMethods
    {
        public static bool TryExecute(this ICommand command, object parameter)
        {
            bool can = command.CanExecute(parameter);
            if (can)
                command.Execute(parameter);
            return can;
        }
        public static bool TryExecute(this ICommand command)
          => TryExecute(command, null);
  }

}

视图模型:

using Simplified;
using System.Data;
using System.Windows.Input;

namespace DeferredRowDeletion
{
    public class DrdViewModel : BaseInpc
    {
        public DataTable Table { get; } = new DataTable();
        public DrdViewModel()
        {
            Table.Columns.Add("Name", typeof(string));
            Table.Columns.Add("Value", typeof(int));
            Table.Columns.Add("Marked for deletion", typeof(bool));
            foreach (var name in new string[] { "First", "Second", "Third", "Fourth", "Fifth" })
            {
                var row = Table.NewRow();
                row[0] = name;
                row[1] = Table.Rows.Count;
                row[2] = Table.Rows.Count % 2 == 1;
                Table.Rows.Add(row);
            }
        }

        private ICommand _markRemoveChangeCommand;
        private bool _isRemoveRowsImmediately;

        public ICommand MarkRemoveChangeCommand => _markRemoveChangeCommand
            ?? (_markRemoveChangeCommand = new RelayCommand<DataRow>(
                row => row[2] = !(bool)(row[2] ?? false),
                row => !IsRemoveRowsImmediately
                ));

        public bool IsRemoveRowsImmediately
        {
            get => _isRemoveRowsImmediately;
            set => Set(ref _isRemoveRowsImmediately, value);
        }
    }
}

Window XAML:

<Window x:Class="DeferredRowDeletion.DrdWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DeferredRowDeletion"
        mc:Ignorable="d"
        Title="DrdWindow" Height="450" Width="800">
    <FrameworkElement.DataContext>
        <local:DrdViewModel/>
    </FrameworkElement.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <CheckBox Content="Removw Rows Immediately"
                  IsChecked="{Binding IsRemoveRowsImmediately}"
                  Margin="5"/>
        <DataGrid x:Name="dataGrid" Grid.Row="1"
                  ItemsSource="{Binding Table, Mode=OneWay}"
                  AutoGeneratingColumn="OnAutoGeneratingColumn"
                  CanUserDeleteRows="{Binding IsRemoveRowsImmediately}"
                  PreviewKeyDown="OnPreviewKeyDown">
            <DataGrid.RowStyle>
                <Style TargetType="DataGridRow">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding [Marked for deletion]}" Value="true">
                            <Setter Property="Background" Value="HotPink"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.RowStyle>
        </DataGrid>
    </Grid>
</Window>

代码隐藏 Window:

using Simplified;
using System.Data;
using System.Windows;
using System.Windows.Input;

namespace DeferredRowDeletion
{
    public partial class DrdWindow : Window
    {
        public DrdWindow()
        {
            InitializeComponent();
        }
        private void OnAutoGeneratingColumn(object sender, System.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
        {
            if (e.PropertyName == "Marked for deletion")
                e.Cancel = true;
        }

        private void OnPreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == Key.Delete)
            {
                DrdViewModel viewModel = (DrdViewModel)DataContext;
                var rowView = dataGrid.CurrentItem as DataRowView;
                if (rowView != null && !rowView.IsEdit)
                    viewModel.MarkRemoveChangeCommand.TryExecute(rowView.Row);
            }
        }
    }
}

如果您无法使用此示例,请写下原因并添加详细信息以解释您的问题。

答案补充说明补充细节:

I think I should've mentioned that I use DataRow's RowState property bound to the DataTrigger to update row background colour. Added details to the question.

要控制行的可见性,您需要更改 DataTable.DefaultView.RowStateFilter 属性 的值。 这在 ViewModel 中并不难做到。

但另一个问题是 RowState 属性 不会通知其更改。 所以触发器绑定不会像那样工作。 在我的示例中,我通过调用 Items.Refresh () 解决了这个问题。 也许您正在使用不同的解决方案,因为您没有写过与此相关的任何问题。

using Simplified;
using System.Data;
using System.Windows.Input;

namespace DeferredRowDeletion
{
    public class ShowDeletedRowsViewModel : BaseInpc
    {
        public DataTable Table { get; } = new DataTable();
        public ShowDeletedRowsViewModel()
        {
            Table.Columns.Add("Name", typeof(string));
            Table.Columns.Add("Value", typeof(int));
            foreach (var name in new string[] { "First", "Second", "Third", "Fourth", "Fifth" })
            {
                var row = Table.NewRow();
                row[0] = name;
                row[1] = Table.Rows.Count;
                Table.Rows.Add(row);
            }
            // Commits all the changes 
            Table.AcceptChanges();

            Table.Rows[1].Delete();
            Table.Rows[3].Delete();

            // Show Deleded Rows
            IsVisibilityDelededRows = true;
        }

        private ICommand _markRemoveChangeCommand;
        private bool _isVisibilityDelededRows;

        public ICommand MarkRemoveChangeCommand => _markRemoveChangeCommand
            ?? (_markRemoveChangeCommand = new RelayCommand<DataRow>(
                row => IsVisibilityDelededRows ^= true,
                row => !IsVisibilityDelededRows
                ));

        public bool IsVisibilityDelededRows
        {
            get => _isVisibilityDelededRows;
            set => Set(ref _isVisibilityDelededRows, value);
        }

        protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue)
        {
            base.OnPropertyChanged(propertyName, oldValue, newValue);

            if (propertyName == nameof(IsVisibilityDelededRows))
            {
                // Change the row filter if the associated property has changed
                if (IsVisibilityDelededRows)
                {
                    Table.DefaultView.RowStateFilter |= DataViewRowState.Deleted;
                }
                else
                {
                    Table.DefaultView.RowStateFilter &= ~DataViewRowState.Deleted;
                }
            }
        }
    }
}
<Window x:Class="DeferredRowDeletion.SdrWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DeferredRowDeletion" xmlns:data="clr-namespace:System.Data;assembly=System.Data"
        mc:Ignorable="d"
        Title="SdrWindow" Height="450" Width="800">
    <FrameworkElement.DataContext>
        <local:ShowDeletedRowsViewModel/>
    </FrameworkElement.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel>
            <CheckBox x:Name="cbAutoRefresh" Content="Auto Items.Refresh()" IsChecked="True" Margin="5"/>
            <CheckBox Content="Visibility Deleded Rows"
                  IsChecked="{Binding IsVisibilityDelededRows}"
                  Margin="5"/>
        </StackPanel>
        <DataGrid x:Name="dataGrid" Grid.Row="1"
                  ItemsSource="{Binding Table, Mode=OneWay}"
                  PreviewKeyUp="OnPreviewKeyUp">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Path=Row.RowState, Mode=OneWay}"
                                    Header="RowState"/>
            </DataGrid.Columns>
            <DataGrid.RowStyle>
                <Style TargetType="DataGridRow">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Path=Row.RowState}" Value="{x:Static data:DataRowState.Deleted}">
                            <Setter Property="Background" Value="HotPink"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.RowStyle>
        </DataGrid>
    </Grid>
</Window>
        private void OnPreviewKeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Delete && cbAutoRefresh.IsChecked == true)
                dataGrid.Items.Refresh();
        }