且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-10-17 15:18:00

模板中的绑定无效,因为DataContext是DataTable中的DataRowView。



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



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



这里是具有类A和类B的实现。我在数据表中添加了每个类的两列以显示





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 =真实 ItemsSource = {Binding Items} AutoGeneratingColumn = DataGrid_AutoGeneratingColumn CanUserAddRows = False>
< / DataGrid>
< / Grid>
< / Window>

MainWindow.xaml.cs:

 使用系统; 
使用System.Collections.Generic;
使用System.Linq;
使用System.Text;
使用System.Threading.Tasks;
使用System.Windows;
使用System.Windows.Controls;
使用System.Windows.Data;
使用System.Windows.Documents;
使用System.Windows.Input;
使用System.Windows.Media;
使用System.Windows.Media.Imaging;
使用System.Windows.Navigation;
使用System.Windows.Shapes;

命名空间WpfApplication17
{
///< summary>
/// MainWindow.xaml的交互逻辑
///< / summary>
公共局部类MainWindow:窗口
{
public System.Data.DataTable Items {get;组; }

public MainWindow()
{
InitializeComponent();

System.Data.DataTable dt =新的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(){名称= A2,GroupName = GroupName,IsSelected = false},
new B(){FullName = B1,IsChecked = true},
new B(){FullName = B2,IsChecked = false}
);

项目= 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,
标头= e.Column.Header,
HeaderTemplate = e.Column.HeaderTemplate,
HeaderStringFormat = e.Column.HeaderStringFormat,
SortMemberPath = e.PropertyName //用于索引到DataRowView中(必须始终是该实现的属性名称)
};
e.Column = c;
}
}
}

公共类A
{
公共字符串名称{get;组; }
公用字串GroupName {get;组; }
public bool IsSelected {get;组; }
}

公共类B
{
公共字符串FullName {get;组; }
public bool IsChecked {get;组; }
}

公共类DataRowViewConverter:IValueConverter
{
#region IValueConverter成员

公共对象Convert(对象值,类型targetType,对象参数,System.Globalization.CultureInfo文化)
{
DataGridCell cell = value as DataGridCell;
if(cell == null)
返回null;

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

返回drv.Row [cell.Column.SortMemberPath];
}

公共对象ConvertBack(对象值,类型targetType,对象参数,System.Globalization.CultureInfo文化)
{
throw new NotImplementedException();
}

#endregion
}
}


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.

I have set

AutoGenerateColumns="True" 

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

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

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

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?

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

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>

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

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.

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.

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.

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