WPF datagrid header text binding

后端 未结 9 2226
执念已碎
执念已碎 2020-11-27 18:01

The column header of the DataGrid is not a FrameWork element for some reason, and so you cannot use bindings to set things like the header text. Please correct me if that is

相关标签:
9条回答
  • 2020-11-27 18:20

    I solved this by using the HeaderTemplate and binding to the DataContext of the DataGrid, using 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>
    

    The same binding within the Header property did not work out.

    0 讨论(0)
  • 2020-11-27 18:32

    I know this post is old, but when I looked up how to do this, this is the first entry that came up. I did not like this answer because it seemed like overkill. After more searching, I ran across this link the showed how to do this in the markup, using a template column.

    <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>
    
    0 讨论(0)
  • 2020-11-27 18:37

    **EDIT :-

    You can style the DataGridColumnHeader and do some funky bindings. try here and download the ColumnHeaderBindings.zip, it has a little test project, that is a bit of a hack, but it works

    **End Edit

    The Binding on the column happens on a per row basis, the column is not part of the visual tree, the binding gets applied to each item in the grid, from the grids source code you can see that the property Binding has these comments

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

    So binding to the columns does not make much sense, because as you have found out, when you are not part of the visual tree you have no data context.

    The same problem exists with the ComboBoxColumn when you want to bind to the items source. You can bind to a StaticResource, but StaticResources dont have a data context either. You could use an object data provider or just instantiate directly in xaml.

    but i would just create the columns in code, and set the header. this problem would just go away then.

    there is a good article here on the visual layout.

    0 讨论(0)
  • 2020-11-27 18:37

    @mmichtch's answer works well for me, you just have to create a local namespace(xmlns), which contains reference to your project as follows:

    xmlns:local="clr-namespace:your_project_name"
    

    and along with it don't forget to mention the property you want to bind:

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

    it works well with VS 2010 and .net version 4.

    0 讨论(0)
  • 2020-11-27 18:38

    My solution allows writing a single line in DataGridColumn with the name of the property that need to bind. He has the following features:

    • There is support for DataGridTextColumn
    • There is support for DataGridTemplateColumn
    • Set StringFormat for each column
    • Specify a static value for the StringFormat
    • Fully complies with MVVM pattern

    Example, which is below, includes StringFormat (he should stand before the PropertyPath):

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

    Equivalent to a this line:

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

    Who need more examples of solutions and features, please read below.

    Link for the sample project.


    Notes about the solution

    From all the solutions that I have seen earlier, the easiest for me turned out to be this example:

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

    Please pay attention to DataGridTextColumn.HeaderTemplate, if was used DataGridTextColumn.Header, then for .NET framework below version 4.5 and for Silverlight would produce an exception:

    Header property does not support UIElements

    It would seem that what is necessary? I wanted to find a solution that would allow to write a single line in DataGridColumn with the name of the property that need to bind.

    And here's what happened:

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

    This construction similar to this:

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

    Also is possible to use StringFormat for each column like this:

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

    And there is the ability to specify a static value for the StringFormat:

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

    Here is the original DataTemplate, which is dynamically set to the column:

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

    In order to RelativeSource did not depend on the type of DataContext, I took great solution from Mr.Bruno.

    In this case, DataGridCellsPanel contains the correct DataContext, which is set for a parent DataGrid.

    Below is the basic code that is performed all the magic:

    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 must appear before the PropertyPath, because it is optional. In order to for columns, who did not have it is not an exception occurs, I registered try-catch in GetStringFormat:

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

    Plus: do not write in methods try-catch block, that are trying to get the value.

    Minus: The minus for every missed StringFormat exception will be generated once when the program starts. If it is critical for you, you can always specify the StringFormat="null" for the column.

    Just in case, show the full code of project:

    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
    }
    
    0 讨论(0)
  • 2020-11-27 18:42

    This is the easy way to bind the DataGridTextColumn header to the data context:

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

    You will obviously need to have properties: SecondColumnTitle and ThirdColumnTitle implemented on your data context class.

    I have this solution working in .net 4.5 and did not have a chance nor reason to try it in earlier versions of the framework.

    0 讨论(0)
提交回复
热议问题