WPF DataGrid - 数据绑定到 CellTemplates DataTemplate 中的 DataTable 单元格

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25643765/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 12:27:03  来源:igfitidea点击:

WPF DataGrid - Databind to DataTable cell in CellTemplates DataTemplate

c#wpfmvvmdatagrid

提问by AxdorphCoder

I have a DataGrid with a DataTable with as ItemsSource. The number of columns differ from time to time. If the DataType of a column is of class A I want to use a DataTemplate to customize the appearance of the cell content.

我有一个带有 DataTable 的 DataGrid 和 ItemsSource。列数不时变化。如果列的 DataType 属于 AI 类,则希望使用 DataTemplate 来自定义单元格内容的外观。

I have set

我已经设定

AutoGenerateColumns="True" 

on the DataGrid so that all columns in the DataTable will be generated.

在 DataGrid 上,以便生成 DataTable 中的所有列。

I replace the DataGridColumn with a DataGridTemplateColumn if the DataType is of type A

如果 DataType 是 A 类型,我将 DataGridColumn 替换为 DataGridTemplateColumn

private void DataGrid_AutoGeneratingColumn(object sender, system.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
{
    if (e.PropertyType == typeof(A))
    {
        e.Column = new DataGridTemplateColumn
        {
            CellTemplate = (DataTemplate)Resources["ATemplate"],
            Header = e.Column.Header,
            HeaderTemplate = e.Column.HeaderTemplate,
            HeaderStringFormat = e.Column.HeaderStringFormat
        };
    }
}

The DataTemplate looks like this.

数据模板看起来像这样。

<DataTemplate x:Key="ATemplate">
   <RadioButton Content="{Binding Name}" GroupName="{Binding GroupName}" IsChecked="{Binding IsSelected}" />
</DataTemplate>

The radiobutton is shown, but I get binding errors for all properties, like

显示了单选按钮,但我收到了所有属性的绑定错误,例如

BindingExpression path error: 'IsSelected' property not found on 'object' ''DataRowView'

Class A looks like this

A类看起来像这样

public class A
{
    public string Name { get; set; }
    public string GroupName { get; set; }
    public bool IsSelected { get; set; }
}

How can i databind the DataTemplate to the right cell and property?

如何将 DataTemplate 数据绑定到正确的单元格和属性?

(If you have a MVVM solution in which I don't have to use DataGrid_AutoGeneratingColumn it would be great)

(如果您有一个我不必使用 DataGrid_AutoGeneratingColumn 的 MVVM 解决方案,那就太好了)

EDIT

编辑

I have tried this solution too with no luck. Only the classname is shown in the cell as usual when it doesen't know how to render the class.

我也尝试过这个解决方案,但没有运气。当它不知道如何呈现类时,像往常一样在单元格中只显示类名。

<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Items}">
   <DataGrid.Resources>
      <DataTemplate DataType="{x:Type viewModel:A}">
         <RadioButton Content="{Binding Path=Name}" GroupName="{Binding Path=GroupName}" IsChecked="{Binding Path=IsSelected}" />
      </DataTemplate>
   </DataGrid.Resources>
</DataGrid>

回答by J.H.

The bindings in the template don't work because the DataContext is a DataRowView from the DataTable.

模板中的绑定不起作用,因为 DataContext 是来自 DataTable 的 DataRowView。

One solution is to change your template to set the DataContext to the object you want (of type A), then all your bindings would work (Name, GroupName, IsSelected). To do that, you will need to make a converter and have your template use it.

一种解决方案是更改您的模板以将 DataContext 设置为您想要的对象(类型 A),然后您的所有绑定都将起作用(Name、GroupName、IsSelected)。为此,您需要制作一个转换器并让您的模板使用它。

The DataContext in the template, is bound to it's DataGridCell ancestor which is passed into the converter. From the cell, we can get the DataContext (DataRowView) and we can get the cell's Column. When we make the column in DataGrid_AutoGeneratingColumn, we set the column's SortMemberPath to e.PropertyName (the column's name in the datatable). In the converter, we lookup the object in the DataRowView.Row using the SortMemberPath as the index. We return this as the DataContext for the template.

