Привязка текста заголовка сетки данных WPF - PullRequest
27 голосов
/ 02 ноября 2009

Заголовок столбца DataGrid по какой-то причине не является элементом FrameWork, и поэтому вы не можете использовать привязки для установки таких вещей, как текст заголовка. Пожалуйста, исправьте меня, если это не так, если это изменилось в .NET 4.0 (сейчас я использую последний WPFToolkit из CodePlex).

Я пытаюсь использовать DataGrid для представления табеля рабочего времени, где дата дня должна быть частью текста заголовка (т. Е. «Солнце, ноябрь 01»), и в моем XAML есть следующее:

        <dg:DataGrid.Columns>
        <dg:DataGridTextColumn Header="Description" Width="Auto" Binding="{Binding Description}" IsReadOnly="True"/>
        <dg:DataGridTextColumn Header="Mon" Width="50" Binding="{Binding Allocations[0].Amount}"  />
... every other day of the week ....
        <dg:DataGridTextColumn Header="Sun" Width="50" Binding="{Binding Allocations[6].Amount}"  />
        <dg:DataGridTextColumn Header="Total" MinWidth="50" Binding="{Binding TotalAllocatedAmount}" IsReadOnly="True" />
    </dg:DataGrid.Columns>

Я хотел бы использовать тот же AllocationViewModel, который я использую для данных (т. Е. "{Binding Allocations [0] .Amount}", и связать его свойство DisplayName с текстом заголовка. Может кто-нибудь показать мне, как это сделать? Если мне нужно использовать статический ресурс, как я могу получить DataContext там?

РЕДАКТИРОВАТЬ ---------------- ПРЕДПОЧТИТЕЛЬНАЯ РАБОТА ВОКРУГ

Джош Смит недавно написал о DataContextSpy , и это самый чистый обходной путь, с которым я столкнулся в этой проблеме. Вот класс, который заставляет его работать:

/// <summary>
/// Workaround to enable <see cref="DataContext"/> bindings in situations where the DataContext is not redily available. 
/// </summary>
/// <remarks>http://blogs.infragistics.com/blogs/josh_smith/archive/2008/06/26/data-binding-the-isvisible-property-of-contextualtabgroup.aspx</remarks>
public class DataContextSpy : Freezable
{
    public DataContextSpy()
    {
        // This binding allows the spy to inherit a DataContext.
        BindingOperations.SetBinding(this, DataContextProperty, new Binding());
    }

    public object DataContext
    {
        get { return GetValue(DataContextProperty); }
        set { SetValue(DataContextProperty, value); }
    }

    // Borrow the DataContext dependency property from FrameworkElement.
    public static readonly DependencyProperty DataContextProperty = FrameworkElement
        .DataContextProperty.AddOwner(typeof (DataContextSpy));

    protected override Freezable CreateInstanceCore()
    {
        // We are required to override this abstract method.
        throw new NotImplementedException();
    }
}

Имея это, я могу перехватить нужный мне DC в xaml:

    <dg:DataGrid.Resources>
        <behavior:DataContextSpy x:Key="spy" DataContext="{Binding Allocations}" />
    </dg:DataGrid.Resources>

А затем примените при необходимости через связывание:

            <dg:DataGridTextColumn Header="{Binding Source={StaticResource spy}, Path=DataContext[0].DisplayName}" 
                               Width="50" Binding="{Binding Allocations[0].Amount}"  />

Suh-Weet!

Ответы [ 8 ]

18 голосов
/ 17 октября 2012

Это простой способ привязать заголовок DataGridTextColumn к контексту данных:

<DataGrid x:Name="summaryGrid" Grid.Row="3" AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False">
       <DataGrid.Columns>
            <DataGridTextColumn Header="Hard Coded Title" Width="*"/>
            <DataGridTextColumn Width="100">
                <DataGridTextColumn.Header>
                    <TextBlock Text="{Binding DataContext.SecondColumnTitle, 
                                              RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
                </DataGridTextColumn.Header>
            </DataGridTextColumn>
            <DataGridTextColumn Width="150">
                <DataGridTextColumn.Header>
                    <TextBlock Text="{Binding DataContext.ThirdColumnTitle, 
                                              RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
                </DataGridTextColumn.Header>
            </DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>

Очевидно, вам понадобятся свойства: SecondColumnTitle и ThirdColumnTitle, реализованные в вашем классе контекста данных.

У меня есть это решение, работающее в .net 4.5, и у меня не было ни шансов, ни причин попробовать его в более ранних версиях фреймворка.

4 голосов
/ 24 ноября 2009

Кстати, в Silverlight (протестировано с SL 3.0) вы можете просто использовать свойство Header в качестве DataContext для ControlTemplate , установленного через HeaderStyle (см. Мой связанный вопрос по SO ).

Я только что попробовал это решение в WPF 3.5, используя WPF Toolkit DataGrid и , он работает !

2 голосов
/ 27 февраля 2014

Мое решение позволяет написать одну строку в DataGridColumn с именем свойства, которое необходимо связать. У него есть следующие особенности:

  • Есть поддержка DataGridTextColumn
  • Есть поддержка DataGridTemplateColumn
  • Установить StringFormat для каждого столбца
  • Укажите статическое значение для StringFormat
  • Полностью соответствует шаблону MVVM

Пример, приведенный ниже, включает StringFormat (он должен стоять перед PropertyPath):

<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="StringFormat: {0:C}"
                    Behaviors:DataGridHeader.PropertyPath="HeaderValueOne" ... /> 

Эквивалент этой линии:

<DataGridTextColumn HeaderStringFormat="{0:C}"
                    Header="{Binding Path=HeaderValueOne}" ... />

Кому нужно больше примеров решений и функций, читайте ниже.

Link для примера проекта.


Notes about the solution

Из всех решений, которые я видел ранее, самым простым для меня оказалось это example:

<DataGridTextColumn Binding="{Binding Name}">
    <DataGridTextColumn.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=DataContext.YourPropertyName,
                                      RelativeSource={RelativeSource AncestorType={x:Type SomeControl}}" />
         </DataTemplate>
    </DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>        

Обратите внимание на DataGridTextColumn.HeaderTemplate, если используется DataGridTextColumn.Header, то для .NET Framework ниже версии 4.5 и для Silverlight будет выдано исключение:

Свойство Header не поддерживает UIElements

Казалось бы, что нужно? Я хотел найти решение, которое позволило бы написать одну строку в DataGridColumn с именем свойства, которое нужно связать.

И вот что случилось:

<DataGridTextColumn Behaviors:DataGridHeader.PropertyPath="HeaderValueOne" // Attached dependency property 

Эта конструкция похожа на эту:

<DataGridTextColumn Header="{Binding Path=HeaderValueOne}" ... />

Также можно использовать StringFormat для каждого столбца, например:

<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="StringFormat: {0:C}"
                    Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue" ... />

И есть возможность указать статическое значение для StringFormat:

<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="{x:Static Member=this:TestData.TestStaticStringFormatValue}" // public static string TestStaticStringFormatValue = "Static StringFormat: {0}$";
                    Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue"

Вот оригинал DataTemplate, который динамически устанавливается в столбец:

<DataTemplate>
    <TextBlock Text="{Binding Path=DataContext.YourPropertyName,
                              StringFormat="YourStringFormat",
                              RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}" />
