Search code examples
wpftextfontsscaleviewbox

Why are letters scaling to different sizes in comparable view boxes within WPF?


I am attempting to create an onscreen keyboard using a Grid for key layout. Each key consists of a Border with a TextBlock containing a letter. To make the letters scale I have wrapped each TextBlock in a ViewBox, for example;

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>

    <Grid Grid.Row="0" Grid.Column="0">
        <Border BorderThickness="1" BorderBrush="Gray">
            <Viewbox>
                <TextBlock Text="a" />
            </Viewbox>
        </Border>
    </Grid>

    <Grid Grid.Row="0" Grid.Column="1">
        <Border BorderThickness="1" BorderBrush="Gray">
            <Viewbox>
                <TextBlock Text="b" />
            </Viewbox>
        </Border>
    </Grid>

    <Grid Grid.Row="0" Grid.Column="2">
        <Border BorderThickness="1" BorderBrush="Gray">
            <Viewbox>
                <TextBlock Text="c" />
            </Viewbox>
        </Border>
    </Grid>
</Grid>

The problem is when you shrink the control by resizing the window horizontally (i.e. squash the borders together horizontally). As each letter has a slightly different width and height, the amount of zooming/scaling applied by each viewbox is not exactly the same. This results in the letters being rendered at different vertical heights, i.e. the "b" will be on a horizontal plane above the "a" and "c", which looks a little wrong.

The only work around I can think of (which works) relies on fixing the widths of each textblock, e.g. setting Width="10". This, however, feels unsatisfactory as it requires knowledge of the font which will be used to display each letter and an assumption about the maximum width. A middle ground would be to achieve this automatically the largest possible letter/glyph in each viewbox by including a hidden letter in each textblock;

    <Grid Grid.Row="0" Grid.Column="0">
        <Border BorderThickness="1" BorderBrush="Gray">
            <Viewbox>
                <Grid>
                    <TextBlock Text="a" />
                    <TextBlock Text="X" Visibility="Hidden" />
                </Grid>
            </Viewbox>
        </Border>
    </Grid>

I don't like that solution though and would love a reliable way to ensure all textblocks are the same size and so scale acceptably, without hard coding values or assumptions about the font.

Any ideas?

Thanks.


Solution

  • This MSDN question is answered correctly; http://social.msdn.microsoft.com/Forums/vstudio/en-US/c052fa89-4788-4d85-b266-fdd5c637a0ff/sharing-viewbox-zoom-level-between-items?forum=wpf

    The solution relies on leveraging the SharedSizeGroup behaviour on a grid to ensure that the viewbox of every key is the same size as every other viewbox, like so;

    <Viewbox>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition SharedSizeGroup="col"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition SharedSizeGroup="row"/>
        </Grid.RowDefinitions>
        <TextBlock Text="a" />
      </Grid>
    </Viewbox>
    

    Other solutions involving hard coding the width/height of the viewbox, or binding to a common viewbox and filling it with the largest possible glyph work, but are not perfect solutions. The above solution makes no assumptions and relies on built in WPF measure/arrange logic to produce the desired outcome.