c# – When I try to cast the DataGrid.ItemsSource to the DataView for the second time, I get an error (“Unable to cast object …”)

I will give a rating to everyone who knows the proper solution.

I want the rows that the user selects to be deleted.

As far as I know, the DataGrid.ItemsSource cannot be removed directly using the DataGrid.Items.Remove() method, so I must convert the DataGrid.ItemsSource to a DataTable and use the DataGrid.ItemsSource = DataTable.DefaultView method. To convert DataGrid.ItemsSource to DataTableI tried the following solutions in various situations, and each has advantages and disadvantages (I spent several months):

First solution (I made this by myself):
XAML:

    <DataGrid x:Name="BookDataGrid" EnableRowVirtualization="True" VirtualizingPanel.ScrollUnit="Pixel" CanUserAddRows="False" BeginningEdit="BookDataGrid_BeginningEdit" RowEditEnding="BookDataGrid_RowEditEnding" HeadersVisibility="Column" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="386" Width="486" Margin="0">
        <DataGrid.Columns>
            <DataGridTextColumn x:Name="BookName" Binding="{Binding BookName}" Width="SizeToHeader">
                <DataGridTextColumn.EditingElementStyle>
                    <Style TargetType="TextBox">
                        <Setter Property="AcceptsReturn" Value="True"/>
                        <Setter Property="ContextMenu" Value="{StaticResource CustomContextMenu}"/>
                        <Setter Property="TextWrapping" Value="WrapWithOverflow"/>
                    </Style>
                </DataGridTextColumn.EditingElementStyle>
            </DataGridTextColumn>
            <DataGridTemplateColumn x:Name="BookImage" Width="SizeToHeader">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Image x:Name="BookImg" Source="{Binding BookImage}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

C#:

    public byte[] ImageSourceToBytes(BitmapEncoder BitEncoder, ImageSource ImgSource)
    {
        byte[] Bytes = null;
        switch ((ImgSource as BitmapSource) != null)
        {
            case true:
                BitEncoder.Frames.Add(BitmapFrame.Create((ImgSource as BitmapSource)));
                using (var Stream = new System.IO.MemoryStream())
                {
                    BitEncoder.Save(Stream);
                    Bytes = Stream.ToArray();
                }
                break;
        }
        return Bytes;
    }
    public DataTable DataGridToDataTable(DataGrid DG, DataTable DT, byte NumberOfColumns, byte VisualColumnIndex, string ControlName)
    {
        for (int i = 0; i < DG.Items.Count; i++)
        {
            DT.Rows.Add(DG.Items[i]);
        }
        for (int i = 0; i < DG.Items.Count; i++)
        {
            for (byte j = 0; j < NumberOfColumns; j++)
            {
                switch (j == VisualColumnIndex)
                {
                    case true:
                        FrameworkElement FE = DG.Columns[j].GetCellContent((DataGridRow)DG.ItemContainerGenerator.ContainerFromIndex(i));
                        Image Img = new Image() { Source = ((((DataGridTemplateColumn)DG.Columns[j]).CellTemplate.FindName(ControlName, FE) as Image).Source) };
                        DT.Rows[i][j] = ImageSourceToBytes(new PngBitmapEncoder(), Img.Source);
                        break;
                    default:
                        DG.ScrollIntoView((DataRowView)DG.Items[i]);
                        DT.Rows[i][j] = ((DG.Columns[j].GetCellContent(((DataGridRow)DG.ItemContainerGenerator.ContainerFromIndex(i)))) as TextBlock).Text;
                        break;
                }
            }
        }
        return DT;
    }

Advantage: even if one of the columns is of the Image type, this approach operates without an error.

Disadvantage: When the EnableRowVirtualization attribute is set to Truethe method DG.ScrollIntoView((DataRowView)DG.Items[i]) must be used; otherwise, a null error will result. For large numbers of rows, this approach is incredibly sluggish (eg if we have 20,000 rows it could take 1 hour or more).

Second solution (I made this by myself):

XAML:

    <DataGrid x:Name="BookDataGrid" EnableRowVirtualization="False" CanUserAddRows="False" BeginningEdit="BookDataGrid_BeginningEdit" RowEditEnding="BookDataGrid_RowEditEnding" HeadersVisibility="Column" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="386" Width="486" Margin="0">
        <DataGrid.Columns>
            <DataGridTextColumn x:Name="BookName" Binding="{Binding BookName}" Width="SizeToHeader">
                <DataGridTextColumn.EditingElementStyle>
                    <Style TargetType="TextBox">
                        <Setter Property="AcceptsReturn" Value="True"/>
                        <Setter Property="ContextMenu" Value="{StaticResource CustomContextMenu}"/>
                        <Setter Property="TextWrapping" Value="WrapWithOverflow"/>
                    </Style>
                </DataGridTextColumn.EditingElementStyle>
            </DataGridTextColumn>
            <DataGridTemplateColumn x:Name="BookImage" Width="SizeToHeader">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Image x:Name="BookImg" Source="{Binding BookImage}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