</DataTemplate>

Чтобы RelativeSource не зависело от типа DataContext, я взял solution у мистера Bruno .

В этом случае DataGridCellsPanel содержит правильный DataContext, который установлен для родительского DataGrid.

Ниже приведен базовый код, который выполняет вся магия:

IsSetHeader PropertyChanged handler

private static void IsSetHeader(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    var textColumn = sender as DataGridTextColumn;
    var templateColumn = sender as DataGridTemplateColumn;
    string path = e.NewValue as string;

    if ((textColumn == null) & (templateColumn == null)) 
    {
        return;
    }

    if (String.IsNullOrEmpty(path) == false)
    {
        currentStringFormat = ReturnStringFormat(textColumn, templateColumn);
        dataTemplate = CreateDynamicDataTemplate(path, currentStringFormat);

        if (dataTemplate != null)
        {
            if (textColumn != null)
                textColumn.HeaderTemplate = dataTemplate;

            if (templateColumn != null)
                templateColumn.HeaderTemplate = dataTemplate;
        }
    }
}

CreateDynamicDataTemplate

private static DataTemplate CreateDynamicDataTemplate(string propertyPath, string stringFormat)
{
    var pc = new ParserContext();
    MemoryStream sr = null;

    string xaml = GetXamlString(propertyPath, stringFormat);            
    sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));

    pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
    pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");

    return XamlReader.Load(sr, pc) as DataTemplate;
}

GetXamlString

