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

时间:2014-09-03 12:01:33

标签: c# wpf mvvm datagrid

我有一个带有DataTable的DataGrid和ItemsSource。列数随时间而不同。如果列的DataType是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看起来像这样。

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

显示了radiobutton,但是我得到了所有属性的绑定错误,例如

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>

1 个答案:

答案 0 :(得分:10)

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

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

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

这是使用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
    }
}