WPF DataGrid 添加类变量名称的列,无法获取单元格内容

问题描述 投票:0回答:1
我正在编写一个程序,需要以类似网格的格式提供信息,用户指定列数(为此目的,我称之为轨道)和行数(我称之为段)。

为此,我使用了一个 DataGrid,它将具有列数(轨道)和 1 行,并且每个单元格都应保存每个轨道的段数。提供此信息后,它将使用为每个数据定义的列数和行数填充第二个 DataGrid。这意味着列可能有不同的行数。

请参阅 Excel 中的这两张图片来说明我所追求的内容,我首先定义列数并添加 1 行,以便可以指定第二个网格的行数:

然后我期望第二个网格包含添加的行,准备好进一步的用户输入,这就是我将用于进一步处理的内容。我添加了边框,以便您看到这些是每个轨道的可用单元格(XAML 中的 CanUserAddRows 为 False,因此用户需要在第一个数据网格中指定确切的数字,因为它们将指导我的循环,并且我不能有空细胞)。

请检查我的最低工作示例,其中包含下面的 XAML 和 C# 代码:

<Window x:Class="WpfApp1.MainWindow" 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:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="350" Width="500"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="204*"/> <ColumnDefinition Width="69*"/> <ColumnDefinition Width="227*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="40"/> <RowDefinition Height="60"/> <RowDefinition Height="40*"/> <RowDefinition Height="160*"/> </Grid.RowDefinitions> <Label Content="Number of tracks" Grid.Row="0" Grid.Column="0"></Label> <TextBox x:Name="txt_tracks" Grid.Row="0" Grid.Column="1" Width="50" Height="20"/> <Button x:Name="bt_settracks" Grid.Row="0" Grid.Column="2" Content="Set tracks" Width="100" Height="20" Click="bt_settracks_Click"/> <ScrollViewer Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <DataGrid x:Name="dgrid_tracks" CanUserAddRows="False"></DataGrid> </ScrollViewer> <Button x:Name="bt_setsegments" Content="Set segments" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Width="100" Height="20" Click="bt_setsegments_Click"/> <ScrollViewer Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <DataGrid x:Name="dgrid_segments" CanUserAddRows="False"></DataGrid> </ScrollViewer> </Grid> </Window>


using System; using System.Collections.ObjectModel; using System.Data; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace WpfApp1 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } public class dTracks { public ushort rowTracks { get; set; } } private void Create_Headers(DataGrid dgrid, ushort NUM_TRACKS) { dgrid.ItemsSource = null; dgrid.Columns.Clear(); for (ushort i = 0; i < NUM_TRACKS; i++) { DataGridTextColumn newtrack = new DataGridTextColumn(); newtrack.Header = "Track " + i.ToString(); newtrack.Binding = new Binding("rowTracks"); newtrack.IsReadOnly = false; newtrack.Width = 100; dgrid.Columns.Add(newtrack); } } private void Set_Tracks(DataGrid dgrid, ushort NUM_TRACKS) { Create_Headers(dgrid, NUM_TRACKS); ObservableCollection<dTracks> tracks = new ObservableCollection<dTracks>() { new dTracks() { rowTracks = 0 } }; dgrid.ItemsSource = tracks; } private void Set_Segments(DataGrid dg_tracks, DataGrid dg_segments, ushort NUM_TRACKS) { Create_Headers(dg_segments, NUM_TRACKS); MessageBox.Show(dg_tracks.Columns.Count.ToString()); MessageBox.Show(dg_tracks.Items.Count.ToString()); for (int j = 0; j < dg_tracks.Columns.Count; j++) { for (int i = 0; i < dg_tracks.Items.Count - 1; i++) { String s = (dg_tracks.Items[i] as DataRowView).Row.ItemArray[j].ToString(); MessageBox.Show(s); } } } private void bt_settracks_Click(object sender, RoutedEventArgs e) => Set_Tracks(dgrid_tracks, Convert.ToUInt16(txt_tracks.Text)); private void bt_setsegments_Click(object sender, RoutedEventArgs e) => Set_Segments(dgrid_tracks, dgrid_segments, Convert.ToUInt16(txt_tracks.Text)); } }
当你运行它时,你已经会看到一些问题:

    在第一个 DataGrid 中,它创建一个附加列,其中包含与 ObservableCollection 关联的类中的变量名称;
  • 当您键入每个轨道的分段数时,它适用于所有单元格,而不仅仅是当前单元格;
  • 当您设置分段时,第二个 DataGrid 会正确显示您指定的轨道数。
