GridViewColumnHeader not spanning whole ListView width - wpf

I am having trouble with the layout of a ListView / GridView combination in my WPF project. The columnheaders are just not spanning the whole width of the parent ListView. I tried to find documentation everywhere but didn't find any clues about the underlying containers / control templates.
I guess the standard control template of the header contains something that I am not taking care of, I just cannot wrap my head around it.
The (simplified) ListView has a few pixel space to the left and to the right of the row containing the headers, where the background of the parent ListView is visible:
https://i.imgur.com/XijV88a.jpg
Question: How can I make the columnheaders span the whole width of the ListView and make the few pixels space disappear?
My XAML:
<ListView Padding="0"
BorderThickness="0"
Background="Red">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="BorderThickness" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="Black" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn Header="1" Width="40"/>
<GridViewColumn Header="2" Width="40"/>
<GridViewColumn Header="3" Width="40"/>
</GridView>
</ListView.View>
<ListViewItem> 1 </ListViewItem>
<ListViewItem> 2 </ListViewItem>
</ListView>
The only way so far to achieve the look I am going for is to change the background of the listview to the same color as the headers - there has to be a better way, though. Any help is appreciated!

This column does not support "*" as width argument so you cannot easily say - fill the rest...
What you should do is hook up on SizeChanged event of the ListView and there do the calculations. So for instance, in your case, if we wanted first two columns to take 40 each and the third column to take the rest you would do it like so:
private void ListView_SizeChanged(object sender, SizeChangedEventArgs e)
{
ListView listView = sender as ListView;
GridView gView = listView.View as GridView;
var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth;
gView.Columns[0].Width = 40;
gView.Columns[1].Width = 40;
gView.Columns[2].Width = workingWidth - 80;
}
EDIT
For the gaps(to the left and to the right) in the header row you want to alter the ColumnHeaderContainerStyle like so:
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="Black" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="-2,0,-2,0" />
<Setter Property="Padding" Value="0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</GridView.ColumnHeaderContainerStyle>
So make sure you just reduce the margin by -2 on each side.

Related

Change colour of single cell in DataGrid when selected, overriding current colour

