Thursday, January 8, 2015

WPF Zooming

... in response to MarkLTX, who posted an awesome article about zooming and how double-animation makes the user-experience much better ( http://www.codeproject.com/Tips/860914/Add-Zooming-to-a-WPF-Window-or-User-Control ):

In addition to the article I would like to mention a method which wasn't described. I think it is the most natural way in a WPF application to use XAML-Binding (without any code behind) and to use the slider only, instead of using an animation as e.g.: Word 2013 does it (maybe they also have an animation to keep things smooth, but if so, I don't really recognize it). Anyway, the animation was added to give the user a better feeling about what is going on and to show him (through the animation) that the current action which will be proceed is "zooming" and not any magic which confuses him.

I think that the user-activity of changing a slider makes it unnecessary to do any other things (as long as the performance of the application is OK).

Here a sample application which uses a slider to zoom:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<Window x:Name="window"
        x:Class="ZoomerApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="350"
        Width="525">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <WrapPanel Margin="10,10,10,0">
            <WrapPanel.LayoutTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="{Binding Value, ElementName=ZoomSlider}"
                                    ScaleY="{Binding Value, ElementName=ZoomSlider}" />
                    <SkewTransform />
                    <RotateTransform />
                    <TranslateTransform />
                </TransformGroup>
            </WrapPanel.LayoutTransform>
                <Button>Hello</Button>
                <Button>Hello</Button>
                <Button>Hello</Button>
                <Button>Hello</Button>
                <Button>Hello</Button>
                <Button>Hello</Button>
                <Button>Hello</Button>
                <Button>Hello</Button>
                <Button>Hello</Button>

        </WrapPanel>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="1*" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Slider x:Name="ZoomSlider"
                    Width="100"
                    HorizontalAlignment="Right"
                    Margin="10,2,10,0"
                    Grid.Column="0"
                    Value="1" />
            <StackPanel Orientation="Horizontal"
                        Grid.Column="1">
                <Label Content="{Binding Value, ElementName=ZoomSlider}"
                       ContentStringFormat="{}Zooming: {0:N2}" />
            </StackPanel>
            <Button Width="100"
                    Content="OK"
                    Margin="20,2,0,10"
                    Height="25"
                    Grid.Column="3"
                    IsDefault="True" />
            <Button Width="100"
                    Margin="2,2,10,10"
                    Content="Cancel"
                    Height="25"
                    Grid.Column="4"
                    IsCancel="True" />
        </Grid>
    </Grid>
</Window>


What I did:
  • I created a window with a main grid and 2 containers inside...
    • the top container is a WrapPanel which will be zoomed
    • the bottom container has a slider which sets the zoom level
  • The binding is on the slider.value (so I named the slider ZoomSlider to be able to call it by its element-name). There are much more elegant ways, but I think this one is elegant enough for this quick sample.
  • line 18 and 19 are the most important
    • a binding is set up on the value of the slider
    • the value is set on line 49 to default 1 what equals 100%
  • line 52 creates a label which visualizes the content of the slider.value.
I used to create zooming like this and always got good feedback about it (I think the main reason is because it behaves so similar to the word-solution). What also makes this solution attractive for the developer is that no code in the code-behind is needed to accomplish zooming (as long as the zooming level should not be persisted).

kind regards, Daniel

No comments: