【问题标题】:Display deleted rows in a DataTable在 DataTable 中显示已删除的行
【发布时间】:2021-08-18 03:03:50
【问题描述】:

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

有没有办法让DataRowDataGrid 中显示为要删除?我知道如何实现行背景效果,具体取决于用户操作。唯一的问题是我不知道如何保持已删除的行可见。这个想法是用户可以恢复更改或应用它们,这就是应该实际删除待删除行的时候。

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

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

【问题讨论】:

    标签: c# wpf datatable datagrid datarow


    【解决方案1】:

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

    可能是这样的:

    //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);,而不是索引。 而对于反向删除,您只需通过删除颜色并从集合中删除行索引或整行来取消标记。

    【讨论】:

    • 嗯,但是如果我的行背景颜色根据 Row.RowState 属性更新(请参阅问题部分中的 xaml),并且行仅在 RowState == Deleted 时变为红色,这只有在您立即在行实例上调用 Delete(),在这种情况下如何延迟删除?
    • 为什么要立即致电Delete()?这个想法类似于windows回收。用户按下“删除行”按钮(或 smth),它只标记为可删除,但不会以任何方式删除。用户能够通过取消标记以某种方式恢复。事实上,删除只是用户的视觉技巧。然后,用户可以调用诸如“永久删除标记的行”之类的方法,然后您调用 Delete() / Remove() / RemoveAt() 将其删除而没有恢复机会。
    • 是的,这正是我想要达到的效果,问题是,正如我所说,我当前更改行颜色的方法绑定到行的 RowState 属性,所以一行会变成红色仅当RowState 更改为已删除,这似乎只能通过调用Delete() 才能实现。您是否认为我必须继承 DataRow 并为 Row 状态引入我自己的枚举并使用该自定义枚举绑定到?
    • 所以基本上我从 DataTableDataRow 派生并为我的自定义 DataRow 添加了一个新属性,并将 XAML 背景标记绑定到该新属性,无论何时何地我喜欢它都会更改.
    【解决方案2】:

    如果我理解正确,您需要使用 Delete 键,而不是删除行,而是在它们上放置一个标记。 在 DataGrid 中,您需要突出显示用此标记标记的行的颜色。 你没有展示你的桌子,所以我将在我的简单调解中展示。

    示例使用BaseInpc and RelayCommand classes

    除此之外,使用命令扩展方式:

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

    窗口 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>
    

    窗口后面的代码:

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

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

    答案由补充细节的说明补充:

    我想我应该提到我使用绑定到 DataTrigger 的 DataRow 的 RowState 属性来更新行背景颜色。为问题添加了详细信息。

    要控制行的可见性,您需要更改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();
            }
    

    【讨论】:

    • 您好,谢谢您的回复。我想我应该提到我使用绑定到 DataTrigger 的DataRow 的 RowState 属性来更新行背景颜色。为问题添加了详细信息。从 xaml 代码 sn-p 可以看出,我使用 RowState 属性来查看行是否正在等待删除。并且 RowState 仅当您在行上调用 Delete() 时才被分配为 Deleted 状态
    • 阅读我的答案的补充。
    • 看来我做错了什么。我编辑了我的答案 - 添加了一些文字。但是由于某种原因,在编辑之后,答案的开头发生了变化……但是您不需要开头 - 所以它不是必需的。
    • 我恢复了答案的开头,即使现在已经不重要了。
    猜你喜欢
    • 1970-01-01
    • 2016-09-10
    • 1970-01-01
    • 2011-02-12
    • 1970-01-01
    • 2017-02-16
    • 2015-06-13
    • 1970-01-01
    • 2016-03-18
    相关资源
    最近更新 更多