Sam Brinsted
Sam Brinsted

Reputation: 41

WPF datagrid column heading span more than once column

In a WPF datagrid is it possible to group column headings?

What I'm after is

| Column 1 | Column 2 | Column 3|
| a  b  c  | a  b  c  | a  b  c |
| z  x  y  | z  x  y  | z  x  y |

I've searched around and can't see an obvious way of doing this. I could use a templated column and then mimick the extra cells within the each template but that wouldn't work well for ordering etc.

I suppose all I'm saying it that I'm looking for is how people have managed to span column headings across multiple coluns.

Any help or ideas would be greatly appreciated.

Upvotes: 4

Views: 20450

Answers (6)

Erik
Erik

Reputation: 954

Here is how I did it, entirely in XAML.

The width of the GD Column determins the width of each of the 4 columns, the UniformGrid sees to it that the columns are equal width. The TextBoxes stretch to full available width.

The coloring of the DG Cell (=4 TextBoxes) is replaced by the coloring (MouseOver) of the individual textboxes.

<DataGridTemplateColumn Width=200>
    <DataGridTemplateColumn.CellStyle>
        <Style/> <!-- Disable previously set style -->
    </DataGridTemplateColumn.CellStyle>
    <DataGridTemplateColumn.Header>
        <StackPanel>
            <TextBlock  
                IsEnabled="False"
                Style="{StaticResource TextBlockDataGridHeader}">
                <Run>Windspeeds in area</Run>
            </TextBlock>
            <UniformGrid
                HorizontalAlignment="Stretch"
                Columns="4">
                <Label Grid.Column="0" Content="1"/>
                <Label Grid.Column="1" Content="2"/>
                <Label Grid.Column="2" Content="3"/>
                <Label Grid.Column="3" Content="4"/>
            </UniformGrid>
            <TextBlock           
                Style="{StaticResource TextBlockDataGridHeader}"
                Foreground="Red"
                Text="m/s"/>

        </StackPanel>
    </DataGridTemplateColumn.Header>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <UniformGrid
                HorizontalAlignment="Stretch"
                Columns="4">
                <UniformGrid.Resources>
                    <Style TargetType="TextBox">
                        <Setter Property="Padding" Value="3,0,3,0"/>
                        <!-- Let row background shine through -->
                        <Setter Property="Background" Value="Transparent"/>
                        <Setter Property="Foreground" Value="White"/>
                        <!-- 'Cells' have no border -->
                        <Setter Property="BorderThickness" Value="0"/>
                        <Style.Triggers>
                            <!-- Flip colors on mouseOver -->
                            <Trigger Property="IsMouseOver" Value="True">
                               <Setter Property="Background" Value="White" />
                               <Setter Property="Foreground" Value="Black" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </UniformGrid.Resources>                  
                <TextBox 
                    Grid.Column="0"         
                    Text="{Binding VRef0[0],Mode=TwoWay, UpdateSourceTrigger=LostFocus,StringFormat=N1}"/>
                <TextBox 
                    Grid.Column="1"
                    Text="{Binding VRef0[1],Mode=TwoWay, UpdateSourceTrigger=LostFocus,StringFormat=N1}"/>
                <TextBox 
                    Grid.Column="2"
                    Text="{Binding VRef0[2],Mode=TwoWay, UpdateSourceTrigger=LostFocus,StringFormat=N1}"/>
                <TextBox 
                    Grid.Column="3"
                    Text="{Binding VRef0[3],Mode=TwoWay, UpdateSourceTrigger=LostFocus,StringFormat=N1}"/>
            </UniformGrid>
        </DataTemplate>                        
    </DataGridTemplateColumn.CellTemplate> 
</DataGridTemplateColumn>

Upvotes: 0

MarcBalta
MarcBalta

Reputation: 131

This is an old post and I liked D'Hags answere. Unfortunatley it didn't satisfy my reqiurement entirely. Hence I extended D'Hags solution a bit which allows now:

  • column spans are possible in an arbitrary manner and not only the entire row
  • column span is defined by using object references. When column 2 and column 3 are databinding to the same object reference then a span is rendered
  • the column span should behave exaclty like the others columns, so that for instance selections are possible.

The solution goes like this:

A Datagrid with the a style:

    <DataGrid  x:Name="dataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False">
        <DataGrid.Resources>
            <Style TargetType="DataGridRow">

                        <Setter Property="ItemsPanel" >
                            <Setter.Value>
                                <ItemsPanelTemplate>
                                    <local:DataGridSpannedCellPanel ></local:DataGridSpannedCellPanel>

                                </ItemsPanelTemplate>
                            </Setter.Value>
                        </Setter>

            </Style>
        </DataGrid.Resources>

    </DataGrid>

That sets the following class to be used instead of using DataGridCellsPanel directly:

public class DataGridSpannedCellPanel : DataGridCellsPanel
{
    protected override Size ArrangeOverride(Size arrangeSize)
    {
        if (DataContext is IEnumerable)
        {
            base.ArrangeOverride(arrangeSize);

            IEnumerable<Object> data = ((IEnumerable)DataContext).Cast<Object>();
            double totalPreviousWidth = 0;
            double totalPos = 0;


            List<int> columnSize = new List<int>();
            double currentSize = 0;


            for (int i = 0; i < data.Count(); ++i)
            {
                Object el = data.ElementAt(i);
                Object nextEl = null;

                UIElement uiel = InternalChildren[i];

                if (data.Count() > i + 1)
                {
                    nextEl = data.ElementAt(i + 1);
                }


                if (Object.ReferenceEquals(el, nextEl) && el != null)
                {
                    totalPreviousWidth += uiel.RenderSize.Width;
                    uiel.Arrange(new Rect(new Point(0, 0), new Size(0, 0)));
                }
                else
                {
                    if (totalPreviousWidth > 0)
                    {
                        uiel.Arrange(new Rect(new Point(totalPos, 0),
                    new Size(totalPreviousWidth + uiel.RenderSize.Width, uiel.RenderSize.Height))

                     );
                        currentSize = totalPreviousWidth + uiel.RenderSize.Width;
                    }

                    totalPos += uiel.RenderSize.Width;

                    totalPreviousWidth = 0;
                }
            }

            return arrangeSize;
        }
        else
        {
            return base.ArrangeOverride(arrangeSize);
        }

    }
 }
}

And it used in the following way:

public partial class MainWindow : Window
{

    void AddColumn(DataGrid dg, int i)
    {

        var col = new DataGridTextColumn();
        col.Header = (char)('A' + i);
        col.Binding = new Binding("[" + i + "]");

        dg.Columns.Add(col);

    }

    public MainWindow()
    {
        InitializeComponent();

        for (int i = 0; i < 10; ++i)
        {
            AddColumn(dataGrid, i);
        }

        List<object> rows = new List<object>();

        String[] txtHeader = new string[7];
        String multiSpan = "MergedColumn";
        txtHeader[0] = "Col1";
        txtHeader[1] = "Col2";
        txtHeader[2] = "Col3";
        // these columns should be merged to one, which is indicated by assigning 
        // the same reference to all columns.
        txtHeader[3] = multiSpan;
        txtHeader[4] = multiSpan;
        txtHeader[5] = multiSpan;

        int[] intArr = new int[10];
        for (int i = 0; i < 10; i++)
        {
            intArr[i] = i;
        }

        rows.Add(txtHeader);
        rows.Add(intArr);

        dataGrid.ItemsSource = rows;
    }
}
}

Upvotes: 0

D&#39;Hag
D&#39;Hag

Reputation: 601

This is an old thread, but I thought I should share how I did it.

In my application, I want to display three columns of date entries, under a single column header, "Maintenance Fee Dates." I created a single column, with two DataTemplates, one for display and one for editing:

