【问题标题】:How can I change cell template in a dynamic DataGrid (binded to a DataTable)?如何更改动态 DataGrid(绑定到 DataTable)中的单元格模板?
【发布时间】:2020-10-02 16:35:33
【问题描述】:

我正在尝试更改 DataGrid 内的单元格模板,具体取决于包含单元格类型的 List<List<int>>(即 1=bool、2=int、3=string、4=custom、等等...)。自定义类型(类型,因为它们可以超过 1 个)必须由 ComboBox 表示。对于数字和字符串,我需要一个普通的TextBox,对于布尔值,我需要一个CheckBoxDataGrid 绑定到 DataTable,我可以在运行时调整和编辑它。这是一些代码:

<Grid>
    <DataGrid ItemsSource="{Binding Path=DataTable}" Name="Grid" AutoGenerateColumns="True" 
      CanUserResizeRows="True" CanUserDeleteRows="False"
      CanUserAddRows="False" AreRowDetailsFrozen="False"
      SelectionUnit="Cell" LoadingRow="Grid_LoadingRow">
        <DataGrid.Style>
            <Style TargetType="DataGrid">
                <Setter Property="AlternatingRowBackground" Value="LightYellow"/>
            </Style>
        </DataGrid.Style>
    </DataGrid>
</Grid>
public partial class TableEditorWindow : Window
    {
        public string[] DebugNames = { "Bob", "Dan", "Pierre", "Mark", "Gary" };

        // Stores the values of the Table
        public ds_grid Table { get; set; }

        // Stores the types of each cell in the Table
        public ds_grid ValueTypesTable { get; set; }

        // Used as wrapper between the Table variable and the DataGrid
        public DataTable DataTable { get; set; }


        public TableEditorWindow()
        {
            InitializeComponent();

            Table = new ds_grid(5, 5);

            // Fills the Table with 1s
            for (int i = 0; i < 5; ++i)
            {
                for (int j = 0; j < Table.Width; ++j)
                {
                    Table.Set(i, j, 1d);
                }
            }

            DataTable = new DataTable();

            // Add the columns
            for (int i = 0; i < 5; ++i)
            {
                DataTable.Columns.Add(DebugNames[i]);
            }

            // Add the rows
            for (int i = 0; i < Table.Height; ++i)
            {
                DataRow _row = DataTable.NewRow();

                for (int j = 0; j < Table.Width; ++j)
                {
                    _row[j] = Table.Get(j, i);
                }

                DataTable.Rows.Add(_row);
            }

            Grid.DataContext = this;
            Grid.RowHeaderWidth = 50;
            Grid.ColumnWidth = 100;
        }

        // Gives to each row the correct name
        private void Grid_LoadingRow(object sender, DataGridRowEventArgs e)
        {
            int _id = e.Row.GetIndex();

            e.Row.Header = DebugNames[_id];
        }
    }

ds_grid 基本上是一个List&lt;List&lt;object&gt;&gt;,周围有一些实用方法。

我看到有一些解决方案,例如使用 DataTrigger,但我认为在这种情况下我需要在 XAML 文件中的DataGrid 中写入,但我不能因为AutoGenerateColumnsTrue。还有可能更改DataTable 的每一列的类型,但我不希望该列的每个单元格都属于该类型,我希望在运行时只有一个单元格成为该类型。

也许有更好的解决方案,例如不使用DataGrid,或者不使用DataTable,或者有一种方法可以将AutoGenerateColumns 设置为False,并在需要时通过代码手动生成每一列.任何建议都非常感谢。

提前致谢。

【问题讨论】:

  • 有事件拦截更改生成列:stackoverflow.com/a/40359000/1506454
  • 是的,但我不希望以这种方式显示整个列。我只希望该列的 1 个单元格与其他单元格不同。

标签: c# wpf datatable binding datagrid


【解决方案1】:

这与我单独提交的原始答案有很大不同。

我还想指出,DataGrid非常非常规用法。常见的数据结构是每列都有一个类型,我几乎不需要其他类型。如果您坚持该约定,我之前的答案将有效。话虽如此,您所要求的可以完成。

如果您真的想忽略通用数据结构并在单元格级别自定义内容,则需要自定义DataGridColumn

public class DataTableBoundColumn : DataGridBoundColumn
{
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        if (dataItem == CollectionView.NewItemPlaceholder) { return null; }

        DataRowView dataRow = (dataItem as DataRowView);
        if (dataRow == null) { throw new ArgumentException(); }

        object cellData = dataRow[cell.Column.DisplayIndex];

        var contentHost = new ContentControl() { Content = cellData };

        //Do some tests on cellData to determine the type and pick a DataTemplate
        //Alternatively, you could build the actual content here in code-behind, but a DataTemplate would probably be cleaner
        contentHost.ContentTemplate = (DataTemplate)SomeResourceDictionary["SomeResourceKey"];

        return contentHost;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        return GenerateElement(cell, dataItem);
    }
}