private static string GetXamlString(string propertyPath, string stringFormat)
{
    #region Original PropertyPath for TextBlock

    // {Binding Path=DataContext.YourProperty, RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}"
    // Thanks to Bruno (https://stackoverflow.com/users/248118/bruno) for this trick

    #endregion

    var sb = new StringBuilder();

    sb.Append("<DataTemplate><TextBlock Text=\"{Binding Path=DataContext.");
    sb.Append(propertyPath);
    sb.Append(", StringFormat=");
    sb.Append(stringFormat);
    sb.Append(", RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}\" /></DataTemplate>");

    return sb.ToString();
}

StringFormat должен появляться перед PropertyPath, потому что это необязательно. Для того, чтобы для столбцов, у которых его не было, не возникает исключение, я зарегистрировал try-catch в GetStringFormat:

 public static string GetStringFormat(DependencyObject DepObject)
 {
    try
    {
        return (string)DepObject.GetValue(StringFormatProperty);
    }

    catch 
    {
        return String.Empty;
    }
 }

Plus: не записывать в методы try-catch блок, пытающийся получить значение.

Минус: Минус для каждого пропущенного StringFormat исключения будет сгенерирован один раз при запуске программы. Если это важно для вас, вы всегда можете указать StringFormat="null" для столбца.

На всякий случай покажите полный код проекта:

public static class DataGridHeader
{
    #region Private Section

    private static string textColumnStringFormat = null;
    private static string templateColumnStringFormat = null;
    private static string currentStringFormat = null;
    private static DataTemplate dataTemplate = null;

    #endregion

    #region PropertyPath DependencyProperty

    public static readonly DependencyProperty PropertyPathProperty;

    public static void SetPropertyPath(DependencyObject DepObject, string value)
    {
        DepObject.SetValue(PropertyPathProperty, value);
    }

    public static string GetPropertyPath(DependencyObject DepObject)
    {
        return (string)DepObject.GetValue(PropertyPathProperty);
    }

    #endregion

    #region StringFormat DependencyProperty

    public static readonly DependencyProperty StringFormatProperty;

    public static void SetStringFormat(DependencyObject DepObject, string value)
    {
        DepObject.SetValue(StringFormatProperty, value);
    }

    public static string GetStringFormat(DependencyObject DepObject)
    {
        try
        {
            return (string)DepObject.GetValue(StringFormatProperty);
        }

        catch 
        {
            return String.Empty;
        }
    }

    #endregion

    #region Constructor

    static DataGridHeader()
    {
        PropertyPathProperty = DependencyProperty.RegisterAttached("PropertyPath",
                                                                   typeof(string),
                                                                   typeof(DataGridHeader),
                                                                   new UIPropertyMetadata(String.Empty, IsSetHeader));

        StringFormatProperty = DependencyProperty.RegisterAttached("StringFormat",
                                                                   typeof(string),
                                                                   typeof(DataGridHeader),
                                                                   new UIPropertyMetadata(String.Empty));  
    }

    #endregion

    #region IsSetHeader PropertyChanged Handler

    private static void IsSetHeader(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var textColumn = sender as DataGridTextColumn;
        var templateColumn = sender as DataGridTemplateColumn;
        string path = e.NewValue as string;

        if ((textColumn == null) & (templateColumn == null)) 
        {
            return;
        }

        if (String.IsNullOrEmpty(path) == false)
        {
            currentStringFormat = ReturnStringFormat(textColumn, templateColumn);
            dataTemplate = CreateDynamicDataTemplate(path, currentStringFormat);

            if (dataTemplate != null)
            {
                if (textColumn != null)
                    textColumn.HeaderTemplate = dataTemplate;

                if (templateColumn != null)
                    templateColumn.HeaderTemplate = dataTemplate;
            }
        }
    }

    #endregion

    #region ReturnStringFormat Helper

    private static string ReturnStringFormat(DependencyObject depObject1, DependencyObject depObject2) 
    {
        textColumnStringFormat = GetStringFormat(depObject1) as string;
        templateColumnStringFormat = GetStringFormat(depObject2) as string;

        if (String.IsNullOrEmpty(textColumnStringFormat) == false)
        {
            return textColumnStringFormat;
        }

        if (String.IsNullOrEmpty(templateColumnStringFormat) == false)
        {
            return templateColumnStringFormat;
        }

        return "null";
    }

    #endregion

    #region CreateDynamicDataTemplate Helper