<DataGrid.Resources>
  <DataTemplate x:Key="cellTemplate">
     <Grid>
        <Grid.ColumnDefinitions>
           <ColumnDefinition Width="100" />
           <ColumnDefinition Width="100" />
           <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        <TextBlock x:Name="tbDate1"
                    Text="{Binding Path=Date1}"
                    Grid.Column="0" />
        <TextBlock x:Name="tbDate2"
                    Text="{Binding Path=Date2}"
                    Grid.Column="1" />
        <TextBlock x:Name="tbDate3"
                    Text="{Binding Path=Date3}"
                    Grid.Column="2" />
     </Grid>
  </DataTemplate>
  <DataTemplate x:Key="cellEditingTemplate">
     <Grid>
        <Grid.ColumnDefinitions>
           <ColumnDefinition Width="100" />
           <ColumnDefinition Width="100" />
           <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        <DatePicker Grid.Column="0"
                    Margin="0"
                    Name="dpDate1"
                    Width="100"
                    SelectedDate="{Binding Path=Date1}" />
        <DatePicker Grid.Column="1"
                    Margin="0"
                    Name="dpDate2"
                    Width="100"
                    SelectedDate="{Binding Path=Date2}" />
        <DatePicker Grid.Column="2"
                    Margin="0"
                    Name="dpDate3"
                    Width="100"
                    SelectedDate="{Binding Path=Date3}" />
     </Grid>
  </DataTemplate>

Then I define the column as a DataGridTemplateColumn, pointing at the DataTemplates above:

<DataGrid.Columns>
....
   <DataGridTemplateColumn CellTemplate="{StaticResource cellTemplate}"
                           Header="Maintenance Fee Dates"
                           CellEditingTemplate="{StaticResource cellEditingTemplate}" />
....
</DataGrid.Columns>

Since the DataTemplate is laid out with a Grid that has three fixed-length columns, I get three nice columns of dates (or DatePickers when editing) under the single column header.

Horizontal Gridlines can be handled by the Grid. To have vertical Gridlines between the three columns, just put the middle column's control in a Border control. Set the Border control to the same width as the Grid column, display only its right and left borders, and set its BorderBrush to match the color of the DataGrid's Gridlines:

  <DataTemplate x:Key="cellTemplate">
     <Grid>
        <Grid.ColumnDefinitions>
           <ColumnDefinition Width="100" />
           <ColumnDefinition Width="100" />
           <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        <TextBlock x:Name="tbDate1"
                    Text="{Binding Path=Date1}"
                    Grid.Column="0" />
        <Border BorderThickness="1,0,1,0"
                BorderBrush="DarkGray"
                Width="100">
           <Border.Child>
              <TextBlock x:Name="tbDate2"
                          Text="{Binding Path=Date2}"
                          Grid.Column="1" />
           </Border.Child>
        </Border>
        <TextBlock x:Name="tbDate3"
                    Text="{Binding Path=Date3}"
                    Grid.Column="2" />
     </Grid>
  </DataTemplate>

Upvotes: 6

Rachel
Rachel

Reputation: 132588

This question is a bit old, but it was one of the first google results that came up when I was looking for a solution to this problem, and I don't like either answer posted here. So here's a simple alternative using a custom Header and ClipToBounds=False.

<DataGridTextColumn.Header>
    <StackPanel Orientation="Horizontal">
        <TextBlock x:Name="HeightSpacer" Text="P" FontWeight="Bold" />
        <Canvas Height="{Binding ElementName=HeightSpacer, Path=ActualHeight}">
            <TextBlock Text="hone Numbers" FontWeight="Bold" ClipToBounds="False" />
        </Canvas>
    </StackPanel>
</DataGridTextColumn.Header>

The only thing you need to do is ensure that the combined width of whatever columns this header is spamming is at least the width of the header column.

The TextBlock that contains the first character of the header text and Canvas.Height binding is used to block out the height needed for the header. It is only needed if your full datagrid header does not have an element that defines the header height, or if the header spamming multiple columns is bigger than the rest of them (which was my case... this was the only header column with bold text)

Upvotes: 2

ADurkin
ADurkin

Reputation: 11

You might be better off removing the column headers and adding in your own outside the grid. There is a good post here that shows how to make a "header" that spans multiple columns.

Upvotes: 1

majocha
majocha

Reputation: 1191

Make a custom HeaderTemplate for your columns.

    <DataGrid>
        <DataGrid.Columns>
            <DataGridTextColumn>
                <DataGridTextColumn.HeaderTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock>Column 1</TextBlock>
                            <TextBlock>xyz</TextBlock>
                        </StackPanel>
                    </DataTemplate>
                </DataGridTextColumn.HeaderTemplate>
            </DataGridTextColumn>
            <DataGridTextColumn Header="Header" />
        </DataGrid.Columns>
    </DataGrid>

Upvotes: 1

Related Questions