I have this code which affects the whole row of a DataGrid:
<DataGrid x:Name="tblopenRequests" ItemsSource="{Binding}" Grid.Column="1" Grid.Row="2"
IsReadOnly="true" RowHeaderWidth="0" AutoGenerateColumns="False" CanUserAddRows="False" SelectionMode="Single">
<DataGrid.Resources>
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}" >
<Setter Property="Background" Value="LightSeaGreen" />
<Setter Property="Foreground" Value="white" />
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1 1 1 1"/>
<Setter Property="Margin" Value="-1,-1,0,0" />
<Setter Property="Height" Value="28" />
<Setter Property="Width" Value="auto"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
<Style TargetType="DataGridCell">
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Style.Triggers>
<Trigger Property="DataGridCell.IsSelected" Value="True">
<Setter Property="Background" Value="Goldenrod" />
<Setter Property="BorderBrush" Value="white" />
<Setter Property="Foreground" Value="black" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="15"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
I now want to make ONE cell in the row be overridden with a different format to stand out more.
This is all I have at the moment but I keep failing in my attempts.
<DataGridTextColumn Binding="{Binding expectedDate}" Header="Expected Date" Width="90">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
I have tried adding atrigger in here and colouring the cell only. However, this didn't do anything, just kept the orange from the above code in the resources section.
I just need this one cell pink/light red instead of orange but I can't find a site or forum article with this answer.
Thanks to #thatguy. The upper apporach helped me a lot. It's worth to mention that this is one of many answers on stackoverflow and the web which works with AutoGenerateColumns="True".
Conditional colors can also be done in code behind with:
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Column.Header, RelativeSource={RelativeSource Self}}" Value="Expected Date">
<Setter Property="Background" Value="{Binding SomeColor}" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
And for example:
public SolidColorBrush SomeColor
{
get
{
return IsDifferent ? new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 255, 139, 0)) : new SolidColorBrush(System.Windows.Media.Color.FromArgb(139, 255, 139, 0));
}
}
Single Cell Style With Bindings
In your cell style, you can refer to the associated column and identify it e.g. by comparing its Header.
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<!-- ...other setters. -->
<Style.Triggers>
<!-- ...other triggers. -->
<DataTrigger Binding="{Binding Column.Header, RelativeSource={RelativeSource Self}}" Value="Expected Date">
<Setter Property="Background" Value="Pink" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
Depending on the state in which the color should be changed, you might need to use a MultiDataTrigger for example to change the color only in selected state.
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True" />
<Condition Binding="{Binding Column.Header, RelativeSource={RelativeSource Self}}" Value="Expected Date" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Pink" />
</MultiDataTrigger>
Using the Header to identify a column is cumbersome and error prone. Unfortunately there is no built-in unique identifier on columns that you can use. However, you can create an attached property.
public static class DataGridColumnProperties
{
public static readonly DependencyProperty IdentifierProperty = DependencyProperty.RegisterAttached("Identifier",
typeof(string), typeof(DataGridColumnProperties), new PropertyMetadata(null));
public static string GetIdentifier(DependencyObject dependencyObject)
{
return (string)dependencyObject.GetValue(IdentifierProperty);
}
public static void SetIdentifier(DependencyObject dependencyObject, string value)
{
dependencyObject.SetValue(IdentifierProperty, value);
}
}
Using this attached property, you can set an identifier on your column.
<DataGridTextColumn local:DataGridColumnProperties.Identifier="ExpectedDate" ...>
You would refer to it in the style like this:
<DataTrigger Binding="{Binding Column.(local:DataGridColumnProperties.Identifier), RelativeSource={RelativeSource Self}}" Value="ExpectedDate">
<Setter Property="Background" Value="Pink" />
</DataTrigger>
This approach keeps your column definitions more robust, as styles are independent of the Header.
Multiple Cell Styles
If you prefer to use multiple cell styles, you can solve your issue without bindings. In the following, I have created all styles within the DataGrid, but you can move them to any resource dictionary.
Define a regular cell style that applies to most columns, here RegularCellStyle. This is the style from your question. Then, create a special style for the first column, here SpecialCellStyle.
This style uses the BasedOn attribute to inherit all setters and triggers from the regular style. In this style you only define what changes compared to the base style. In your case it is only the Background in selected state, so we add a trigger for it.
Next, we apply the regular style to the DataGrids CellStyle property. This style will be applied to all columns. Then we apply the special style to the CellStyle of the first column. The column cell style will take precedence. From the documentation:
A Style can be applied to a cell at the table, column, or cell level. To apply a Style to all cells in a column, set the DataGridColumn.CellStyle property. This will take precedence over the DataGrid.CellStyle property. To apply a Style to an individual cell, set the Style property directly on the DataGridCell. This will take precedence over all other styles applied to the cell.
Below is the complete code for your sample.
<DataGrid x:Name="tblopenRequests" ItemsSource="{Binding}" Grid.Column="1" Grid.Row="2"
IsReadOnly="true" RowHeaderWidth="0" AutoGenerateColumns="False" CanUserAddRows="False" SelectionMode="Single">
<DataGrid.Resources>
<!-- Style from your question. -->
<Style x:Key="RegularCellStyle" TargetType="DataGridCell">
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Style.Triggers>
<Trigger Property="DataGridCell.IsSelected" Value="True">
<Setter Property="Background" Value="Goldenrod" />
<Setter Property="BorderBrush" Value="white" />
<Setter Property="Foreground" Value="black" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="15" />
</Trigger>
</Style.Triggers>
</Style>
<!-- Special first column style based on the regular style. -->
<Style x:Key="SpecialCellStyle"
BasedOn="{StaticResource RegularCellStyle}"
TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="DataGridCell.IsSelected" Value="True">
<Setter Property="Background" Value="Pink" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.CellStyle>
<StaticResource ResourceKey="RegularCellStyle" />
</DataGrid.CellStyle>
<DataGrid.ColumnHeaderStyle>
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="LightSeaGreen" />
<Setter Property="Foreground" Value="white" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="1,1,1,1" />
<Setter Property="Margin" Value="-1,-1,0,0" />
<Setter Property="Height" Value="28" />
<Setter Property="Width" Value="auto" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding expectedDate}"
Header="Expected Date"
Width="90"
CellStyle="{StaticResource SpecialCellStyle}"/>
<!-- ...other columns WITHOUT cell style will apply the regular style. -->
</DataGrid.Columns>
</DataGrid>

First item custom style border in ListView

How can I set different style for first item in ListView? In my case, I want to change first item border, to get GUI like this:
My current code (no top border):
<ListView
ItemsSource="{Binding MyData}">
<ListView.ItemContainerStyle>
<Setter Property="BorderThickness" Value="0,0,0,1" />
</ListView.ItemContainerStyle>
</ListView>
There is a very simple solution. You don't have to write custom converters etc. Use PreviousData in RelativeSource
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
<Setter Property="BorderThickness" Value="0,1,0,1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>

Can I apply a WPF style to an element only in a certain layout?