以上内容基于this article 的示例。使用此列类型,您的 AutoGeneratingColumn 处理程序将如下所示:

private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    DataTableBoundColumn col = new DataTableBoundColumn();
    e.Column = col;
    e.Column.Header = "Whatever you want";
}

【讨论】:

  • 非常感谢!在对该主题进行了大量研究之后(您发送给我的链接很棒),我已经设法做到了!因为我想摆脱 DataTable 和 Binding(因为它在大网格 (1000x1000) 上太慢了),所以我也没有在 XAML 文件中使用 DataTemplate。相反,我根据条件通过代码创建了每个元素。通过这种方式,我提高了很多性能,并且摆脱了 DataTable。现在绑定是手动的:每次编辑一个单元格时,我都会引发一个事件,该事件会更新我使用的数据结构内的内容,以便更新。
  • 这可能看起来“离题”,但我花了很多钱:当向DataTable 添加列时,在我的情况下有必要使用重载Add(string columnName, **Type type**),否则结果dataRow[cell.Column.DisplayIndex]GenerateElement 中提取的不是我所期望的(在我的情况下是一个字符串)。
  • 另见here:如果DataGrid.EnableRowVirtualization="True"(默认值),则滚动后出现的行可能不会调用GenerateElement。我猜EnableColumnVirtualization 也是如此。
【解决方案2】:

如果您想customize the auto-generated columns,则必须使用DataGrid.AutoGeneratingColumn 事件。我知道您不希望列的实体相同,但您仍然需要使用此事件 - 您只需使用不同的方式。

您在考虑Styles 和DataTriggers 以动态更改单元格模板时走在正确的轨道上。这通常可以通过在 XAML 中声明列来完成,但您可以通过使用 DataGrid.AutoGeneratingColumn 并将列声明为资源来解决此问题。

采取这样的措施:

<DataGrid>
    <DataGrid.Resources>
        <DataGridTemplateColumn x:Key="TemplateColumn" x:Shared="False">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ContentControl Content="{Binding}">
                        <ContentControl.Style>
                            <Style TargetType="ContentControl">
                                <Style.Triggers>
                                    <!--Use DataTriggers to set the content to whatever you need-->
                                    <DataTrigger>
                                        <!--...-->
                                    </DataTrigger>

                                    <DataTrigger>
                                        <!--...-->
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </ContentControl.Style>
                    </ContentControl>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Resources>
</DataGrid>

上面是一个DataTemplate,它使用ContentControlDataTriggers来动态设置ContentTemplate。我用x:Key 将其定义为DataGrid 的资源。 x:Shared="False" 意味着 WPF 将在请求资源时创建此列的新实例,而不是创建一个并给出对该单个实例的引用,以便用户可以“共享”它。这将允许您将列的多个实例添加到 DataGrid

您的AutoGeneratingColumn 会是这样的:

private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    //Add an if statement if you only want to replace some of the columns with the dynamic template one

    var DG = (DataGrid)sender;
    DataGridTemplateColumn col = (DataGridTemplateColumn)DG.Resources["TemplateColumn"];
    e.Column = col;
    e.Column.Header = "Whatever you want";
}

这会将您的列替换为 TemplateColumn 资源的实例。

【讨论】:

  • 首先感谢您的明确回答!但是我在理解它时仍然存在一些问题:1)我应该在DataGrid_AutoGeneratingColumn中写的评论中写什么?举个例子:列高是4。除了第二列和第四列之外,该列的所有单元格都是默认的。我该怎么做?
  • 2) 我应该在 DataTrigger 中添加什么条件?让我们保留上面的示例......在这种情况下,有两种类型的单元格,默认的一种和修改后的一种。了解单元格类型的唯一方法是在确切位置访问ValueTypesTable 的元素。我如何知道 XML 中该特定列的索引?或者也许我应该使用另一个数据作为条件?如果你能举个例子那就太棒了!再次感谢您!
  • @Penca53 DataGrid 的工作前提是每列代表列表中项目的单个特定属性。一列中的所有单元格通常是相同的。您将无法在单个列中包含一些默认值和一些自定义项 - 它们必须全部是自定义的,您只需使用 DataTrigger 使自定义单元格看起来像默认单元格(如果需要) s.
  • @Penca53 该单元格内的DataContext 将是行项目,我认为对您来说将是DataRow。在AutoGeneratingColumn 中,您可以获得列的DisplayIndex,您应该可以使用它来设置该列的绑定以指向DataTable 中的右列。
  • 如果条件是一个叫X的变量,可以是1-2-3,DataTrigger里面应该写什么?以及如何将该变量绑定到该特定数据触发器?这就是问题所在。
猜你喜欢
  • 1970-01-01
  • 2021-10-23
  • 2011-04-07
  • 2014-10-27
  • 2016-11-12
  • 2017-11-05
  • 2020-03-27
  • 2014-09-22
  • 2016-08-02
相关资源
最近更新 更多