WPF数据表格 - 在CellTemplates DataTemplate中将单元格绑定到DataTable单元格

8
我有一个DataGrid,其中包含一个DataTable作为ItemsSource。列的数量随时间变化而不同。如果某个列的数据类型是A类,我想使用DataTemplate自定义单元格内容的外观。
我已经设置了
AutoGenerateColumns="True" 

在DataGrid上设置,这样DataTable中的所有列都将被生成。

如果DataType是类型A,则我会用DataGridTemplateColumn替换DataGridColumn。

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

数据模板长这样。
<DataTemplate x:Key="ATemplate">
   <RadioButton Content="{Binding Name}" GroupName="{Binding GroupName}" IsChecked="{Binding IsSelected}" />
</DataTemplate>

单选按钮已经显示,但是对于所有属性我都得到了绑定错误,例如:

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

类A看起来像这样

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

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

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

编辑

我也尝试过这个解决方案,但没有成功。当无法渲染类时,仅显示单元格中的类名。

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

对不起,我的错...那样做行不通。 - Sheridan
1个回答

20

因为数据上下文是来自DataTable的DataRowView,所以模板中的绑定不起作用。

一种解决方案是将模板的数据上下文更改为你想要的对象(类型为A),然后所有绑定都将起作用(Name、GroupName、IsSelected)。为此,你需要创建一个转换器并让模板使用它。

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

这里是使用类A和类B的实现示例。我向我的数据表中添加了每个类的两列,以显示它可以与多个实例一起使用。

enter image description here

MainWindow.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:

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

2
我尝试了你提到的每一项,但在DataTemplate中的DataContext绑定一直有问题。非常感谢你! - AxdorphCoder
我认为将数据绑定到DataGridCell会导致内存泄漏(由于静态PropertyChangedTracker)。将数据绑定到非INotifyPropertyChanged源将会造成泄漏。 - ThumbGen
我该如何在C#中以编程方式生成DataTemplate? - mpsyp
1
你真是个传奇。这正是我四年后从你的答案中遇到的问题。我不认为我自己能够解决这个问题。转换器是我缺失的主要部分。 - Pseudonymous

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接