سؤال

I am trying to customize a DataGridColumnHeader to show multiple text fields instead of
showing only the header text provided by DataGridColumn.Header property.
If i didn't miss something, i just have to create a DataTemplate and bind to the properties
of the parent object. This works fine for the DataGridColumn.Header property but
binding to attached property fails.

For the sake of completeness, implementation of the attached property:

public static class CustomHeader
{
    public static string GetUnit(DependencyObject obj) { return (string)obj.GetValue(UnitProperty); }
    public static void SetUnit(DependencyObject obj, string value) { obj.SetValue(UnitProperty, value); }

    public static readonly DependencyProperty UnitProperty = DependencyProperty.RegisterAttached(
        "Unit", typeof(string), typeof(CustomHeader), new FrameworkPropertyMetadata(null));
}

Usage in the Xaml-Markup:

<DataGrid x:Name="tObjectDataGrid" Margin="10,50,10,10" 
                  AutoGenerateColumns="False" EnableRowVirtualization="True"  
                  ItemsSource="{Binding ObjectList}"  
                  RowDetailsVisibilityMode="VisibleWhenSelected" >
  <DataGrid.Resources>  
    <DataTemplate x:Key="CustomHeaderTemplate">  
      <StackPanel>  
        <TextBlock Text="{Binding}" />  
        <TextBlock Text="{Binding Path=(cust:CustomHeader.Unit)}" /> <-- attached binding doesn't work :( 
      </StackPanel>  
    </DataTemplate>  
  </DataGrid.Resources>  

  <DataGrid.Columns>  
    <DataGridTextColumn x:Name="SpeedColumn"
                        Width="1*"
                        Binding="{Binding Speed}"
                        Header="Speed"
                        HeaderTemplate="{StaticResource CustomHeaderTemplate}"
                        cust:CustomHeader.Unit="[m/s]" />  
  </DataGrid.Columns>  
</DataGrid>  

I really appreciate any comment or weblink that clarifies what i am missing here. Thanks in advance.

هل كانت مفيدة؟

المحلول

You should use multi value converter (msdn).

XAML:

<DataGrid x:Name="tObjectDataGrid" Margin="10,50,10,10" 
          AutoGenerateColumns="False" EnableRowVirtualization="True"  
          ItemsSource="{Binding ObjectList}"  
          RowDetailsVisibilityMode="VisibleWhenSelected" >
    <DataGrid.Resources>
        <cust:UnitConverter x:Key="unitCon" />
        <DataTemplate x:Key="CustomHeaderTemplate">
            <StackPanel>
                <TextBlock Text="{Binding}" />
                <TextBlock>
                    <TextBlock.Text>
                        <MultiBinding Converter="{StaticResource unitCon}">
                            <Binding Path="Columns" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=DataGrid}" />
                            <Binding Path="." />
                        </MultiBinding>
                    </TextBlock.Text>
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </DataGrid.Resources>

    <DataGrid.Columns>
        <DataGridTextColumn x:Name="SpeedColumn"
                Width="1*"
                Binding="{Binding Speed}"
                Header="Speed"
                HeaderTemplate="{StaticResource CustomHeaderTemplate}"
                cust:CustomHeader.Unit="[m/s]" />

        <DataGridTextColumn x:Name="SpeedColumn2"
                Width="1*"
                Binding="{Binding Speed2}"
                Header="Speed2"
                HeaderTemplate="{StaticResource CustomHeaderTemplate}"
                cust:CustomHeader.Unit="[km/h]" />
    </DataGrid.Columns>
</DataGrid>

Code-behind:

public class UnitConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string result = null;

        ObservableCollection<DataGridColumn> columns = values[0] as ObservableCollection<DataGridColumn>;
        string headerName = values[1].ToString();

        var coll = columns.Where(x => x.Header.ToString() == headerName).FirstOrDefault();

        if (coll != null)
            result = CustomHeader.GetUnit(coll);

        return result;
    }

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

I think that better solution will be create new class e.g HeaderData and after that you can create instance of it in xaml and binding to this class.

Example:

Class to hold header data:

class HeaderData
{
    public string Name { get; set; }
    public string Unit { get; set; }
}

XAML code:

<DataGrid x:Name="tObjectDataGrid" Margin="10,50,10,10" 
          AutoGenerateColumns="False" EnableRowVirtualization="True"  
          ItemsSource="{Binding ObjectList}"  
          RowDetailsVisibilityMode="VisibleWhenSelected" >
    <DataGrid.Resources>
        <DataTemplate x:Key="CustomHeaderTemplate">
            <StackPanel>
                <TextBlock Text="{Binding Name}" />
                <TextBlock Text="{Binding Unit}" />
            </StackPanel>
        </DataTemplate>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridTextColumn x:Name="SpeedColumn"
                        Width="1*"
                        Binding="{Binding Speed}"                                
                        HeaderTemplate="{StaticResource CustomHeaderTemplate}">             
            <DataGridTextColumn.Header>
                <cust:HeaderData Name="Speed" Unit="[m/s]" />
            </DataGridTextColumn.Header>
        </DataGridTextColumn>
        <DataGridTextColumn x:Name="SpeedColumn2"
                        Width="1*"
                        Binding="{Binding Speed2}"                                
                        HeaderTemplate="{StaticResource CustomHeaderTemplate}">
            <DataGridTextColumn.Header>
                <cust:HeaderData Name="Speed2" Unit="[km/h]" />
            </DataGridTextColumn.Header>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

نصائح أخرى

At first glance, your code is normal, should work. But when your dependency property is set to DataGridTextColumn, SetUnit not called and the variable Unit value NULL. I tried to assign a value of attached dependency property in Window (since it attached, you can set its value anywhere) in this case must work:

<Window x:Class="DataGridAttachedHelp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DataGridAttachedHelp"
    local:CustomHeader.Unit="[m/s]"
    Name="MyWindow"
    Title="MainWindow" Height="350" Width="525"
    WindowStartupLocation="CenterScreen">

<Grid>
    <DataGrid x:Name="tObjectDataGrid" Margin="10,50,10,10" 
              AutoGenerateColumns="False" EnableRowVirtualization="True"                    
              RowDetailsVisibilityMode="VisibleWhenSelected" >

        <DataGrid.Resources>
            <DataTemplate x:Key="CustomHeaderTemplate">
                <StackPanel>
                    <TextBlock Text="{Binding}" />
                    <TextBlock Text="{Binding Path=(local:CustomHeader.Unit), ElementName=MyWindow}" />
                </StackPanel>  
            </DataTemplate>  
        </DataGrid.Resources>  

        <DataGrid.Columns>  
            <DataGridTextColumn x:Name="SpeedColumn" Width="1*" Binding="{Binding Speed}" Header="Speed"
                    HeaderTemplate="{StaticResource CustomHeaderTemplate}" />  
        </DataGrid.Columns>  
    </DataGrid>      
</Grid>
</Window>

In your case the property did not work, because it is necessary to indicate the source of the properties, for example: ElementName, Source. Therefore, simply add the name of DataGridTextColumn in parameter ElementName:

<TextBlock Text="{Binding Path=(local:CustomHeader.Unit), ElementName=SpeedColumn}" />
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top