I have a TextBlock style like this:
<Style TargetType="TextBlock" x:Key="FormLabel">
<Setter Property="Height" Value="20" />
<Setter Property="Margin" Value="10" />
<Setter Property="TextAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
I use it in Grid based forms, e.g:
<TextBlock Text="Code" Grid.Row="1" Grid.Column="0" Style="{StaticResource FormLabel}" />
Now instead of repeating the style name on every TextBlock in the grid, I would prefer to e.g. have a Grid style like:
<Style TargetType="Grid" x:Key="FormGrid">
<Setter Property="Width" Value="400" />
...
</Style>
Then I would, if possible, like to modify my TextBlock style to only apply to that element when it is a child element of a Grid with style FormGrid.
Is this possible, and if so, how can I achieve it?
This is indeed possible by using an implicit style within another style as a resource. Take this example:
...
<Window.Resources>
<Style x:Key="FormGrid" TargetType="Grid">
<Style.Resources>
<Style TargetType="TextBlock">
<Setter Property="Height" Value="20" />
<Setter Property="Margin" Value="10" />
<Setter Property="TextAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Style.Resources>
<Setter Property="Width" Value="400" />
</Style>
</Window.Resources>
<StackPanel>
<Grid Style="{StaticResource FormGrid}">
<TextBlock Text="This text block is styled with FormGrid TextBlock implicit style."/>
</Grid>
<TextBlock Text="This text block uses the default style."/>
</StackPanel>
...
This is not possible by out of the box WPF abilities. What you are looking here is CSS like style selectors. WPF Only allows style inheritance through BasedOn property. I am not sure if this could be an alternative, but you can define that specific TextBlock style as part of that grid resources and target to match any textblock inside of it.
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Height" Value="20" />
<Setter Property="Margin" Value="10" />
<Setter Property="TextAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Grid.Resources>

WPF: Button not taking the full space in a Listbox

I've a ListBox, in which I will have some Button added by prism.
For now, I've the following code(with some dummy button here just for testing purpose:
<DockPanel LastChildFill="True">
<ListBox HorizontalContentAlignment="Stretch" Padding="0" Margin="0" BorderBrush="Black" DockPanel.Dock="Left" HorizontalAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Resources>
<Style TargetType="Button">
<Setter Property="Height" Value="60" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
</ListBox.Resources>
<Button>Button 1</Button>
<Button >Button 2</Button>
</ListBox>
<ContentControl></ContentControl>
</DockPanel>
The issue that I'm having currently is that my buttons are not taking the full space:
How to make sure it use all the available space?
Is there some way to ensure that all buttons will be have the height = to the width?
(just to be clear, I don't want anything set on the button since they will be provided by Prism from different modules)
When I run the application and focus an object, it seems we see that the ListBoxItemis taking the full place but the Button inside isn't:
How to make sure it use all the available space?
This is due to how the ListBox control's template is designed. To remove the small margin at the left and right of the button, set Padding=0 for the ListBoxItem style.
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0" />
</Style>
</ListBox.ItemContainerStyle>
Is there some way to ensure that all buttons will be have the height = to the width?
You could do a RelativeSource binding to bind the container's height to the actual width of the parent ListBox:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource AncestorType=ListBox},Path=ActualWidth}" />
</Style>
</ListBox.ItemContainerStyle>

WPF DataGrid DataGridRow Selection Border and Spacing

I'm using the WPF DataGrid to display data and when the user selects a row, I'd like the background of the entire row to be highlighted (with a gradient) and also to have a border. I've been using the following code, which works for the most part:
<Style TargetType="DataGridRow">
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked}" Value="True">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{StaticResource BorderColor}" />
<Setter Property="Background" Value="{StaticResource BackgroundColor}" />
</DataTrigger>
</Style.Triggers>
</Style>
This issue I'm having is with the border. If the BorderThickness is initially set to 0, then the entire Row "shifts over" to make space for the border when the DataTrigger is triggered. If I set the BorderThickness to 1 initially, then the highlighted Row is displayed correctly, but there is an empty border around the Row when it's in it's default state, causing the Row gridlines not to touch the edge.
Any ideas on how I could work around this?
I've found that tweaking visuals is much easier with ListBox instead of DataGrid so maybe that could be one way to go.
Try this as a starting point:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked}" Value="True">
<Setter Property="BorderBrush" Value="{StaticResource BorderColor}" />
<Setter Property="Background" Value="{StaticResource BackgroundColor}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:MyClass}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsChecked}" />
<TextBlock Text="{Binding MyTextProperty}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This should set the correct border and background without moving elements of a selected row or showing surrounding gaps for unselected ones.
Just replace local:MyClass with your class and customize the Grid contents for your scenario.
See this answer on how to deal with MouseOver/Selected styles, I tried copying the template property setter into my example above and adjusting the Panel.Background and Border.BorderBrush setters which seems to work well.

Resources