XAML 当我第二次尝试将DataGrid.ItemsSource转换为DataView时,收到错误(“无法转换对象...”)

xam8gpfp  于 2022-12-07  发布在  其他
关注(0)|答案(1)|浏览(190)

我希望删除用户选择的行。
据我所知,DataGrid.ItemsSource不能直接使用DataGrid.Items.Remove()方法删除,所以我必须将DataGrid.ItemsSource转换为DataTable并使用DataGrid.ItemsSource = DataTable.DefaultView属性。为了将DataGrid.ItemsSource转换为DataTable,我在各种情况下尝试了以下解决方案,每种方案都有优缺点(我花了几个月的时间进行研究和测试):

第一个解决方案(我自己做的):

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

优点:即使其中一个数据行是Image类型,此方法也不会发生错误。
缺点:当EnableRowVirtualization属性设置为True时,必须使用方法DG.ScrollIntoView((DataRowView)DG.Items[i]);对于大量的行,这种方法非常慢(例如,如果我们有20,000行,可能需要一个小时或更长时间)。

第二个解决方案(我自己做的):

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>

x1米15英寸

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

优点:此解决方案将DataGrid.ItemsSource转换为DataTable的速度比第一个解决方案快,因为在此情况下EnableRowVirtualization等于False
缺点:此解决方案消耗大量内存;例如,如果我们有100,000行,数据库表的大小为2GB,它将占用2GB的RAM,并且可能发生RAM空间错误。x1c 0d1x

第三种解决方案:

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

优点:第一次触发DataGridDeleteMenu PreviewMouseLeftButtonDown事件时,DataGrid.ItemsSource会在此方法中快速变更为DataTable
缺点:但是,重新运行事件时,会发生System.InvalidCastException: 'Unable to cast object of type 'System.Collections.Generic.List 1[System.Object]' to type 'System.Data.DataView'.'错误。

我测试了以下代码(我放置了一个断点):

var dataType = BookDataGrid.ItemsSource.GetType().BaseType;

结果:
第一次
值={名称=“按值组件封送处理”全名=“系统.组件模型.按值组件封送处理”}
**类型=**系统.类型{系统.运行时类型}
第二次
值={名称=“对象”全名=“系统.对象”}
**类型=**系统.类型{系统.运行时类型}

似乎只有 * 值 * 发生了变化。
"但为什么"
"解决方法是什么"

我使用以下工具:
x1米28英寸1x

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

x一个月30个月1个月x一个月31个月1个月x一个月32个月1个月

谢谢大家的关注。

wydwbb8l

wydwbb8l1#

最后,我找到了一个不存在上述问题的解决方案。
我犯了一个策略性错误,当我们第一次将DataGrid.ItemsSource连接到DataViewCollectionViewSource.Source时,DataViewCollectionViewSource.Source的值会随着每次更改(如插入、编辑和删除)而自动更改,因此连接DataGrid.ItemsSourceDataViewCollectionViewSource.Source的代码不需要重写。
请仔细查看下图,以更好地了解此功能的工作原理。当第一个窗口中的DataGrid.ItemsSource发生变化时,第二个窗口中的DataGrid.ItemsSource也会发生变化。x1c 0d1x
我在不同的情况下测试了以下代码,几乎使用了MS Access数据库的全部容量。
条件如下:
记录数= 252,500
数据库大小= 2,094,128 KB(没有任何行的数据库大小= 604 KB)
每行的大小=(Database size -没有任何行的数据库大小)/记录数=〉大约8.291184 KB(当然,如果我没有弄错的话)
测试中使用的硬件和软件= Acer Aspire 5750G Laptop (Core i5 2nd Gen/4 GB RAM/Win7-x64)Visual Studio 2017.NET Framework 4.5.2WPF
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;
        }
    }

输出:

我也尝试了下面的代码,它非常慢。它花了超过10分钟,我停止了应用程序。但List<>花了大约16秒

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

我希望这是将DataGrid.ItemsSource转换为DataTableDataView或其他类型的数据源的最全面的解决方案。
谢谢大家的关注。

相关问题