-1

So I have a DataTable that I bind to the DataGrid in XAML. User is allowed to add, modify and remove rows for a table. I would like to mark rows with a specific colour, depending on the action that user makes. For instance, if user adds a row, that row will be marked as Green. If user modifies a row, that row will then be marked as orange. And if user removes the row, that row will be marked as red. The problem that I have is that the removed row is no longer visible once I call row.Delete(); from a view model.

Is there a way to keep a DataRow marked for removal shown in a DataGrid? I know how to achieve the row background effect, depending on user action. The only problem is I don't know how to keep the deleted row visible. The idea is that user can either revert changes or apply them and that's when the pending deletion row should be actually deleted.

EDIT (Added example on how I update row background colour):

<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>
vic
  • 9
  • 3

2 Answers2

0

I think, when user marks row for deleting - you should save it index somewhere (int[] array or List<int> for example), then call yourDataGridView.Rows.RemoveAt(index) for each element in that collection when user finished working with table.

Maybe something like:

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

Instead of index you may save whole DataGridRow and call yourDataGrid.Remove(wholeRow);. And for reverse deletion, you just unmark it by removing color and removing row index or whole row from a collection.

Auditive
  • 1,607
  • 1
  • 7
  • 13
  • Hm, but if my row background colour updates according to the Row.RowState property (see xaml in the question section), and row turns red only when RowState == Deleted, which is only possible if you instantly call Delete() on a row instance, how you delay deletion in this case? – vic May 31 '21 at 22:27
  • Why instantly call `Delete()`? The idea is similar to windows recycle. User press "Delete row" button (or smth) and it only marks as deletable, but not removed in any way. User able to restore is in some way by unmarking. in fact, that deletion is only visual trick for user. Then, user may call smth like "Permanently delete marked rows" and then you call `Delete()` / `Remove()` / `RemoveAt()` to delete it without restore opportunity. – Auditive Jun 01 '21 at 04:26
  • yeah that's the exact effect I want to achieve, problem is, as I said, my current approach of changing row colour is bound to the ``RowState`` property of the row, so a row will turn red only if ``RowState`` is changed to Deleted, which seems to be possible only by calling ``Delete()``. Do you think I'll have to subclass the ``DataRow`` and introduce my own enum for Row state and use that custom enum to bind to? – vic Jun 01 '21 at 07:53
  • So basically I derived from ``DataTable`` and ``DataRow`` and added a new property for my custom ``DataRow``, and bound XAML background markup to that new property which is changed whenever and however I like it. – vic Jun 07 '21 at 18:25
0

If I understood you correctly, you need to use the Delete key not to delete lines, but to put a marker on them. And in the DataGrid, you need to highlight color the rows marked with this marker. You have not shown your table, so I will demonstrate in my simple conciliation.

The example uses the BaseInpc and RelayCommand classes.

In addition to them, the command extension method is used:

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

}

ViewModel:

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>

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

If you are unable to use this example, then write the reason and add details to the explanation of your question.

The answer is supplemented by clarifications for the added details:

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.

To control the visibility of rows, you need to change the value of the DataTable.DefaultView.RowStateFilter property. This is not hard to do in the ViewModel.

But an additional problem is that the RowState property does not notify about its change. So the trigger binding won't work just like that. In my example, I solved this by calling Items.Refresh (). Perhaps you are using a different solution since you have not written about any problems associated with this.

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();
        }
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • Hey, thanks for the reply. 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. As you can see from the xaml code snippet, I use RowState property to see if row is pending deletion. And RowState is assigned Deleted state only when you call Delete() on a row isntance – vic May 31 '21 at 22:22
  • Read the supplement to my answer. – EldHasp Jun 01 '21 at 07:23
  • Looks like I did something wrong. I edited my answer - added some text to it. But for some reason, after the editing, the beginning of the answer changed ... But you don’t need a beginning — so it’s not essential. – EldHasp Jun 01 '21 at 07:29
  • I restored the beginning of the answer, even though it doesn't matter now. – EldHasp Jun 01 '21 at 07:42