模板中的 DataContext 绑定到它的 DataGridCell 祖先,后者被传递到转换器中。从单元格中,我们可以获取 DataContext (DataRowView) 和单元格的 Column。当我们在 DataGrid_AutoGeneratingColumn 中创建列时,我们将列的 SortMemberPath 设置为 e.PropertyName(数据表中列的名称)。在转换器中,我们使用 SortMemberPath 作为索引在 DataRowView.Row 中查找对象。我们将其作为模板的 DataContext 返回。

Here is the implementation with a class A and class B. I added two columns of each class to my data table to show that it works with multiple instances.

这是一个类 A 和类 B 的实现。我将每个类的两列添加到我的数据表中,以表明它适用于多个实例。

enter image description here

在此处输入图片说明

MainWindow.xaml:

主窗口.xaml:

<Window x:Class="WpfApplication17.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModel="clr-namespace:WpfApplication17"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <viewModel:DataRowViewConverter x:Key="drvc" />
        <DataTemplate x:Key="ATemplate">
            <RadioButton DataContext="{Binding RelativeSource={RelativeSource AncestorType=DataGridCell}, Converter={StaticResource drvc}}" Content="{Binding Path=Name}" GroupName="{Binding Path=GroupName}" IsChecked="{Binding Path=IsSelected}" />
        </DataTemplate>
        <DataTemplate x:Key="BTemplate">
            <CheckBox DataContext="{Binding RelativeSource={RelativeSource AncestorType=DataGridCell}, Converter={StaticResource drvc}}" Content="{Binding Path=FullName}" IsChecked="{Binding Path=IsChecked}" />
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Items}" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" CanUserAddRows="False">
        </DataGrid>
    </Grid>
</Window>

MainWindow.xaml.cs:

主窗口.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication17
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public System.Data.DataTable Items { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            System.Data.DataTable dt = new System.Data.DataTable();
            dt.Columns.Add("StringColumn", typeof(string));
            dt.Columns.Add("IntColumn", typeof(int));
            dt.Columns.Add("AColumn1", typeof(A));
            dt.Columns.Add("AColumn2", typeof(A));
            dt.Columns.Add("BColumn1", typeof(B));
            dt.Columns.Add("BColumn2", typeof(B));

            dt.Rows.Add(
                "TestString",
                123,
                new A() { Name = "A1", GroupName = "GroupName", IsSelected = true },
                new A() { Name = "A2", GroupName = "GroupName", IsSelected = false },
                new B() { FullName = "B1", IsChecked=true },
                new B() { FullName = "B2", IsChecked=false }
            );

            Items = dt;
            this.DataContext = this;
        }

        private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            DataTemplate dt = null;
            if (e.PropertyType == typeof(A))
                dt = (DataTemplate)Resources["ATemplate"];
            else if (e.PropertyType == typeof(B))
                dt = (DataTemplate)Resources["BTemplate"];

            if (dt != null)
            {
                DataGridTemplateColumn c = new DataGridTemplateColumn()
                {
                    CellTemplate = dt,
                    Header = e.Column.Header,
                    HeaderTemplate = e.Column.HeaderTemplate,
                    HeaderStringFormat = e.Column.HeaderStringFormat,
                    SortMemberPath = e.PropertyName // this is used to index into the DataRowView so it MUST be the property's name (for this implementation anyways)
                };
                e.Column = c;
            }
        }
    }

    public class A
    {
        public string Name { get; set; }
        public string GroupName { get; set; }
        public bool IsSelected { get; set; }
    }

    public class B
    {
        public string FullName { get; set; }
        public bool IsChecked { get; set; }
    }

    public class DataRowViewConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            DataGridCell cell = value as DataGridCell;
            if (cell == null)
                return null;

            System.Data.DataRowView drv = cell.DataContext as System.Data.DataRowView;
            if (drv == null)
                return null;

            return drv.Row[cell.Column.SortMemberPath];
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}