none
WPF datagrid header text binding RRS feed

  • Question

  • Dear all,

    I try to set my Datagrid header dynamically all morning, but it doesn't work.

    I've tried the following codes so far:

                <DataGrid x:Name="DG_Test" 
                          AutoGenerateColumns="False" 
                          ItemsSource="{Binding list_Data}"
               ....
               <DataGrid.Columns>
               ....
                        <DataGridTextColumn Binding="{Binding Path= Col_1_data, Mode=TwoWay}" IsReadOnly="True" >
                            <DataGridTextColumn.HeaderTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding DataContext.Header_1, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
                                </DataTemplate>
                            </DataGridTextColumn.HeaderTemplate>
                        </DataGridTextColumn>    
               .....

    My model contains both the data and the column header (Col_1_data, Col_2_data ... but also Header_1, Header_2...)

    I tried AncestorType=UserControl but also AncestorType=DataGrid, but header always remains empty.

    But if I set header as static (e.g. "manual header"), it works. Header is displayed in DataGrid!!!

                        <DataGridTextColumn Binding="{Binding Path= Col_1_data, Mode=TwoWay}" IsReadOnly="True" >
                            <DataGridTextColumn.HeaderTemplate>
                                <DataTemplate>
                                    <TextBlock Text="manual header"/>
                                </DataTemplate>
                            </DataGridTextColumn.HeaderTemplate>
                        </DataGridTextColumn> 

    What am I doing wrong? Can anyone help me?

    Thank you

    Saturday, August 3, 2019 8:58 AM

Answers

  • I've found the solution: You create a new model for the column headings. From this you create an object (not a list) in ViewModel. Then you use it in XAML.

    Instead of writing this:
    ...Binding DataContext.Header_1...

    you have to take the following:

    Binding DataContext.MyNewModelObject.Header_1

    And then it works.

    One question:

    How is it possible that a company like Microsoft with so much money then hires people who develop a new technology (here WPF) and don't do it consistently?
    No wonder the technology is dying!

    It (WPF) could have been an incredibly good thing, but the boys who designed it have from time to time forgotten what the concept was. So with 3/4 all controls you have the possibility to specify binding, and then abruptly like here with Header no more possibility! Why?

    I find such discrepancies over and over again, so that one day I will be really happy when WPF dies. 

    • Marked as answer by daniel_2019 Saturday, August 3, 2019 12:43 PM
    Saturday, August 3, 2019 12:42 PM