沿着函数 Set_Segments() 我尝试打印一些值只是为了调试。第一个 DataGrid 的列数比应有的多显示 1 列,然后我尝试循环访问单元格以获取它们的值并将它们添加到第二个 DataGrid (以复制第二张图片),但这些值不会出现并且循环中的 MessageBox 甚至没有产生任何内容,甚至没有错误。

显然 C# 部分缺少/错误,但到目前为止,我找不到任何考虑每列不同行数的示例,因为大多数演示都使用内存中已有的对象填充数据网格。

也许你们可以演示一下如何解决上述问题。

c# wpf datagrid
1个回答
0
投票
主要思想是使用水平方向的

ListBox

作为配置表(代替单行
DataGrid
),使用
DataGrid
作为实际的列视图。对于此视图,我们必须取消单元格及其边框,使其看起来像基于列的
DataGrid
我们基本上用
DataTable
 填充 
NULL
 来标记非数据单元格:

曲目 0轨道1空空空空空空
所有包含

null

 值的单元格都不会呈现。与每列使用简单的 
ListBox
 相比,优点是您可以获得 
DataGrid
 控件的排序和重新排序功能。

以下示例展示了如何通过使用值转换器并定义

DataGrid.CellStyle

 来实现所需的视觉呈现。

Microsoft learn:数据绑定概述 (WPF .NET)

Microsoft learn:数据模板概述

MainWindow.xaml

<Window x:Name="Root"> <StackPanel x:Name="RootPanel"> <!-- Click to add a new column definition --> <Button Content="Add column" Click="OnCreateNewColumnDefinitionButtonClicked" /> <ListBox ItemsSource="{Binding ElementName=Root, Path=TableColumnInfos, Mode=OneTime}"> <!-- Change list box orientation to horizontal --> <ListBox.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate DataType="{x:Type local:TableColumnInfo}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="Column name:" /> <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ColumnName}" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="Row count:" /> <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding RowCount}" /> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <!-- Generate the table from the collected data --> <Button Content="Generate table" Click="Button_Click" /> <DataGrid ItemsSource="{Binding ElementName=Root, Path=ColumnTable}" GridLinesVisibility="None" ColumnReordered="OnDataGridColumnReordered" CanUserAddRows="False"> <DataGrid.CellStyle> <Style TargetType="DataGridCell"> <Style.Resources> <local:CellBorderThicknessConverter x:Key="CellBorderThicknessConverter" /> </Style.Resources> <!-- Remove the cell border and disable the cell if it is a non-data cell --> <Setter Property="BorderThickness"> <Setter.Value> <MultiBinding Converter="{StaticResource CellBorderThicknessConverter}" ConverterParameter="1"> <Binding RelativeSource="{RelativeSource AncestorType=DataGrid}" Path="Columns" /> <Binding RelativeSource="{RelativeSource Self}" /> </MultiBinding> </Setter.Value> </Setter> <Setter Property="BorderBrush" Value="Black" /> </Style> </DataGrid.CellStyle> </DataGrid> </StackPanel> </Window>

MainWindow.xaml.cs

