Reputation: 41
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
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
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:
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
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
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
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
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