Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TextBlock text not vertically centering within DataGridCell

I'm creating a DataGrid in C# (from code-behind/not XAML), but no matter what I try, I can't get the text to be vertically center in the data cells:

]

I started off with:

var CellStyle = new Style(typeof(DataGridCell)) {
    Setters = {
        new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Center)
    }
};

Which correctly targets the cell and horizontally centres the text (per the screenshot above).

Trying to vertically center the text, I know that a TextBlock doesn't support vertical content alignment, only its own vertical alignment within a parent element.

Per this question (Text vertical alignment in WPF TextBlock) I tried to fake it using Padding:

var CellStyle = new Style(typeof(DataGridCell)) {
    Setters = {
        new Setter(TextBlock.PaddingProperty, new Thickness(5)),
        new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Center)
    }
};

This made no difference. Then I tried this:

var CellStyle = new Style(typeof(DataGridCell)) {
    Setters = {
        new Setter(DataGridCell.VerticalContentAlignmentProperty, VerticalAlignment.Center),
        new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Center),
        new Setter(TextBlock.VerticalAlignmentProperty, VerticalAlignment.Center)
    }
};

Which resulted in:

Adding new Setter(DataGridCell.HeightProperty, 50d), results in screenshot #1.

How can I vertically center the text in my data cells?

like image 814
Danny Beckett Avatar asked Aug 26 '14 14:08

Danny Beckett


2 Answers

Using Blend for Visual Studio, we have this style for the DataGridCell:

<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridCell}">
                <Border BorderBrush="{TemplateBinding BorderBrush}" 
                        BorderThickness="{TemplateBinding BorderThickness}" 
                        Background="{TemplateBinding Background}" 
                        SnapsToDevicePixels="True"                          
                >
                    <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
</Setter>

So looks like there is not any default support for changing the alignments here. Normally the <ContentPresenter> should have code like this:

<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>

Then we can change the VerticalContentAlignment and HorizontalContentAlignment in the style of the DataGridCell to change the alignments.

That means if using XAML code, your solution can be solved by just appending the above code. But if you want to use code behind, it's of course much longer and more complicated.

Here I introduce to you 2 solutions. First by building the VisualTree for the ControlTemplate and set that template for the Template property of DataGridCell:

//root visual of the ControlTemplate for DataGridCell is a Border
var border = new FrameworkElementFactory(typeof(Border));
border.SetBinding(Border.BorderBrushProperty, new Binding("BorderBrush") { 
        RelativeSource = RelativeSource.TemplatedParent
});
border.SetBinding(Border.BackgroundProperty, new Binding("Background") {RelativeSource = RelativeSource.TemplatedParent });
border.SetBinding(Border.BorderThicknessProperty, new Binding("BorderThickness") {RelativeSource = RelativeSource.TemplatedParent });
border.SetValue(SnapsToDevicePixelsProperty, true);
//the only child visual of the border is the ContentPresenter
var contentPresenter = new FrameworkElementFactory(typeof(ContentPresenter));
contentPresenter.SetBinding(SnapsToDevicePixelsProperty, new Binding("SnapsToDevicePixelsProperty") {RelativeSource=RelativeSource.TemplatedParent });
contentPresenter.SetBinding(VerticalAlignmentProperty, new Binding("VerticalContentAlignment") { RelativeSource = RelativeSource.TemplatedParent });
contentPresenter.SetBinding(HorizontalAlignmentProperty, new Binding("HorizontalContentAlignment") {RelativeSource = RelativeSource.TemplatedParent });
//add the child visual to the root visual
border.AppendChild(contentPresenter);

//here is the instance of ControlTemplate for DataGridCell
var template = new ControlTemplate(typeof(DataGridCell));
template.VisualTree = border;
//define the style
var style = new Style(typeof(DataGridCell));
style.Setters.Add(new Setter(TemplateProperty, template));
style.Setters.Add(new Setter(VerticalContentAlignmentProperty, 
                             VerticalAlignment.Center));
style.Setters.Add(new Setter(HorizontalContentAlignmentProperty, 
                             HorizontalAlignment.Center));
yourDataGrid.CellStyle = style;

The second solution is by using XamlReader to parse the XAML code directly, that means we need the exact XAML code as given before saved in a string and XamlReader will parse that string giving out an instance of Style:

var xaml = "<Style TargetType=\"{x:Type DataGridCell}\"><Setter Property=\"VerticalContentAlignment\" Value=\"Center\"/>" +
           "<Setter Property=\"HorizontalContentAlignment\" Value=\"Center\"/>" +
           "<Setter Property=\"Template\">" +
           "<Setter.Value><ControlTemplate TargetType=\"DataGridCell\">" +
           "<Border BorderBrush=\"{TemplateBinding BorderBrush}\" BorderThickness=\"{TemplateBinding BorderThickness}\" Background=\"{TemplateBinding Background}\" SnapsToDevicePixels=\"True\">" +
           "<ContentPresenter SnapsToDevicePixels=\"{TemplateBinding SnapsToDevicePixels}\" VerticalAlignment=\"{TemplateBinding VerticalContentAlignment}\" HorizontalAlignment=\"{TemplateBinding HorizontalContentAlignment}\"/>" +
           "</Border></ControlTemplate></Setter.Value></Setter></Style>";

var parserContext = new System.Windows.Markup.ParserContext();          
parserContext.XmlnsDictionary
             .Add("","http://schemas.microsoft.com/winfx/2006/xaml/presentation");
parserContext.XmlnsDictionary
             .Add("x","http://schemas.microsoft.com/winfx/2006/xaml");            
yourDataGrid.CellStyle = (Style)System.Windows.Markup.XamlReader.Parse(xaml,parserContext); 

You can see that both solutions are fairly long but they are actually what you should do using code behind. That means we should always use XAML code as much as possible. Many features in WPF are designed mainly for XAML code, so using code behind is of course not straightforward and usually verbose.

NOTE: The XAML code I posted at the beginning is not the full default style for DataGridCell, it has some more Triggers. That means the code may be much longer, sorry, here is the full default XAML code:

<Style TargetType="{x:Type DataGridCell}">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderBrush" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridCell}">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True"                            
                >
                    <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsSelected" Value="True">
            <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
            <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
        </Trigger>
        <Trigger Property="IsKeyboardFocusWithin" Value="True">
            <Setter Property="BorderBrush" Value="{DynamicResource {x:Static DataGrid.FocusBorderBrushKey}}"/>
        </Trigger>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsSelected" Value="true"/>
                <Condition Property="Selector.IsSelectionActive" Value="false"/>
            </MultiTrigger.Conditions>
            <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
            <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
        </MultiTrigger>
        <Trigger Property="IsEnabled" Value="false">
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
        </Trigger>
    </Style.Triggers>
</Style>

However I've just tested it, looks like the default style is always applied to the DataGridCell, it's just overridden by the Setter you added (which set the same properties). Here is the testing code, the Trigger still works:

//change the highlight selected brush to Red (default by blue).
yourDataGrid.Resources.Add(SystemColors.HighlightBrushKey, Brushes.Red);
like image 138
King King Avatar answered Oct 21 '22 07:10

King King


My answer just summarizes King King's, in case it helps someone. In XAML:

Use the property CellStyle="{StaticResource CustomCell}" in your DataGrid where

<Style x:Key="CustomCell" TargetType="{x:Type DataGridCell}">
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridCell}">
                <ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
like image 31
James Hirschorn Avatar answered Oct 21 '22 08:10

James Hirschorn