When I try to cast the DataGrid.ItemsSource to the DataView for the second time, I get an error ("Unable to cast object...")

Reza Jaferi 331 Reputation points
2022-07-13T11:42:27.727+00:00

I will give two credits to anyone who answers this question by submitting the output (I have two accounts).
If my country's banks were not sanctioned, I would give a gift to the individual who answered this question (I've been looking for an acceptable response for months).

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 property.
To convert DataGrid.ItemsSource to DataTable, I tried the following solutions in various situations, and each has advantages and disadvantages (I spent several months researching and testing):

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: The method DG.ScrollIntoView((DataRowView)DG.Items[i]) must be used when the EnableRowVirtualization attribute is set to True; otherwise, a null error will occur. For large numbers of rows, this approach is incredibly sluggish (e.g., if we have 20,000 rows, it could take an 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.
220318-after-running-the-app.png

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.
220396-error.gif

I use the following tools:
XAML:

<Window.Resources>  
    <local:DatabaseDataSet x:Key="Database_DataSet"/>  
    <CollectionViewSource x:Key="BookTableViewSource" Source="{Binding BookTable, Source={StaticResource Database_DataSet}}"/>  
    <CollectionViewSource x:Key="MemberTableViewSource" Source="{Binding MemberTable, Source={StaticResource Database_DataSet}}"/>  
</Window.Resources>  
<Grid DataContext="{StaticResource BookTableViewSource}" Width="486" Height="386">  
    <DataGrid x:Name="BookDataGrid" HeadersVisibility="Column" EnableRowVirtualization="True" VirtualizingPanel.ScrollUnit="Pixel" CanUserAddRows="False" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="486" Height="386" Margin="0">  
        <DataGrid.Columns>  
            <DataGridTextColumn x:Name="BookName" Binding="{Binding BookName}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Publisher" Binding="{Binding Publisher}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Category" Binding="{Binding Category}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="BookCode" Binding="{Binding BookCode}" IsReadOnly="True" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Inventory" Binding="{Binding Inventory}" IsReadOnly="True" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="ReleaseDate" Binding="{Binding ReleaseDate}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="DateTaken" Binding="{Binding DateTaken}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="ReturnDate" Binding="{Binding ReturnDate}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="RecipientName" Binding="{Binding RecipientName}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Language" Binding="{Binding BookLanguage}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Length" Binding="{Binding Length}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Form" Binding="{Binding Form}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Translator" Binding="{Binding Translator}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Narrator" Binding="{Binding Narrator}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="ISBN" Binding="{Binding ISBN}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Location" Binding="{Binding Location}" Width="SizeToHeader"/>  
            <DataGridTextColumn x:Name="Price" Binding="{Binding Price}" Width="SizeToHeader"/>  
            <DataGridTemplateColumn x:Name="BookImage" Width="SizeToHeader">  
                <DataGridTemplateColumn.CellTemplate>  
                    <DataTemplate>  
                        <Image x:Name="BookImg" Source="{Binding BookImage}"/>  
                    </DataTemplate>  
                </DataGridTemplateColumn.CellTemplate>  
            </DataGridTemplateColumn>  
        </DataGrid.Columns>  
    </DataGrid>  
</Grid>  

C#:

    public void BookDatagridRefresh()  
    {  
        DatabaseDataSet Database_DataSet = ((DatabaseDataSet)TryFindResource("Database_DataSet"));  
        DatabaseDataSetTableAdapters.BookTableTableAdapter BookTable_TableAdapter = new DatabaseDataSetTableAdapters.BookTableTableAdapter();  
        BookTable_TableAdapter.Fill(Database_DataSet.BookTable);  
        BookDataGrid.ItemsSource = Database_DataSet.Tables["BookTable"].DefaultView;  
    }  

Visual Studio 2017 .NET Framework 4.5.2 WPF
220421-binding.png

Thank you for your attention.

.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,374 questions
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,671 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,247 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
764 questions
{count} vote

Accepted answer
  1. رضا جافری 1,291 Reputation points
    2022-07-18T02:37:07.687+00:00

    Finally, I found a solution that does not have the above problems.
    I made a strategic mistake. When we connect DataGrid.ItemsSource to DataView or CollectionViewSource.Source for the first time, the values of DataView and CollectionViewSource.Source change automatically with each change (like insert, edit, and delete), so the code that connects DataGrid.ItemsSource to DataView or CollectionViewSource does not need to be rewritten.

    Look carefully at the image below to get a better understanding of how this feature works. When DataGrid.ItemsSource changes in the first window, DataGrid.ItemsSource in the second window also changes.
    222524-datagrid.gif

    I tested the following codes in different situations, using nearly the entire capacity of the MS Access database.

    The conditions are as follows:

    Number of records = 252,500

    Database size = 2,094,128 KB (database size without any rows = 604 KB)

    Size of each row = (Database size - database size without any rows) / Number of records => about 8.291184 KB (Of course, if I haven't made a mistake)

    Hardware and software used in the test = Acer Aspire 5750G Laptop (Core i5 2nd Gen/4 GB RAM/Win7-x64), Visual Studio 2017, .NET Framework 4.5.2, WPF

    XAML:

    <Window.Resources>  
        <local:DatabaseDataSet x:Key="Database_DataSet"/>  
        <CollectionViewSource x:Key="BookTableViewSource" Source="{Binding BookTable, Source={StaticResource Database_DataSet}}"/>  
        <CollectionViewSource x:Key="MemberTableViewSource" Source="{Binding MemberTable, Source={StaticResource Database_DataSet}}"/>  
    </Window.Resources>  
    <Grid DataContext="{StaticResource BookTableViewSource}" Width="486" Height="386">  
        <DataGrid x:Name="BookDataGrid" HeadersVisibility="Column" EnableRowVirtualization="True" VirtualizingPanel.ScrollUnit="Pixel" CanUserAddRows="False" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="486" Height="386" Margin="0">  
            <DataGrid.Columns>  
                <DataGridTextColumn x:Name="BookName" Binding="{Binding BookName}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Publisher" Binding="{Binding Publisher}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Category" Binding="{Binding Category}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="BookCode" Binding="{Binding BookCode}" IsReadOnly="True" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Inventory" Binding="{Binding Inventory}" IsReadOnly="True" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="ReleaseDate" Binding="{Binding ReleaseDate}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="DateTaken" Binding="{Binding DateTaken}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="ReturnDate" Binding="{Binding ReturnDate}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="RecipientName" Binding="{Binding RecipientName}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Language" Binding="{Binding BookLanguage}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Length" Binding="{Binding Length}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Form" Binding="{Binding Form}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Translator" Binding="{Binding Translator}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Narrator" Binding="{Binding Narrator}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="ISBN" Binding="{Binding ISBN}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Location" Binding="{Binding Location}" Width="SizeToHeader"/>  
                <DataGridTextColumn x:Name="Price" Binding="{Binding Price}" Width="SizeToHeader"/>  
                <DataGridTemplateColumn x:Name="BookImage" Width="SizeToHeader">  
                    <DataGridTemplateColumn.CellTemplate>  
                        <DataTemplate>  
                            <Image x:Name="BookImg" Source="{Binding BookImage}"/>  
                        </DataTemplate>  
                    </DataGridTemplateColumn.CellTemplate>  
                </DataGridTemplateColumn>  
            </DataGrid.Columns>  
        </DataGrid>  
    </Grid>  
    

    C#:

    public DataView BDV = new DataView();  
    object[] BookCodeSelectedItems = null;  
    object[] ISBN_Value = null;  
    public MainWindow()  
    {  
        InitializeComponent();  
        BDV = BookDataGrid.ItemsSource as DataView; //In "XAML" or "C#," binding is only necessary once  
    }  
    private void DataGridDeleteMenu_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)  
    {  
        switch (BookDataGrid.SelectedItems.Count > 0)  
        {  
            case false:  
                MessageWindow MW = new MessageWindow();  
                MW.YesButton.Visibility = Visibility.Hidden;  
                MW.NoButton.Visibility = Visibility.Hidden;  
                MW.MessageLabel.Margin = new Thickness(0, 110, 0, 0);  
                MW.MessageLabel.HorizontalAlignment = HorizontalAlignment.Center;  
                MW.Image.Source = GetImageFromBytes(System.IO.File.ReadAllBytes(System.Windows.Forms.Application.StartupPath + @"\Images\Warning.bin"));  
                MW.MessageTextBlock.Text = "No rows selected";  
                MW.OKButton.Content = "OK";  
                MW.ShowDialog();  
                break;  
            default:  
                var stopwatch = new System.Diagnostics.Stopwatch();  
                stopwatch.Start();  
                List<object> Row = new List<object>();  
                //Additionally, I tested the "AddRange" function of the "List<>" and it appeared to be one second slower. I mean "Row.AddRange(BookDataGrid.Items.Cast<object>().ToList());"  
                for (int i = 0; i < BookDataGrid.Items.Count; i++)  
                {  
                    Row.Add(BookDataGrid.Items[i]);  
                }  
                BookCodeSelectedItems = new object[BookDataGrid.SelectedItems.Count]; //I need this for further calculations  
                ISBN_Value = new string[BookDataGrid.SelectedItems.Count]; //I need this for further calculations  
                int j = 0;  
                foreach (DataRowView DRV in BookDataGrid.SelectedItems)  
                {  
                    BookCodeSelectedItems[j] = BDV.Table.Rows[BDV.Table.Rows.IndexOf(DRV.Row)][3];  
                    ISBN_Value[j] = BDV.Table.Rows[BDV.Table.Rows.IndexOf(DRV.Row)][14];  
                }  
                for (int i = 0; i < BookDataGrid.SelectedItems.Count; i++)  
                {  
                    Row.Remove(BookDataGrid.SelectedItems[i]);  
                }  
                BookDataGrid.ItemsSource = Row;  
                stopwatch.Stop();  
                MessageBox.Show("Total seconds: " + stopwatch.Elapsed.TotalSeconds.ToString());  
                break;  
        }  
    }  
    

    Output:
    222475-output.gif

    I also tried the following code, it was very slow. It took more than 10 minutes and I stopped the app. But List<> took about 16 seconds.

    var stopwatch = new System.Diagnostics.Stopwatch();  
    stopwatch.Start();  
    for (int i = BookDataGrid.SelectedItems.Count - 1; i >= 0; i--)  
    {  
        BDV.Table.Rows.Remove(((DataRowView)BookDataGrid.SelectedItems[i]).Row);  
    }  
    BookDataGrid.ItemsSource = BDV.Table.DefaultView;  
    stopwatch.Stop();  
    MessageBox.Show("Total seconds: " + stopwatch.Elapsed.TotalSeconds.ToString());  
    

    I hope this is the most comprehensive solution for converting DataGrid.ItemsSource to DataTable, DataView or another type of data source.
    Thank you for your attention.

    1 person found this answer helpful.
    0 comments No comments

0 additional answers

Sort by: Most helpful