    private static DataTemplate CreateDynamicDataTemplate(string propertyPath, string stringFormat)
    {
        var pc = new ParserContext();
        MemoryStream sr = null;

        string xaml = GetXamlString(propertyPath, stringFormat);            
        sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));

        pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
        pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");

        return XamlReader.Load(sr, pc) as DataTemplate;
    }

    #endregion

    #region GetXamlString Helper

    private static string GetXamlString(string propertyPath, string stringFormat)
    {
        #region Original PropertyPath for TextBlock

        // {Binding Path=DataContext.YourProperty, RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}"
        // Thanks to Bruno (https://stackoverflow.com/users/248118/bruno) for this trick

        #endregion

        var sb = new StringBuilder();

        sb.Append("<DataTemplate><TextBlock Text=\"{Binding Path=DataContext.");
        sb.Append(propertyPath);
        sb.Append(", StringFormat=");
        sb.Append(stringFormat);
        sb.Append(", RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}\" /></DataTemplate>");

        return sb.ToString();
    }

    #endregion
}

XAML

<Window x:Class="BindingHeaderInDataGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:BindingHeaderInDataGrid"
        xmlns:Behaviors="clr-namespace:BindingHeaderInDataGrid.AttachedBehaviors"
        WindowStartupLocation="CenterScreen"
        Title="MainWindow" Height="220" Width="600">

    <Window.DataContext>
        <this:TestData />
    </Window.DataContext>

    <Grid Name="TestGrid">
        <DataGrid Name="TestDataGrid" 
                  Width="550"
                  Height="100"
                  Margin="10"
                  VerticalAlignment="Top"
                  Background="AliceBlue">

            <DataGrid.Columns>
                <DataGridTextColumn Behaviors:DataGridHeader.StringFormat="StringFormat: {0:C}"
                                    Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue"
                                    Width="100"
                                    IsReadOnly="False">

                    <DataGridTextColumn.HeaderStyle>
                        <Style TargetType="{x:Type DataGridColumnHeader}">
                            <Setter Property="Height" Value="20" />
                            <Setter Property="Background" Value="Pink" />
                            <Setter Property="Margin" Value="2,0,0,0" />
                        </Style>
                    </DataGridTextColumn.HeaderStyle>
                </DataGridTextColumn>

                <DataGridTextColumn Behaviors:DataGridHeader.StringFormat="{x:Static Member=this:TestData.TestStaticStringFormatValue}"
                                    Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue"
                                    Width="2*"
                                    IsReadOnly="False">

                    <DataGridTextColumn.HeaderStyle>
                        <Style TargetType="{x:Type DataGridColumnHeader}">
                            <Setter Property="Height" Value="20" />
                            <Setter Property="Background" Value="CadetBlue" />
                            <Setter Property="Margin" Value="2,0,0,0" />
                        </Style>
                    </DataGridTextColumn.HeaderStyle>
                </DataGridTextColumn>

                <DataGridTextColumn Behaviors:DataGridHeader.PropertyPath="TestUsualHeaderValue"
                                    Width="1.5*" 
                                    IsReadOnly="False">

                    <DataGridTextColumn.HeaderStyle>
                        <Style TargetType="{x:Type DataGridColumnHeader}">
                            <Setter Property="Height" Value="20" />
                            <Setter Property="Background" Value="Gainsboro" />
                            <Setter Property="Margin" Value="2,0,0,0" />
                        </Style>
                    </DataGridTextColumn.HeaderStyle>
                </DataGridTextColumn>

                <DataGridTemplateColumn Behaviors:DataGridHeader.PropertyPath="TestTemplateColumnValue"
                                        Width="150"
                                        IsReadOnly="False">

                    <DataGridTemplateColumn.HeaderStyle>
                        <Style TargetType="{x:Type DataGridColumnHeader}">
                            <Setter Property="Height" Value="20" />
                            <Setter Property="Background" Value="Beige" />
                            <Setter Property="Margin" Value="2,0,0,0" />
                        </Style>
                    </DataGridTemplateColumn.HeaderStyle>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

        <Button Name="ChangeHeader" 
                Width="100" 
                Height="30"
                VerticalAlignment="Bottom"
                Content="ChangeHeader" 
                Click="ChangeHeader_Click" />
    </Grid>
</Window>

Code-behind

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }        

    private void ChangeHeader_Click(object sender, RoutedEventArgs e)
    {
        TestData data = this.DataContext as TestData;

        data.TestStringFormatValue = "777";
        data.TestUsualHeaderValue = "DynamicUsualHeader";
        data.TestTemplateColumnValue = "DynamicTemplateColumn";
    }
}

public class TestData : NotificationObject
{
    #region TestStringFormatValue

    private string _testStringFormatValue = "1";

    public string TestStringFormatValue
    {
        get
        {
            return _testStringFormatValue;
        }

        set
        {
            _testStringFormatValue = value;
            NotifyPropertyChanged("TestStringFormatValue");
        }
    }

