Привязка текста заголовка сетки данных WPF
Заголовок столбца DataGrid по какой-то причине не является элементом FrameWork, и поэтому вы не можете использовать привязки для установки таких вещей, как текст заголовка. Пожалуйста, исправьте меня, если это не так, если это изменилось в.NET 4.0 (сейчас я использую последнюю версию WPFToolkit от CodePlex).
Я пытаюсь использовать DataGrid для представления расписания, где дата дня должна быть частью текста заголовка (т. Е. "Sun, Nov 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 ответов
Это простой способ привязать заголовок 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, и у меня не было ни шансов, ни причин попробовать его в более ранних версиях фреймворка.
Я знаю, что этот пост старый, но когда я посмотрел, как это сделать, появилась первая запись. Мне не понравился этот ответ, потому что он казался излишним. После дополнительных поисков я натолкнулся на эту ссылку и показал, как это сделать в разметке, используя столбец шаблона.
<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>
Я решил это с помощью HeaderTemplate
и привязка к DataContext
из DataGrid
, с помощью RelativeSource
.
<DataGrid ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Value1}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.ColumnTitel1, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Такая же привязка внутри Header
собственность не сложилась.
Мое решение позволяет написать одну строку в 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 ниже версии 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://stackru.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;
}
}
Плюс: не пишите в блоке 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://stackru.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
}
Кстати, в Silverlight (протестировано с SL 3.0) вы можете просто использовать свойство Header в качестве DataContext для ControlTemplate, установленного через HeaderStyle (см. Мой связанный вопрос по SO).
Я только что попробовал это решение в WPF 3.5, используя WPF Toolkit DataGrid, и оно работает!
Еще лучшим решением было бы установить привязку в стиле заголовка и передать столбец как dataContext заголовка... (или даже лучше: установить объект, представляющий dataContext заголовка и передать его)
посмотрите там, как это сделать:
**РЕДАКТИРОВАТЬ:-
Вы можете стилизовать 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.
но я бы просто создал столбцы в коде и установил заголовок. эта проблема просто исчезнет тогда.
Здесь есть хорошая статья о визуальном оформлении.
Ответ @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.
Я использовал его для заполнения заголовка столбца DataGrid.
Трюк в режиме привязки. Его "Режим" должен быть установлен на "OneWay". В противном случае это не хорошо.Пример:<DataGridTextColumn Binding="{Binding RowData}" Header="{Binding Mode=OneWay, Source={StaticResource spy},Path=DataContext.HeaderText, FallbackValue= header text}"/>
Я использовал резервное значение в нижнем регистре, а значение из DataContext было написано с большой буквы, чтобы убедиться, что ресурс не нулевой. Кроме того, значение из DataContext показывалось для меня только во время выполнения, во время разработки оно отображало запасное значение. Надеюсь это поможет.