为此,我使用了一个 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));
}
}
当你运行它时,你已经会看到一些问题:
显然 C# 部分缺少/错误,但到目前为止,我找不到任何考虑每列不同行数的示例,因为大多数演示都使用内存中已有的对象填充数据网格。
也许你们可以演示一下如何解决上述问题。
ListBox
作为配置表(代替单行
DataGrid
),使用
DataGrid
作为实际的列视图。对于此视图,我们必须取消单元格及其边框,使其看起来像基于列的
DataGrid
。我们基本上用
DataTable
填充
NULL
来标记非数据单元格:
null
值的单元格都不会呈现。与每列使用简单的
ListBox
相比,优点是您可以获得
DataGrid
控件的排序和重新排序功能。以下示例展示了如何通过使用值转换器并定义
DataGrid.CellStyle
来实现所需的视觉呈现。
Microsoft learn:数据绑定概述 (WPF .NET)
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));
}
}