0
votes

I have the following datagrid

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

and in the codebehind I have:

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

The datatable has more than 30 columns (I only wrote 2 in here to make it easier to follow).. the question is: If I want to show the same template style with different binging source in every column, do I really have to define many different datatemplates (like DataTemplate1, DataTemplate2, ... see above) to bind the CellTemplate of each DataGridTemplateColumn to it ? Can I define one datatemplate and in the code or through other way to dynamic set the binding? Thank you for your answer!

1
each button has it own binding with the corresponding Property, so I don't think there is some way to use just 1 template here. Although we can shorten your templates to be like this <Button Tag="{Binding SomeProperty}"/>. Also you can try some work-around so that we don't need to set the Tag for each Button, then we may have more work to do in codebehind. - King King

1 Answers

0
votes

There is a way but it is not pretty,

For brevity I am using the code behind as the view model and I have removed exception handling and unnecessary lines.

I have convert your object array into a Person object (for reasons that should become evident later in the solution)

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

I have converted your DataSource into an ObservableCollection So the code behind is now

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

The DelegateCommand is defined as

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

This allows the use of Commands and CommandParameters in the single template.

We then use a MultiValueConverter to get the data for each button. It uses the header of the column to interrogate, using reflection, the Person object for the required value and then pass it back as the parameter of the command.

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

The final piece of the puzzle is the 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>

You should be able to cut and past this code into a new wpf app and see it work. Not I have not take care of the AddNewItem row the grid adds (because we have not turned it off).

This can be done with a collection of object array rather than a collection of Person. To do so, you would need to pass the DataGridCellsPanel into the converter and use it with the header to calculate the index of the required value. The convert would look like

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

The converter code would be along the lines

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

I hope this helps.