All replies

  • Hi,
    in my demo your code without changes work without problems. I think your ViewModel and the binding is incorrect. Here is my demo:

    XAML Main Window:

    <Window x:Class="Window45"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            xmlns:uc="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
            mc:Ignorable="d"
            Title="Window45" Height="450" Width="800">
      <Window.DataContext>
        <local:Window45VM/>
      </Window.DataContext>
      <Grid>
        <uc:Window45UC1/>
      </Grid>
    </Window>

    ViewModel:

    Public Class Window45VM
    
      Public Sub New()
        list_Data = New List(Of Data)
        For i = 1 To 10
          list_Data.Add(New Data With {.Col_1_data = $"Data {i}"})
        Next
      End Sub
    
      Public Property list_Data As List(Of Data)
    
      Public Property Header_1 As String = "Col 1"
    
      Public Class Data
        Public Property Col_1_data As String
      End Class
    
    End Class

    And XAML of UserControl:

    <UserControl x:Class="Window45UC1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfControlLibrary1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <Grid>
        <DataGrid x:Name="DG_Test" 
                          AutoGenerateColumns="False" 
                          ItemsSource="{Binding list_Data}">
          <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Path= Col_1_data, Mode=TwoWay}" IsReadOnly="True" >
              <DataGridTextColumn.HeaderTemplate>
                <DataTemplate>
                  <TextBlock Text="{Binding DataContext.Header_1, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
                </DataTemplate>
              </DataGridTextColumn.HeaderTemplate>
            </DataGridTextColumn>
          </DataGrid.Columns>
        </DataGrid>
      </Grid>
    </UserControl>


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks


    Saturday, August 3, 2019 9:48 AM
  • Thanks Peter,

    I set my DataContext as follows:

    <UserControl x:Class="MyNameSpace.Views.ThisViewClass"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:viewModel = "clr-namespace:WpfApp1.MyNameSpace.ViewModels.MyDataGrid"
                 xmlns:i = "http://schemas.microsoft.com/expression/2010/interactivity" 
                 d:DesignHeight="640" d:DesignWidth="1024">
    
        <UserControl.DataContext>
            <viewModel:MyDataGridFormViewModel/>
        </UserControl.DataContext>


    The class of my View (Code Behinde) is empty as expected: 

    namespace TC2.Views.MyDataGrid
    {
        public class ThisViewClass
        {
        }
    }

    In the ViewModel class I feel the data (list of the model)

    namespace TC2.ViewModels.MyDataGrid {
        
        public class MyDataGridFormViewModel {
            
            public void Fill_DG_Test {
    	    list_Data.Add(....) //add columns values, and header name (headername is the same for all records)
    End Sub
            }
        }
    }

    The DataGrid has about 10 columns. I get the values from the MS SQL database. I am using MVVM, pattern:
    1. I have first model classes in which I have defined DataGrid columns (plus column headers)
    2. In the ViewModel class I create an ObservableCollection list using Model and fill this list with values from MS SQL.
    3. in my view (XAML) i have my form. Its code Behinde class is empty. Now I define a connection between my View and ViewModel class using:

        <UserControl.DataContext>
            <viewModel:MyDataGridFormViewModel/>
        </UserControl.DataContext>

    Everything works fine and all values from the ObservableCollection list are nice displayed on the form in Xaml. 

    Now when I try to load the column headers dynamically, it doesn't work.

    Where do you think I make the mistake?

    Thanks

    EDIT:

    Maybe it works for you because you set your DataContex in the main window. I can't do that because I have about 10 UserControls and each UserControl has its own DataContext.




    • Edited by daniel_2019 Saturday, August 3, 2019 12:01 PM
    Saturday, August 3, 2019 11:50 AM
  • I've found the solution: You create a new model for the column headings. From this you create an object (not a list) in ViewModel. Then you use it in XAML.

    Instead of writing this:
    ...Binding DataContext.Header_1...

    you have to take the following:

    Binding DataContext.MyNewModelObject.Header_1

    And then it works.

    One question:

    How is it possible that a company like Microsoft with so much money then hires people who develop a new technology (here WPF) and don't do it consistently?
    No wonder the technology is dying!

    It (WPF) could have been an incredibly good thing, but the boys who designed it have from time to time forgotten what the concept was. So with 3/4 all controls you have the possibility to specify binding, and then abruptly like here with Header no more possibility! Why?

    I find such discrepancies over and over again, so that one day I will be really happy when WPF dies. 

    • Marked as answer by daniel_2019 Saturday, August 3, 2019 12:43 PM
    Saturday, August 3, 2019 12:42 PM
  • Hi,
    if I set the DataContext in UserControl it works fine:

    XAML UserControl:

    <UserControl x:Class="WpfControlLibrary1.Window53UC1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfControlLibrary1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <UserControl.DataContext>
        <local:Window53UC1VM/>
      </UserControl.DataContext>
      <Grid>
        <DataGrid x:Name="DG_Test" 
                          AutoGenerateColumns="False" 
                          ItemsSource="{Binding list_Data}">
          <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Path= Col_1_data, Mode=TwoWay}" IsReadOnly="True" >
              <DataGridTextColumn.HeaderTemplate>
                <DataTemplate>
                  <TextBlock Text="{Binding DataContext.Header_1, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
                </DataTemplate>
              </DataGridTextColumn.HeaderTemplate>
            </DataGridTextColumn>
          </DataGrid.Columns>
        </DataGrid>
      </Grid>
    </UserControl>

    ViewModel for UserControl:

    using System.Collections.Generic;
    using System.Windows.Controls;
    
    namespace WpfControlLibrary1
    {
      public class Window53UC1VM
      {
    
        public Window53UC1VM()
        {
          for (int i = 1; i < 10; i++) list_Data.Add(new Data() { Col_1_data = $"Data {i}" });
        }
    
        public List<Data> list_Data { get; set; } = new List<Data>();
    
        public string Header_1 { get; set; } = "Col 1";
    
        public class Data
        {
          public string Col_1_data { get; set; }
        }
      }
    }


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Sunday, August 4, 2019 7:11 AM
  • Hi,
    if the Header-string is the same in every data object you can set the header from the first data object like in this demo:

    XAML:

    <UserControl x:Class="WpfControlLibrary1.Window53UC1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfControlLibrary1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <UserControl.DataContext>
        <local:Window53UC1VM/>
      </UserControl.DataContext>
      <Grid>
        <DataGrid x:Name="DG_Test" 
                          AutoGenerateColumns="False" 
                          ItemsSource="{Binding list_Data}">
          <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Path= Col_1_data, Mode=TwoWay}" IsReadOnly="True" >
              <DataGridTextColumn.HeaderTemplate>
                <DataTemplate>
                  <TextBlock Text="{Binding DataContext.list_Data[0].Header_1, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
                </DataTemplate>
              </DataGridTextColumn.HeaderTemplate>
            </DataGridTextColumn>
          </DataGrid.Columns>
        </DataGrid>
      </Grid>
    </UserControl>
    

    ViewModel:

    using System.Collections.Generic;
    using System.Windows.Controls;
    
    namespace WpfControlLibrary1
    {
      public class Window53UC1VM
      {
    
        public Window53UC1VM()
        {
          for (int i = 1; i < 10; i++) list_Data.Add(new Data() { Col_1_data = $"Data {i}", Header_1 = "Col 1" });
        }
    
        public List<Data> list_Data { get; set; } = new List<Data>();
    
        public class Data
        {
          public string Header_1 { get; set; }
          public string Col_1_data { get; set; }
        }
      }
    }


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Sunday, August 4, 2019 7:18 AM