【问题标题】:WPF use same datatemplate with different bindingWPF 使用具有不同绑定的相同数据模板
【发布时间】:2014-11-04 09:46:06
【问题描述】:

我有以下数据网格

<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 源,我真的吗?必须定义许多不同的数据模板(如 DataTemplate1、DataTemplate2、... 见上文)以将每个 DataGridTemplateColumn 的 CellTemplate 绑定到它?我可以定义一个数据模板并在代码中或通过其他方式动态设置绑定吗?谢谢你的回答!

【问题讨论】:

  • 每个按钮都有自己与相应属性的绑定,所以我认为这里没有办法只使用 1 个模板。尽管我们可以将您的模板缩短为像这样&lt;Button Tag="{Binding SomeProperty}"/&gt;。你也可以尝试一些变通方法,这样我们就不需要为每个按钮设置Tag,那么我们可能在代码隐藏中还有更多工作要做。

标签: wpf binding datatemplate


【解决方案1】:

有办法,但不漂亮,

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

我已将您的对象数组转换为 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;
}

这允许在单个模板中使用命令和命令参数。

然后我们使用 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;
    }

我希望这会有所帮助。

【讨论】:

  • 谢谢,这几天我专注于其他项目,我会尽快尝试你的代码。
猜你喜欢
  • 2020-12-07
  • 1970-01-01
  • 1970-01-01
  • 2012-12-22
  • 2015-01-28
  • 2013-08-16
  • 2013-10-25
  • 2012-07-20
  • 1970-01-01
相关资源
最近更新 更多