C#:

    public DataTable DataGridToDataTable(DataGrid DG, DataTable DT, byte NumberOfColumns, byte VisualColumnIndex, string ControlName)
    {
        for (int i = 0; i < DG.Items.Count; i++)
        {
            DT.Rows.Add(DG.Items[i]);
        }
        for (int i = 0; i < DG.Items.Count; i++)
        {
            for (byte j = 0; j < NumberOfColumns; j++)
            {
                switch (j == VisualColumnIndex)
                {
                    case true:
                        FrameworkElement FE = DG.Columns[j].GetCellContent((DataGridRow)DG.ItemContainerGenerator.ContainerFromIndex(i));
                        Image Img = new Image() { Source = ((((DataGridTemplateColumn)DG.Columns[j]).CellTemplate.FindName(ControlName, FE) as Image).Source) };
                        DT.Rows[i][j] = ImageSourceToBytes(new PngBitmapEncoder(), Img.Source);
                        break;
                    default:
                        DT.Rows[i][j] = ((DG.Columns[j].GetCellContent(((DataGridRow)DG.ItemContainerGenerator.ContainerFromIndex(i)))) as TextBlock).Text;
                        break;
                }
            }
        }
        return DT;
    }

Advantage: this solution converts DataGrid.ItemsSource to DataTable faster than the first because EnableRowVirtualization is equal to False in this case.

Disadvantage: this solution consumes a lot of memory; for example, if we have 100,000 rows and the database table is 2GB in size, it will consume 2GB of RAM and a RAM space error may occur:

Third solution:

XAML:

    <DataGrid x:Name="BookDataGrid" EnableRowVirtualization="True" VirtualizingPanel.ScrollUnit="Pixel" CanUserAddRows="False" BeginningEdit="BookDataGrid_BeginningEdit" RowEditEnding="BookDataGrid_RowEditEnding" HeadersVisibility="Column" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="386" Width="486" Margin="0">
        <DataGrid.Columns>
            <DataGridTextColumn x:Name="BookName" Binding="{Binding BookName}" Width="SizeToHeader">
                <DataGridTextColumn.EditingElementStyle>
                    <Style TargetType="TextBox">
                        <Setter Property="AcceptsReturn" Value="True"/>
                        <Setter Property="ContextMenu" Value="{StaticResource CustomContextMenu}"/>
                        <Setter Property="TextWrapping" Value="WrapWithOverflow"/>
                    </Style>
                </DataGridTextColumn.EditingElementStyle>
            </DataGridTextColumn>
            <DataGridTemplateColumn x:Name="BookImage" Width="SizeToHeader">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Image x:Name="BookImg" Source="{Binding BookImage}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

C#:

    uint[] BookCodeSelectedItems = null; //I need this for further calculations
    private void DataGridDeleteMenu_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        switch (BookDataGrid.SelectedItems.Count > 0)
        {
            case true:
                List<object> DefaultRow = new List<object>();
                DataTable BDT = ((DataView)BookDataGrid.ItemsSource).ToTable(); //The first time the event is executed, no error occurs, but the second time the error occurs on this line
                for (int i = 0; i < BookDataGrid.Items.Count; i++)
                {
                    DefaultRow.Add(BookDataGrid.Items[i]);
                }
                BookCodeSelectedItems = new uint[BookDataGrid.SelectedItems.Count];
                for (int i = 0; i < BookDataGrid.SelectedItems.Count; i++)
                {
                    BookCodeSelectedItems[i] = uint.Parse(BDT.Rows[i][3].ToString());
                    DefaultRow.Remove(BookDataGrid.SelectedItems[i]);
                }
                BookDataGrid.ItemsSource = DefaultRow;
                break;
        }
    }

Advantage: the DataGrid.ItemsSource is rapidly changed to DataTable in this approach when the DataGridDeleteMenu PreviewMouseLeftButtonDown event is fired for the first time.

Disadvantage: However, when the event is re-run, a System.InvalidCastException: 'Unable to cast object of type 'System.Collections.Generic.List 1[System.Object]' to type 'System.Data.DataView'.' error occurs:
Error

I use the following tools:

Visual Studio 2017 .NET Framework 4.5.2 WPF

Thank you for your attention

Leave a Comment