Customize Grid Column Headers

Aug 28, 2008 at 9:44 PM
I'm very new to WPF and the grid and am looking for a little direction. Basically, I am trying to customize the column headings for the DataGrid. I want the headings to include several controls, such as a checkbox, combobox, and textbox. My first thought is to encapsulate the controls I want as a separate control. I am unsure of how to go about customizing the header though. Is there column header templating in the DataGrid? I would need my control to have access to the original text that is set as the column header as well.

Thanks for any help.

Brian
Coordinator
Aug 28, 2008 at 10:28 PM
You can use DataGridColumn.HeaderTemplate.  

<dg:DataGridTextColumn x:Name="FirstNameColumn" DataFieldBinding="{Binding Path=FirstName}" Header="HeaderText">
    <dg:DataGridTextColumn.HeaderTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <CheckBox IsChecked="True" Content="{Binding}" />
                <TextBlock Text="{Binding}" /> 
            </StackPanel>
        </DataTemplate>
    </dg:DataGridTextColumn.HeaderTemplate>
</dg:DataGridTextColumn>

 


To retrieve the text you can do something like this:

 

string header = DataGrid_Standard.Columns[0].Header.ToString();

 



Aug 28, 2008 at 11:15 PM
This is certainly a start, thank you. Unfortunately, you seem to be creating a single column here. I'd like my custom heading to be used for all columns, as I'm going to leave AutoGenerateColumns set to true. The reason for this is that I'm loading arbitrary data from an Excel spreadsheet into the grid and won't know the columns ahead of time.

Brian
Coordinator
Aug 29, 2008 at 1:23 PM
Sure, that's also possible too.  DataGrid has an API called ColumnHeaderStyle where you can apply the same style to all column headers.  Another way to do this is with implicit styling of the ColumnHeader.  Here is an example using DataGrid.ColumnHeaderStyle:

<

 

Style x:Key="defaultColumnHeaderStyle" TargetType="{x:Type dg:DataGridColumnHeader}">
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <StackPanel>
                    <CheckBox IsChecked="True" Content="{Binding}" />
                    <TextBlock Text="{Binding}" />
                </StackPanel>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

 

<

 

dg:DataGrid x:Name="DataGrid_Standard" ColumnHeaderStyle="{StaticResource defaultColumnHeaderStyle}" ... />

 

Aug 29, 2008 at 1:54 PM
Edited Aug 29, 2008 at 1:56 PM
Check also these blogs:
Vincent Sibal
Jaime Rodriguez

Authors show how you can style DataGrid.


Sep 7, 2008 at 8:17 PM
Vincent

Maybe I am going the wrong way:
I like to have a checkbox for selection in each data row of my datagrid.
I want to get one-click-editing (using your sample) , which seems not possible for SelectionUnit = CELL.
I surely could add a boolean property to my business object to accomplish this, but I'd like to keep the business objects as clean as possible.
Therefore I am searching for a way to get something like an unbound column. Is this possible (maybe in V1)?
Now I added a checkbox in each rows header. How can I programmatically get the state of the checkbox for each row?
Regards
Klaus
Coordinator
Sep 8, 2008 at 4:11 PM
Checkbox selection of a row with an unbound column:  

<dg:DataGridTemplateColumn Header="RowIsSelected" >
    <dg:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=IsSelected}" />
        </DataTemplate>
    </dg:DataGridTemplateColumn.CellTemplate>
    <dg:DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=IsSelected}" />
        </DataTemplate>
    </dg:DataGridTemplateColumn.CellEditingTemplate>
</dg:DataGridTemplateColumn>

 


If you want to programmatically get the state of the checkbox inside a row header without data binding, you will have to get access to the DataGridRowHeader visual, then traverse the visual children looking for the CheckBox in your template.

Sep 9, 2008 at 8:23 AM
Vincent

thanks for your response, but I have some problems with it:
1) If I click the checkbox it directly unchecks again. If I only press the mousekey and hold it, I can see the checkbox is checked, but if I let the mouse button go, it is unchecked again
2) I had my datagrid defined with  SelectionUnit = "Cell" because I like to have "one click editing" (one of your samples). Now I have to set it back to SelectionUnit = "FullRow". I did not thought about setting "IsSelected" for the row must lead to a visible selection other than the checkbox checked. Is there no way out?
3) One practical problem: I like to have "one click editing" active when the grid itself has SelectionUnit="Cell". The standard way to achieve "one click editing" is this handler: But how can I get a handle to the parent grid? I searched the object tree but do not find any property for this

 

Private Sub DataGridCell_PreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)

 

 

Dim cell As DataGridCell = CType(sender, DataGridCell)

 

 

 

 

