WPF使用具有不同绑定的相同datatemplate

时间:2014-11-04 09:46:07

标签: wpf binding datatemplate

我有以下数据网格

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="VenusProductInfoQueryWPF.MainWindow"
    Height="350" Width="646" WindowStyle="None" ResizeMode="NoResize" Topmost="False" MouseLeftButtonDown="Window_MouseLeftButtonDown" AllowsTransparency="True" WindowStartupLocation="CenterScreen" ShowInTaskbar="True">
    <Window.Resources>        
        <DataTemplate x:Key="DataTemplate1">
            <Button Tag="{Binding Name}" Content="Show" Click="LinkButton_Click"></Button>
        </DataTemplate>
        <DataTemplate x:Key="DataTemplate2">
            <Button Tag="{Binding Sex}" Content="Show" Click="LinkButton_Click"></Button>
        </DataTemplate>
    </Window.Resources>`

    <Grid>
        <DataGrid x:Name="MyDataGrid"  HorizontalAlignment="Left" Margin="60,44,0,0" 
                  VerticalAlignment="Top" Height="223" Width="402" AutoGenerateColumns="False"
                  AutoGeneratedColumns="MyDataGrid_AutoGeneratedColumns">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Age" Binding="{Binding Path=Age}"></DataGridTextColumn>
                <DataGridTemplateColumn Header="Sex" CellTemplate="{StaticResource DataTemplate2}"/>
                <DataGridTemplateColumn Header="Name" CellTemplate="{StaticResource DataTemplate1}"/>
            </DataGrid.Columns>
        </DataGrid>   
    </Grid>
</Window>

在代码隐藏中我有:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows; 
using System.Windows.Media;

namespace WpfApplication1
{
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        dataSource = new DataTable();
        dataSource.Columns.Add("Age");
        dataSource.Columns.Add("Name");
        dataSource.Columns.Add("Sex");

        AddNewRow(new object[] { 10, "wang", "Male" });
        AddNewRow(new object[] { 15, "huang", "Male" });
        AddNewRow(new object[] { 20, "gao", "Female" });

        dataGrid1.ItemsSource = dataSource.AsDataView();
    }
}
}

数据表有超过30列(我在这里只写了2个,以便更容易理解)..问题是:如果我想在每一列中显示相同的模板样式和不同的binging源,我真的必须定义许多不同的datatemplates(如DataTemplate1,DataTemplate2,...见上文)将每个DataGridTemplateColumn的CellTemplate绑定到它?我可以定义一个datatemplate并在代码中或通过其他方式动态设置绑定吗?谢谢你的回答!

1 个答案:

答案 0 :(得分:0)

有一种方法,但它不漂亮,

为简洁起见,我使用后面的代码作为视图模型,我已经删除了异常处理和不必要的行。

我已将您的对象数组转换为Person对象(原因应该在解决方案的后期变得明显)

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Sex { get; set; }
}

我已将您的DataSource转换为ObservableCollection所以后面的代码现在是

public partial class MainWindow : Window
{
    public MainWindow()
    {
        Items = new ObservableCollection<Person>()
        {
            new Person {Age = 10, Name = "wang", Sex="Male"},
            new Person {Age = 15, Name = "huang", Sex="Male"},
            new Person {Age = 20, Name = "gao", Sex="Female"}
        };
        ShowCommand = new DelegateCommand(ExecuteShowCommand, CanExecuteShowCommand);
        InitializeComponent();
    }

    private bool CanExecuteShowCommand(object arg) { return true; }
    private void ExecuteShowCommand(object obj) { MessageBox.Show(obj != null ? obj.ToString() : "No Parameter received"); }
    public DelegateCommand ShowCommand { get; set; }
    public ObservableCollection<Person> Items { get; set; }
}

DelegateCommand定义为

public class DelegateCommand : ICommand
{
    private Func<object, bool> _canExecute;
    private Action<object> _execute;

    public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
    {
        _canExecute = canExecute;
        _execute = execute;
    }

    public bool CanExecute(object parameter) { return _canExecute.Invoke(parameter); }
    void ICommand.Execute(object parameter) { _execute.Invoke(parameter); }
    public event EventHandler CanExecuteChanged;
}

这允许在单个模板中使用Commands和CommandParameters。

然后我们使用MultiValueConverter获取每个按钮的数据。它使用列的标题来使用反射查询Person对象以获取所需的值,然后将其作为命令的参数传回。

public class GridCellToValueConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        object returnValue = null;
        var person = values.First() as Person;
        var propertyName = values[1] == DependencyProperty.UnsetValue ? string.Empty : (string)values[1];
        if ((person != null) && (!string.IsNullOrWhiteSpace(propertyName))) { returnValue = person.GetType().GetProperty(propertyName).GetValue(person); }
        return returnValue;
    }

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

最后一块拼图是xaml

<Window x:Class="StackOverflow.Q26731995.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:q26731995="clr-namespace:StackOverflow.Q26731995" Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <q26731995:GridCellToValueConverter x:Key="GridCell2Value" />
        <DataTemplate x:Key="ButtonColumnDataTemplate">
            <Button Content="Show" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=ShowCommand}">
                <Button.CommandParameter>
                    <MultiBinding Converter="{StaticResource GridCell2Value}">
                        <Binding /> <!-- The person object -->
                        <Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridCell}}" Path="Column.Header" /> <!-- The name of the field -->
                    </MultiBinding>
                </Button.CommandParameter>
            </Button>
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <DataGrid x:Name="MyDataGrid"  HorizontalAlignment="Left" Margin="60,44,0,0" ItemsSource="{Binding Path=Items}" VerticalAlignment="Top" Height="223" Width="402" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Age" Binding="{Binding Path=Age}"></DataGridTextColumn>
                <DataGridTemplateColumn Header="Sex" CellTemplate="{StaticResource ButtonColumnDataTemplate}"/>
                <DataGridTemplateColumn Header="Name" CellTemplate="{StaticResource ButtonColumnDataTemplate}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

您应该能够将此代码剪切并通过新的wpf应用程序并查看它是否有效。不是我没有处理网格添加的AddNewItem行(因为我们没有将其关闭)。

这可以使用对象数组的集合而不是Person的集合来完成。为此,您需要将DataGridCellsPanel传递给转换器,并将其与标头一起使用以计算所需值的索引。转换看起来像

<MultiBinding Converter="{StaticResource GridCell2Value}">
    <Binding /> <!-- The data -->
    <Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridCell}}" Path="Column.Header}" /> <!-- The name of the field -->
    <Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridCellsPanel}}" /> <!-- The panel that contains the row -->
</MultiBinding>

转换器代码将沿着

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        object returnValue = null;

        var data = values.First() as object[];
        var property = values[1] == DependencyProperty.UnsetValue ? string.Empty : (string)values[1];
        var panel = values[2] as DataGridCellsPanel;
        var column = panel.Children.OfType<DataGridCell>().FirstOrDefault(c => c.Column.Header.Equals(property));
        if (column != null)
        {
            returnValue = data[panel.Children.IndexOf(column)];
        }
        return returnValue;
    }

我希望这会有所帮助。