将DataGrid列的DataTemplate绑定到附加属性

4
我希望能够自定义一个DataGridColumnHeader,以显示多个文本字段而不是仅显示DataGridColumn.Header属性提供的标题文本。如果我没有漏掉什么,我只需要创建一个DataTemplate并绑定到父对象的属性即可。这对于DataGridColumn.Header属性很有效,但绑定到附加属性失败了。
为了完整起见,以下是附加属性的实现:
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));
}

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>  

我非常感谢任何能够澄清我所遗漏的内容的评论或链接。 提前致谢。


你在哪里设置了这个 附加属性 - Rohit Vats
它设置在DataGridTextColumn的最后一行... cust:CustomHeader.Unit="[m/s]"。类'CustomHeader'位于同一程序集(测试项目中)和同一命名空间中,与测试项目的其余部分相同。 "cust"是xmlns:cust ="clr-namespace:DependencyTest"(为了完整起见)... 这种设置是否会引入意外的副作用? - LaWi
行为完全正常,因为您在“DataGridTextColumn”和“HeaderTemplate”上设置了附加属性,而“Visual Tree”中的“HeaderTemplate”不属于您的列。因此,它不会继承该附加属性。如果您将属性的默认值设置为“TestString”,则该值将显示在标题中。 - Rohit Vats
2个回答

4
你应该使用多值转换器(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>

代码后台:

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

我认为更好的解决方案是创建一个新类,例如HeaderData,然后您可以在xaml中创建它的实例并绑定到这个类。
例如:
用于保存标题数据的类:
class HeaderData
{
    public string Name { get; set; }
    public string Unit { get; set; }
}

XAML 代码:

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

谢谢。从易用性的角度来看,第二种方法看起来非常不错。所以我将其标记为答案。 我只是在想,因为我认为附加属性应该正好解决我遇到的这个问题。 当我想要绑定cust:HeaderData中的两个文本值时,我猜我必须将属性实现为DependencyProperties?还是我必须派生自DependencyObject? - LaWi
不,你可以创建简单的属性,而且不必从DependencyObject派生。在我的答案中,我向你展示了一个示例类。 - kmatyaszek
我正在尝试使用这个解决方案,但是希望能够使用双向绑定来绑定TextBox。所以,我想要的不是硬编码的值Unit="[km/h]",而是类似于Unit="{Binding Unit, Mode=TwoWay}"这样的东西。我尝试过使用附加属性和单独的类,但是我无法将值传递回我的视图模型。在附加属性示例中,问题出在ConvertBack上,因为我不知道如何调用SetUnit(obj, value)。在HeaderData情况下,我可以获取到HeaderData.Unit属性的值,但是无法将其传递回我的视图模型。我现在完全被卡住了。 - lukeguy

1
乍一看,您的代码很正常,应该可以工作。但是当您的依赖属性设置为时,未被调用,变量的值为。我尝试在中分配附加依赖属性的值(因为它是附加的,可以在任何地方设置其值),在这种情况下必须工作:
<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>

在您的情况下,属性并未起作用,因为需要指定属性的来源,例如:ElementNameSource。因此,只需在参数ElementName中添加DataGridTextColumn的名称即可:
<TextBlock Text="{Binding Path=(local:CustomHeader.Unit), ElementName=SpeedColumn}" />

1
谢谢您的回复。这解决了一个DataColumn的问题。我也尝试使用RelativeSource进行绑定,但在我的示例中未能获得正确的祖先。我的意图是为每个列使用DynamicResource字符串、图像等轻松应用一些更多的信息到标题上。因此,直接通过窗口/用户控件级别的附加属性进行管理并不完全符合我的需求。 - LaWi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接