If Not cell.IsFocused Then Call cell.Focus()

 

 

 

 

If Not cell.IsSelected Then cell.IsSelected = True

 

 

 

 

End Sub

 

4) Is there any documentation for the CTP like reference list of props / methods / events or will this be only available for v1?

 


Regards
Klaus

 

Sep 9, 2008 at 11:15 AM
Hi

ok, it works when I click in any column except the "selection" col, this is a little clumsy
I had expected that "IsSelected" is some internal (not visual) property that is set by clicking the checkbox

So that means that SelectionUnit = FullRow is a must for selecting rows or using IsSelected at all?

Regards
Klaus

Coordinator
Sep 9, 2008 at 1:11 PM
I'm using SelectionUnit = CellOrFullRow and I'm not seeing any problems with the checkbox being directly unchecked again.  Also, single click editing works just fine with CellOrFullRow.

For #3, you want a PreviousMouseDown on the DataGrid?  If so, it's definitely there.
Sep 9, 2008 at 4:39 PM
Hi

I completely misinterpreted this setting CellOrFullRow, works like a charm for me too.

but:
regarding #3: When I am in the Event DataGridCell_PreviewMouseLeftButtonDown, how do I get a handle to the grid object itself? I did not foun da parent or something! Please forgive me this "simple" questions but there is so much new around WPF...
regarding #4: Some kind of reference would be fine

Thanks
Klaus

Coordinator
Sep 9, 2008 at 6:07 PM

The event that I defined as the PreviewMouseLeftButton was actually defined in the window where my DataGrid lives so I have direct access to the DataGrid already.  If you want to get it from the cell itself, just walk up the visual tree untill you find DataGrid.

Here is an example for finding the parent:

public

 

T FindVisualParent<T>(DependencyObject d) where T : DependencyObject
{
    T retVal =
default(T);
    DependencyObject current = d;
    for (; ; )
    {
        FrameworkElement element = current as FrameworkElement;
        if (element == null)
        {
            break;
        }
        DependencyObject parent = VisualTreeHelper.GetParent(element);
        if ((parent as T) != null)
        {
            retVal = parent
as T;
            break;
        }
        current = parent;
    }
    return retVal;
}

 

 

Sep 29, 2008 at 1:12 PM
vinsibal

I used your snippet below for over weeks, now it stopped working.
I know that this is a really diificultt to support from your place, but do you have any hint where I could start looking?

Regards
Klaus

<dg:DataGridTemplateColumn Header="RowIsSelected" >
    <dg:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=IsSelected}" />
        </DataTemplate>
    </dg:DataGridTemplateColumn.CellTemplate>
    <dg:DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=IsSelected}" />
        </DataTemplate>
    </dg:DataGridTemplateColumn.CellEditingTemplate>
</dg:DataGridTemplateColumn>


Coordinator
Sep 29, 2008 at 4:31 PM
How exactly has it stopped working and what has changed since?
Sep 29, 2008 at 8:03 PM
Vinsibal

unfortunately I cannot say what in detail changed, since I play around with this and tried lots of things, two majors are:
1) I created a base class for my business objects that handles IDataErrorInfo and INotifyPropertyChanged
2) I played with the xaml (mainly building the style)

Please find below my usercontrol definition and the styles which come from application.xaml

Regards
Klaus