partial class MainWindow : Window { public ObservableCollection<TableColumnInfo> TableColumnInfos { get; } public DataTable ColumnTable { get => (DataTable)GetValue(ColumnTableProperty); set => SetValue(ColumnTableProperty, value); } public static readonly DependencyProperty ColumnTableProperty = DependencyProperty.Register( "ColumnTable", typeof(DataTable), typeof(MainWindow), new PropertyMetadata(default)); public MainWindow() { InitializeComponent(); this.TableColumnInfos = new ObservableCollection<TableColumnInfo>(); } private void Button_Click(object sender, RoutedEventArgs e) { var dataTable = new DataTable(); int maxRowCount = 0; foreach (TableColumnInfo columnInfo in this.TableColumnInfos) { var dataColumn = new DataColumn(columnInfo.ColumnName, typeof(string)); dataTable.Columns.Add(dataColumn); if (columnInfo.RowCount > maxRowCount) { maxRowCount = columnInfo.RowCount; } } for (int rowIndex = 0; rowIndex < maxRowCount; rowIndex++) { DataRow row = dataTable.NewRow(); for (int columnIndex = 0; columnIndex < dataTable.Columns.Count; columnIndex++) { // Set cells that are non-data cells to NULL // so that they are later disabled (invisible and disabled for user input) TableColumnInfo columnInfo = this.TableColumnInfos[columnIndex]; string? cellValue = rowIndex >= columnInfo.RowCount ? default : string.Empty; row.SetField(columnIndex, cellValue); } dataTable.Rows.Add(row); } this.ColumnTable = dataTable; } }

CellBorderThicknessConverter.cs

public class CellBorderThicknessConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { DataGridCell dataGridCell = values .OfType<DataGridCell>() .First(); IList<DataGridColumn> dataGridColumns = values .OfType<IList<DataGridColumn>>() .First(); // Data source is not a DataTable if (dataGridCell.DataContext is not DataRowView dataRowView) { if (dataGridCell.DataContext.ToString().Contains("NewItemPlaceholder")) { return Binding.DoNothing; } throw new NotSupportedException("Only DataTable supported"); } double borderThickness = double.TryParse(parameter as string, out double thickness) ? thickness : 1; int columnDataIndex = dataGridColumns.IndexOf(dataGridCell.Column); int columnDisplayIndex = dataGridCell.Column.DisplayIndex; DataRow currentRow = dataRowView.Row; bool isCurrentCellVisible = IsCellVisible(columnDataIndex, currentRow); if (!isCurrentCellVisible) { // Collapse cell so that it is no longer editable dataGridCell.Visibility = Visibility.Collapsed; // Remove the grid lines around this cell return new Thickness(0); } // The current cell is visible. // Next is to determine if we have to draw the left border and top border. // Left/top borders are only drawn if the previous cell/row is hidden // to prevent doubled lines. To know the state of the previous cell/row // we have to inspect their values. // The DataGrid differentiates between column dispaly index (user can rearrange columns) // and the real underlying data column index. bool isCurrentCellFirstColumn = columnDisplayIndex == 0; DataTable sourceTable = dataRowView.DataView.Table; int rowIndex = sourceTable.Rows.IndexOf(currentRow); bool isCurrentCellFirstRow = rowIndex == 0; double leftBorderThickness = borderThickness; double topBorderThickness = borderThickness; if (!isCurrentCellFirstColumn) { int previousDisplayColumnIndex = columnDisplayIndex - 1; int previousDataColumnIndex = GetDataColumnIndexFromDisplayColumnIndex(previousDisplayColumnIndex, dataGridColumns); bool isCurrentRowPreviousCellVisible = IsCellVisible(previousDataColumnIndex, currentRow); if (isCurrentRowPreviousCellVisible) { leftBorderThickness = 0; } } if (!isCurrentCellFirstRow) { int previousRowIndex = rowIndex - 1; DataRow previousRow = sourceTable.Rows[previousRowIndex]; bool isPreviousRowCurrentCellVisible = IsCellVisible(columnDataIndex, previousRow); if (isPreviousRowCurrentCellVisible) { topBorderThickness = 0; } } return new Thickness(leftBorderThickness, topBorderThickness, borderThickness, borderThickness); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotSupportedException(); private bool IsCellVisible(int columnIndex, DataRow dataRow) { object cellValue = dataRow[columnIndex]; return cellValue != DBNull.Value && cellValue != default; } private int GetDataColumnIndexFromDisplayColumnIndex(int displayColumIndex, IList<DataGridColumn> columns) { for (int columnIndex = 0; columnIndex < columns.Count; columnIndex++) { DataGridColumn column = columns[columnIndex]; if (column.DisplayIndex == displayColumIndex) { return columnIndex; } } throw new ArgumentOutOfRangeException(nameof(displayColumIndex)); } }
    
© www.soinside.com 2019 - 2024. All rights reserved.