    #endregion

    #region TestStaticStringFormatValue

    public static string TestStaticStringFormatValue = "Static StringFormat: {0}$";

    #endregion

    #region TestUsualHeaderValue

    private string _testUsualHeaderValue = "UsualHeader";

    public string TestUsualHeaderValue
    {
        get
        {
            return _testUsualHeaderValue;
        }

        set
        {
            _testUsualHeaderValue = value;
            NotifyPropertyChanged("TestUsualHeaderValue");
        }
    }

    #endregion

    #region TestTemplateColumnValue

    private string _testTemplateColumnValue = "TemplateColumn";

    public string TestTemplateColumnValue
    {
        get
        {
            return _testTemplateColumnValue;
        }

        set
        {
            _testTemplateColumnValue = value;
            NotifyPropertyChanged("TestTemplateColumnValue");
        }
    }

    #endregion
}
2 голосов
/ 02 ноября 2009

** РЕДАКТИРОВАТЬ: -

Вы можете стилизовать DataGridColumnHeader и сделать несколько прикольных привязок. попробуйте здесь и скачайте ColumnHeaderBindings.zip, у него есть небольшой тестовый проект, который немного взломан, но работает

** Конец редактирования

Связывание для столбца происходит на основе для строки , столбец не является частью визуального дерева, привязка применяется к каждому элементу в сетке из В исходном коде сетки вы можете видеть, что свойство Binding имеет следующие комментарии

    /// <summary>
    ///     The binding that will be applied to the generated element.
    /// </summary>
    /// <remarks>
    ///     This isn't a DP because if it were getting the value would evaluate the binding.
    /// </remarks>

Поэтому привязка к столбцам не имеет особого смысла, потому что, как вы выяснили, когда вы не являетесь частью визуального дерева, у вас нет контекста данных.

Та же проблема существует с ComboBoxColumn, когда вы хотите привязать к источнику элементов. Вы можете привязать к StaticResource, но StaticResources также не имеет контекста данных. Вы можете использовать объектный поставщик данных или просто создать экземпляр непосредственно в xaml.

но я бы просто создал столбцы в коде и установил заголовок. тогда эта проблема просто исчезнет.

есть хорошая статья здесь о визуальном макете.

1 голос
/ 01 августа 2017
Ответ

@ mmichtch хорошо работает для меня, вам просто нужно создать локальное пространство имен (xmlns), которое будет содержать ссылку на ваш проект следующим образом:

xmlns:local="clr-namespace:your_project_name"

и наряду с этим не забудьте упомянуть свойство, которое вы хотите связать:

                <DataGridTextColumn Width="Auto">
                <DataGridTextColumn.Header>
                    <TextBlock Text="{Binding DataContext.PropertyNameYouWantToBind, 
                                          RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
                </DataGridTextColumn.Header>
            </DataGridTextColumn>

хорошо работает с VS 2010 и .net версии 4.

1 голос
/ 30 апреля 2016

Я знаю, что этот пост старый, но когда я посмотрел, как это сделать, появилась первая запись. Мне не понравился этот ответ, потому что он казался излишним. После дополнительного поиска я наткнулся на эту ссылку , которая показала, как сделать это в разметке, используя столбец шаблона.

<DataGridTemplateColumn>
    <DataGridTemplateColumn.HeaderTemplate>
        <DataTemplate>
            **<TextBlock Text="{Binding DataContext.HeaderTitle, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />**
        </DataTemplate>
    </DataGridTemplateColumn.HeaderTemplate>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}" Width="200" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
1 голос
/ 22 октября 2010

еще лучшим решением было бы установить привязку в стиле заголовка и передать столбец как dataContext заголовка ... (или даже лучше: настроить объект, представляющий dataContext заголовка и передать его)

посмотрите, как это сделать:

Как установить DataContext в заголовке столбца DataGrid

0 голосов
/ 17 января 2014

Я использовал его для заполнения заголовка столбца DataGrid.

Трюк в режиме привязки. Его «Режим» должен быть установлен на «OneWay». В противном случае это не хорошо. Пример:
<DataGridTextColumn Binding="{Binding RowData}" Header="{Binding Mode=OneWay, Source={StaticResource spy},Path=DataContext.HeaderText,  FallbackValue= header text}"/>

Я использовал резервное значение в нижнем регистре, а значение из DataContext было написано с большой буквы, чтобы убедиться, что ресурс не нулевой. Кроме того, значение из DataContext показывалось для меня только во время выполнения, во время разработки оно отображало запасное значение. Надеюсь, это поможет.

...