Search code examples
wpfxamlwidthpixelcentering

WPF centered text in stretched label fixed width


My goal is simpel, however i can't figure it out. Sorry for the Titel but couldn't come up with a better explanation...

I have usercontrol with an label that display's the current time (hooked up to a timer with 1 second interval). The label is the width of its parent and the text is aligned in the center. The format is DateTime.ToString("HH : mm : ss"), the FontFamily and size can be adjusted by the user. So far nothing strange... But, the text is aligned centered so when time is lets say 12:34:02 the pixel width different than 12:34:11. (of course depending on the font) This causes the label jump (because it auto centers itself)

The code below is an example of it. the canvas is used to draw stuff on it and the viewbox is used so it autosizes itself in his parent.

Code:

 <Grid>
    <Viewbox>
        <Canvas Name="canv" Height="300" Width="300">
            <StackPanel Name="stckpnlDateTime">

                <Label Name="lblDateOrText" 
                       Grid.Column="0"
                       Grid.Row="0"
                       Content = "------" 
                       FontSize="25"
                       Foreground="GhostWhite"
                       HorizontalContentAlignment="Center"
                       VerticalAlignment="Bottom"
                       FontFamily="Arial"
                       Width="Auto"/>

                <Label Name="lblTime"
                       Grid.Column="0"
                       Grid.Row="1"
                       Content = "-- : -- : --"
                       FontSize="25"
                       Foreground="GhostWhite"
                       HorizontalContentAlignment="Center"
                       VerticalAlignment="Top"
                       FontFamily="DS-Digital"
                       Width="Auto"/>
            </StackPanel>                
        </Canvas>
    </Viewbox>
</Grid>

Private Sub SystemTime_Tick(sender As Object, e As EventArgs) Handles tmrSystemTime.Tick
    lblTime.Content = Now.ToString("HH : mm : ss")
End Sub

So i tried a different approach, great a grid with 10 columns and 8 labels, one for each char, and stretch the labels to its parent (cell). This works and keeps the chars on a fixed position. But the width of last column is smaller then the rest... In this image you can see hat i mean, the second purple column is what i mean. Example alignment

Code:

 <UserControl.Resources>
    <Style x:Key="LabelStyle" TargetType="Label">
        <Setter Property="Foreground" Value="White" />
        <Setter Property="FontFamily" Value="DS-Digital" />
        <Setter Property="FontSize" Value="40"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="HorizontalContentAlignment" Value="Right"/>
        <Setter Property="Background" Value="Green" />
    </Style>
</UserControl.Resources>

<Grid HorizontalAlignment="Stretch">
    <Viewbox HorizontalAlignment="Stretch">
        <Canvas Name="canv" Height="300" Width="300" HorizontalAlignment="Stretch">
            <StackPanel Name="stckpnlDateTime" HorizontalAlignment="Stretch">                    
                <Label Name="lblDateOrText" 
                       Grid.Column="0"
                       Grid.Row="0"
                       Content = "" 
                       FontSize="25"
                       Foreground="GhostWhite"
                       HorizontalContentAlignment="Center"
                       VerticalAlignment="Bottom"
                       FontFamily="Arial"
                       Width="Auto"/>

                <Grid Name="GridTimeLabel" HorizontalAlignment="Stretch"  Width="Auto" Grid.Column="0" Grid.Row="1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="1*"/>
                    </Grid.ColumnDefinitions>


                    <Label Background="Purple" Grid.Column="0" Grid.Row="0"/>
                    <Label Name="lblTime1" Grid.Column="1" Grid.Row="0" Style="{StaticResource LabelStyle}" Content="-"/>
                    <Label Name="lblTime2" Grid.Column="2" Grid.Row="0" Style="{StaticResource LabelStyle}" Content="-"/>
                    <Label Name="lblTime3" Grid.Column="3" Grid.Row="0" Style="{StaticResource LabelStyle}" Content=":"/>
                    <Label Name="lblTime4" Grid.Column="4" Grid.Row="0" Style="{StaticResource LabelStyle}" Content="-"/>
                    <Label Name="lblTime5" Grid.Column="5" Grid.Row="0" Style="{StaticResource LabelStyle}" Content="-"/>
                    <Label Name="lblTime6" Grid.Column="6" Grid.Row="0" Style="{StaticResource LabelStyle}" Content=":"/>
                    <Label Name="lblTime7" Grid.Column="7" Grid.Row="0" Style="{StaticResource LabelStyle}" Content="-"/>
                    <Label Name="lblTime8" Grid.Column="8" Grid.Row="0" Style="{StaticResource LabelStyle}" Content="-"/>
                    <Label Background="Purple" Grid.Column="9" Grid.Row="0"/>
                </Grid>                 
            </StackPanel>                
        </Canvas>
    </Viewbox>
</Grid>

Long story short, i'm stuk.... hopefully someone could point me in the right direction.


Solution

  • @Jai, thanks for pointing me in the direction of the .Measure() Sub. After fiddling around i ended up with the 3 column grid, measuring the size of the label with the new content, setting the size of the label. This causes the Column to realign, which holds the label in place.

    The code: (created new WPF program for the test, The colors are to see the difference between child and parent)

        <Grid Background="Tomato">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
    
        <Label Name="lblTime"
                       Grid.Column="1"
                       Grid.Row="0"
                       Content = "-- : -- : --"
                       FontSize="50"
                       Foreground="Black"
                       Background="Beige"
                       HorizontalContentAlignment="Left"
                       VerticalAlignment="Center"
                       FontFamily="DS-Digital"/>
    </Grid>
    

    And the code behind it:

    Class MainWindow

    ''' <summary>
    ''' Timer for updating the time Clock
    ''' </summary>
    Dim WithEvents tmrSystemTime As New DispatcherTimer With {.Interval = TimeSpan.FromSeconds(1)} 'Set Timer interval on every second.
    
    Sub New()
    
        ' This call is required by the designer.
        InitializeComponent()
    
        ' Add any initialization after the InitializeComponent() call.
        tmrSystemTime.Start()
    End Sub
    
    Private Sub SystemTime_Tick(sender As Object, e As EventArgs) Handles tmrSystemTime.Tick
        'Set the time in the label
        lblTime.Content = Now.ToString("HH : mm : ss")
    
        'Measure and set the size of the label
        MeasureSizeTimeLabel()
    End Sub
    
    ''' <summary>
    ''' Measure the Max Size of the label with a specific Format
    ''' </summary>
    Private Sub MeasureSizeTimeLabel()
    
        'Store the Max size of the Time Label in this variable
        Dim MaxClockSize As Size
    
        'Measure the Max size of the clock label and use this width
        'lblTime.Content = "00 : 00 : 00"
        lblTime.Measure(New Size(Double.PositiveInfinity, Double.PositiveInfinity))
        MaxClockSize = lblTime.DesiredSize
    
        'Now Set the size of the label
        lblTime.Width = MaxClockSize.Width
        lblTime.Height = MaxClockSize.Height
    End Sub
    

    Thanks all for the help, appreciate the effort and time! :-)