Martyn Ball
Martyn Ball

Reputation: 4895

WPF: Centre TextBlock within Canvas

i'm trying to centre my TextBlock within my Canvas, but it doesn't seem to be working as intended. I want the centre point to change obviously when more text is added so it stays centred.

This is what is being produced, 3 images showing when there is 1 number, 2 numbers and 3.

enter image description here

Here is my ControlTemplate

<Style TargetType="ProgressBar" x:Key="CircularProgress">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ProgressBar">
                    <Grid x:Name="PathGrid" Margin="2" Width="200">
                        <Canvas>
                            <TextBlock x:Name="PathPercentage" Text="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Value, StringFormat={}{0}%}"
                                    Foreground="White"
                                    FontFamily="DefaultFont"
                                    FontSize="{Binding ElementName=PathGrid, Path=ActualWidth, Converter={StaticResource SizeTextOnParent}}">
                                <TextBlock.Margin>
                                    <MultiBinding Converter="{StaticResource CenterElement}">
                                        <Binding ElementName="PathGrid" Path="ActualWidth"/>
                                        <Binding ElementName="PathPercentage" Path="FontSize" />
                                        <Binding ElementName="PathPercentage" Path="FontFamily"/>
                                        <Binding ElementName="PathPercentage" Path="Text"/>
                                    </MultiBinding>
                                </TextBlock.Margin>
                            </TextBlock>
                            <TextBlock Text="{Binding ElementName=PathPercentage, Path=Margin}" />
                            <Ellipse Fill="Transparent" 
                                 Stroke="#434953" 
                                 StrokeThickness="3" 
                                 Width="{Binding ElementName=PathGrid, Path=ActualWidth}" 
                                 Height="{Binding ElementName=PathGrid, Path=ActualWidth}" />

                            <Path x:Name="pathRoot" 
                              Stroke="#8ab71c" 
                              StrokeThickness="6" 
                              HorizontalAlignment="Center" 
                              VerticalAlignment="Top">

                                <Path.Data>
                                    <PathGeometry>
                                        <PathFigureCollection>
                                            <PathFigure StartPoint="{Binding ElementName=PathGrid, Path=ActualWidth, Converter={StaticResource StartPointConverter}, Mode=OneWay}">
                                                <ArcSegment Size="{Binding ElementName=PathGrid, Path=ActualWidth, Converter={StaticResource ArcSizeConverter}, Mode=OneWay}" SweepDirection="Clockwise">
                                                    <ArcSegment.IsLargeArc>
                                                        <MultiBinding Converter="{StaticResource LargeArcConverter}">
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
                                                            <Binding ElementName="FullyIndeterminateGridScaleTransform" Path="ScaleX" />
                                                        </MultiBinding>
                                                    </ArcSegment.IsLargeArc>
                                                    <ArcSegment.Point>
                                                        <MultiBinding Converter="{StaticResource ArcEndPointConverter}">
                                                            <Binding ElementName="PathGrid" Path="ActualWidth" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
                                                            <Binding ElementName="FullyIndeterminateGridScaleTransform" Path="ScaleX" />
                                                        </MultiBinding>
                                                    </ArcSegment.Point>
                                                </ArcSegment>
                                            </PathFigure>
                                        </PathFigureCollection>
                                    </PathGeometry>
                                </Path.Data>
                            </Path>
                        </Canvas>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

And here is the CenterElement converter which defines the TextBlocks margin

namespace Test_Project.Converters
{
    public class CenterElement : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double parentWidth = (double)values[0];
            double fontSize = (double)values[1];
            FontFamily fontFamily = (FontFamily)values[2];
            string text = (string)values[3];

            var textBlock = new TextBlock {
                Text = text,
                TextWrapping = TextWrapping.Wrap,
                FontFamily = fontFamily,
                FontSize = fontSize};

            textBlock.Measure(new Size());
            textBlock.Arrange(new Rect());

            Console.WriteLine("Height: " + textBlock.ActualHeight + " Width: " + textBlock.ActualWidth + " Text: " + text);

            double h = Math.Round(((parentWidth / 2) - (textBlock.ActualHeight / 2)),2);
            double w = Math.Round(((parentWidth / 2) - (textBlock.ActualWidth / 2)), 2);

            Thickness margin = new Thickness(w,h,0,0);

            return margin;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Upvotes: 2

Views: 3279

Answers (1)

Mark Feldman
Mark Feldman

Reputation: 16148

I'd just bind the width of the textbox to the ActualWidth of the entire canvas and set TextAlignment to "Center":

<TextBlock Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType=Canvas}}" TextAlignment="Center" ...etc... />

And if you don't want it in the exact centre of the canvas then you can always offset it with Margin.

Upvotes: 5

Related Questions