My usercontrol:
<UserControl x:Class="Modules"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:toolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WpfToolkit"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             >
   <UserControl.Resources>
      <MenuItem x:Key="M1" Header="M1">
         <MenuItem Header="M1.1"></MenuItem>
      </MenuItem>
   </UserControl.Resources>

   <Grid>
      <toolkit:DataGrid x:Name="dgModules"
                   AlternationCount="2"
                   CanUserAddRows="False"
                   CanUserSortColumns="False"
                   AlternatingRowBackground="LightBlue"
                   GridLinesVisibility="None"
                   AutoGenerateColumns="False"
                   IsSynchronizedWithCurrentItem="False"
                   Background="Transparent"
                   RowHeaderWidth="25"
                   RowBackground="White"
                   BorderThickness="0"
                   BorderBrush="White"
                   SelectionUnit="CellOrRowHeader"
                   ItemsSource="{Binding}">
         <toolkit:DataGrid.Columns>
            <toolkit:DataGridTemplateColumn Header="{StaticResource P.RowSelectedYN}">
               <toolkit:DataGridTemplateColumn.CellTemplate>
                  <DataTemplate>
                     <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGridRow}}, Path=IsSelected}" />
                  </DataTemplate>
               </toolkit:DataGridTemplateColumn.CellTemplate>
               <toolkit:DataGridTemplateColumn.CellEditingTemplate>
                  <DataTemplate>
                     <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGridRow}}, Path=IsSelected}" />
                  </DataTemplate>
               </toolkit:DataGridTemplateColumn.CellEditingTemplate>
            </toolkit:DataGridTemplateColumn>

            <toolkit:DataGridTextColumn
                  Header="{StaticResource P.ModuleName}"
                  ElementStyle="{StaticResource styTextBoxColumnStyleGif}"
                  DataFieldBinding="{Binding Path=ModuleName, ValidatesOnDataErrors=True}"
            />
            <toolkit:DataGridTextColumn
                  Header="{StaticResource P.ModuleSeqNo}"
                  ElementStyle="{StaticResource styTextBoxColumnStyleGif}"
                  DataFieldBinding="{Binding Path=ModuleSeqNo, ValidatesOnDataErrors=True}"
            />
            <toolkit:DataGridTextColumn
                  Header="{StaticResource P.PackageName}"
                  ElementStyle="{StaticResource styTextBoxColumnStyleGif}"
                  DataFieldBinding="{Binding Path=PackageName, ValidatesOnDataErrors=True}"
            />
            <toolkit:DataGridCheckBoxColumn Header="{StaticResource P.OutOfSyncYN}" DataFieldBinding="{Binding Path=OutOfSyncYN}" />
            <toolkit:DataGridCheckBoxColumn Header="{StaticResource P.MdbExistsYN}" DataFieldBinding="{Binding Path=MdbExistsYN}" />
            <toolkit:DataGridTextColumn Header="{StaticResource P.TimestampImport}" DataFieldBinding="{Binding Path=TimestampImport, StringFormat=dd.MM.yyyy hh:mm:ss}" />
            <toolkit:DataGridTextColumn Header="{StaticResource P.TimestampMDB}"    DataFieldBinding="{Binding Path=TimestampMDB, StringFormat=dd.MM.yyyy hh:mm:ss}" />

            <toolkit:DataGridTextColumn
                  Header="{StaticResource P.MergeModuleName}"
                  ElementStyle="{StaticResource styTextBoxColumnStyleGif}"
                  DataFieldBinding="{Binding Path=MergeModuleName, ValidatesOnDataErrors=True}"
            />
            <toolkit:DataGridTextColumn
                  Header="{StaticResource P.UserName}"
                  ElementStyle="{StaticResource styTextBoxColumnStyleGif}"
                  DataFieldBinding="{Binding Path=UserName, ValidatesOnDataErrors=True}"
            />
            <toolkit:DataGridCheckBoxColumn Header="{StaticResource P.PrivateYN}" DataFieldBinding="{Binding Path=PrivateYN}" />
         </toolkit:DataGrid.Columns>
      </toolkit:DataGrid>

   </Grid>
</UserControl>


my styles (from application.xaml)
         <Style x:Key="styTextBoxColumnStyleGif"
                TargetType="{x:Type TextBlock}">
            <Style.Triggers>
               <Trigger Property="Validation.HasError" Value="True">
                  <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}">
                  </Setter>
               </Trigger>
            </Style.Triggers>
            <Setter Property="Validation.ErrorTemplate">
               <Setter.Value>
                  <ControlTemplate>
                     <DockPanel LastChildFill="True">
                        <Grid DockPanel.Dock="Right">
                           <Image Name="ValidizorImage"
                                  Stretch="None"
                                  Source="UserControls\ValExcl.gif"
                                  Margin="-17,0,0,0"
                                  ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGridColumn}},
                            Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
                        </Grid>
                       <TextBlock
                                   ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGridColumn}},
                            Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                   Foreground="White"
                                  
                                   Margin="0,0,0,0"
                                   FontWeight="Bold">
                   <TextBlock.Triggers>
                       <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                           <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}" />
                       </EventTrigger>
                   </TextBlock.Triggers>
                        </TextBlock>
                        <Border BorderBrush="Red"
                                BorderThickness="1">
                           <AdornedElementPlaceholder Name="myControl" />
                        </Border>
                     </DockPanel>
                  </ControlTemplate>
               </Setter.Value>
            </Setter>
         </Style>

         <!-- typed styles -->
        
         <Style TargetType="{x:Type toolkit:DataGridCell}">
            <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown" />
         </Style>

         <Style TargetType="{x:Type toolkit:DataGridTextColumn}">
           <Setter Property="DataFieldBinding">
               <Setter.Value>
                  <Binding >
                     <Binding.ValidationRules>
                        <DataErrorValidationRule />
                     </Binding.ValidationRules>
                  </Binding>
               </Setter.Value>
            </Setter>
         </Style>