Encuadernación de texto del encabezado de la tabla de datos de WPF

El encabezado de columna del DataGrid no es un elemento FrameWork por algún motivo, por lo que no puede usar enlaces para establecer cosas como el texto del encabezado. Por favor corrígeme si eso está mal si eso ha cambiado con .NET 4.0 (ahora estoy usando el último WPFToolkit de CodePlex).

Estoy tratando de usar DataGrid para una presentación de hoja de horas donde la fecha del día debe ser parte del texto del encabezado (es decir, “Dom, 01 de noviembre”), y tengo lo siguiente en mi XAML:

   ... every other day of the week ....    

Me gustaría usar el mismo AllocationViewModel que estoy usando para los datos (es decir, “{Binding Allocations [0] .Amount}” y enlazar su propiedad DisplayName al texto del encabezado. ¿Alguien me puede mostrar cómo hacerlo? para usar un recurso estático, ¿cómo puedo obtener el DataContext allí?

EDIT —————- PREFERRED WORK-AROUND

Josh Smith había publicado un DataContextSpy hace un tiempo, y es la solución más limpia que he encontrado para este problema. Aquí está la clase que lo hace funcionar:

 ///  /// Workaround to enable  bindings in situations where the DataContext is not redily available. ///  /// http://blogs.infragistics.com/blogs/josh_smith/archive/2008/06/26/data-binding-the-isvisible-property-of-contextualtabgroup.aspx 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(); } } 

Con esto en su lugar, puedo secuestrar el DC que necesito en xaml:

     

Y luego aplicar según sea necesario a través de enlace:

   

¡Suh-weet!

Esta es la manera más fácil de vincular el encabezado DataGridTextColumn al contexto de datos:

                

Obviamente deberá tener propiedades: SecondColumnTitle y ThirdColumnTitle implementados en su clase de contexto de datos.

Tengo esta solución funcionando en .NET 4.5 y no tuve la oportunidad ni el motivo para probarla en versiones anteriores del framework.

Por cierto, en Silverlight (probado con SL 3.0) puede simplemente usar la propiedad Header como el DataContext para el conjunto ControlTemplate a través de HeaderStyle (vea mi pregunta relacionada en SO ).

¡Acabo de probar esta solución en WPF 3.5 usando WPF Toolkit DataGrid y funciona !

** EDITAR: –

Puede diseñar el DataGridColumnHeader y hacer algunos enlaces funky. prueba aquí y descarga ColumnHeaderBindings.zip, tiene un pequeño proyecto de prueba, es un truco, pero funciona

** Fin Editar

El enlace en la columna ocurre por fila , la columna no es parte del árbol visual, el enlace se aplica a cada elemento en la cuadrícula, desde el código fuente de cuadrículas puede ver que la propiedad Enlace tiene estos comentarios

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

Por lo tanto, el enlace a las columnas no tiene mucho sentido, porque, como habrás descubierto, cuando no eres parte del árbol visual no tienes contexto de datos.

El mismo problema existe con ComboBoxColumn cuando desea enlazar con el origen de los elementos. Puede vincularse a un StaticResource, pero StaticResources tampoco tiene un contexto de datos. Puede usar un proveedor de datos de objeto o simplemente crear una instancia directamente en xaml.

pero solo crearía las columnas en código y configuraría el encabezado. este problema desaparecería entonces.

hay un buen artículo aquí en el diseño visual.

Mi solución permite escribir una sola línea en DataGridColumn con el nombre de la propiedad que debe enlazarse. Él tiene las siguientes características:

  • Hay soporte para DataGridTextColumn
  • Hay soporte para DataGridTemplateColumn
  • Establecer StringFormat para cada columna
  • Especifique un valor estático para StringFormat
  • Cumple completamente con el patrón MVVM

El ejemplo, que se muestra a continuación, incluye StringFormat (debe estar delante de PropertyPath ):

  

Equivalente a esta línea:

  

Quien necesita más ejemplos de soluciones y características, lea a continuación.

Link para el proyecto de muestra.


Notes about the solution

De todas las soluciones que he visto antes, la más fácil para mí resultó ser este example :

        

Preste atención a DataGridTextColumn.HeaderTemplate , si se usó DataGridTextColumn.Header , luego para .NET framework debajo de la versión 4.5 y para Silverlight produciría una excepción:

La propiedad de encabezado no es compatible con UIElements

Parecería que lo que es necesario? Quería encontrar una solución que me permitiera escribir una sola línea en DataGridColumn con el nombre de la propiedad que debe enlazarse.

Y esto es lo que sucedió:

  

Esta construcción similar a esto:

  

También es posible usar StringFormat para cada columna como esta:

  

Y existe la posibilidad de especificar un valor estático para StringFormat :

  

Aquí está el DataTemplate original, que se establece dinámicamente en la columna:

    

Para que RelativeSource no dependiera del tipo de DataContext , tomé una gran solution del Sr. Bruno .

En este caso, DataGridCellsPanel contiene el DataContext correcto, que se establece para un DataGrid principal.

A continuación se muestra el código básico que se realiza toda la magia:

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(""); return sb.ToString(); } 

StringFormat debe aparecer antes de PropertyPath, porque es opcional. Para columnas, quien no lo tuvo no es una excepción, registré try-catch en GetStringFormat :

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

Además: no escriba en los métodos bloque try-catch, que están tratando de obtener el valor.

Menos: el menos por cada excepción de StringFormat perdida se generará una vez cuando se inicie el progtwig. Si es crítico para usted, siempre puede especificar StringFormat="null" para la columna.

Por las dudas, muestre el código completo del proyecto:

 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(""); return sb.ToString(); } #endregion } 

XAML

                                 

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 } 

una solución aún mejor sería establecer el enlace en el estilo del encabezado y pasar la columna como el dataContext del encabezado … (o incluso mejor: para configurar un objeto que representa el dataContext del encabezado y pasarlo)

ver allí una forma de hacer esto:

Cómo establecer DataContext en un encabezado de columna DataGrid

Sé que esta publicación es antigua, pero cuando busqué la forma de hacerlo, esta es la primera entrada que surgió. No me gustó esta respuesta porque parecía excesivo. Después de más búsquedas, encontré este enlace que mostraba cómo hacer esto en el marcado, usando una columna de plantilla.

    ****         

La respuesta de @mmichtch funciona bien para mí, solo tiene que crear un espacio de nombre local (xmlns), que contiene una referencia a su proyecto de la siguiente manera:

 xmlns:local="clr-namespace:your_project_name" 

y junto con esto, no olvide mencionar la propiedad que desea vincular:

       

funciona bien con VS 2010 y .net versión 4.

Lo usé para poblar el encabezado de columna de DataGrid.

El truco está en el modo vinculante. Su ” Modo ” debe establecerse en ” OneWay ”. De lo contrario, no es bueno.

Ejemplo:

  

Usé un valor de reserva en minúscula, y el valor de DataContext se puso en mayúscula para asegurarme de que el recurso no era nulo. Además, el valor de DataContext solo aparecía en el tiempo de ejecución, durante el tiempo de diseño mostraba el valor de recuperación. Espero que esto ayude.