0

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.

Julius
  • 735
  • 1
  • 9
  • 25
  • Does fixing the textblock to 10 width solve the issue? If it does, pick one textblock to be the master and bind all your other text blocks to its width and height properties. That guarantees your textblocks are all uniform in size - then just use whatever preferred technique you have for managing what that controls size is. – kidshaw Jul 28 '14 at 13:02
  • Other alternatives would be to build it in code behind as a user control and react to the size changed event to scale them or would a simple uniform grid work for you – kidshaw Jul 28 '14 at 13:04
  • Yes that does work, but relies on me hardcoding the maximum width of a single glyph, which feels wrong to me as I'm assuming the max width of a letter in whatever font, with whatever font style. – Julius Jul 28 '14 at 13:13
  • I think uniform grid is the way to go – kidshaw Jul 28 '14 at 13:19

2 Answers2

1

Using a uniform grid and binding to a primary textblock you get a pretty decent scaling:

 <UniformGrid Rows="3" Columns="10">
        <Viewbox>
            <Border>
                <TextBlock TextAlignment="Center" 
                           Width="{Binding ActualWidth, ElementName=textBlock, Mode=OneWay}" 
                           Height="{Binding ActualHeight, ElementName=textBlock, Mode=OneWay}">
                    Q
                </TextBlock>
            </Border>
        </Viewbox>
        <Viewbox>
            <Border>
                <TextBlock x:Name="textBlock" TextAlignment="Center">
                    W
                </TextBlock>
            </Border>
        </Viewbox>
        <Viewbox>
            <Border>
                <TextBlock Width="{Binding ActualWidth, ElementName=textBlock, Mode=OneWay}"
                           Height="{Binding ActualHeight, ElementName=textBlock, Mode=OneWay}" 
                           TextAlignment="Center">
                    E
                </TextBlock>
            </Border>
        </Viewbox>
        <Viewbox>
            <Border>
                <TextBlock Width="{Binding ActualWidth, ElementName=textBlock, Mode=OneWay}" 
                           Height="{Binding ActualHeight, ElementName=textBlock, Mode=OneWay}" 
                           TextAlignment="Center">
                    R
                </TextBlock>
            </Border>
        </Viewbox>
    </UniformGrid>

You'll see each control is bound to the W key - assuming that is the biggest. If you are unsure, you can add a different element as hidden and bind to that - as you implied in your question. The important thing is that the grid sets the size of the main control element.

kidshaw
  • 3,423
  • 2
  • 16
  • 28
  • Upvoted as this solution works, but it relies on knowing the largest glyph at runtime, i.e. the "W" in this example. If a different theme/style is applied where the W is not the widest/tallest glyph then this model breaks down. Also it would not be possible to break out the templating of each key (i.e. the textblock) as they all bind back to a common textblock element. – Julius Jul 28 '14 at 14:28
  • 1
    Thanks - I agree the W link is not ideal. I would bind to a hidden element and set that to a unicode 'full block' character, or something more reliably big. – kidshaw Jul 28 '14 at 14:31
  • 1
    Unicode's full block character is certainly an interesting idea (Alt+219), but not all fonts will include this character. Text & fonts is a bit of a minefield. – Julius Jul 28 '14 at 14:34
  • Of course - I forgot that although it is defined in unicode, their is no requirement for type foundaries to include that character in their fonts. – kidshaw Jul 28 '14 at 14:37
1

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.

Julius
  • 735
  • 1
  • 9
  • 25
  • There is a caveat to this approach: all grids inside the view boxes will be sized to the MAXIMUM size of any of the grid's contents, i.e. a particularly large content will have knock on effects for all other cells. – Julius Jul 31 '14 at 16:56