top of page

Let Users Zoom In or Out of a WPF View

When designing user interfaces, there are times when one size doesn't fit all. For example, users may want to show your app on a projector screen. In this case, the sizes of the controls and fonts need to be big so that people in the back of the room can see them clearly.


Even or a regular screen, users with a visual impairment may want to zoom in on your interface. And users with excellent vision may want to zoom out to see more content on the screen. In this post, I'll show you how to let users zoom in or out of your WPF user interface.


WPF elements (derived from class FrameworkElement) have a LayoutTransform property that lets you apply various kinds of transformations (for example, scale and rotation). For zooming, we use the scale transformation.


Here's a simple example of how to apply the scale transformation in XAML:


<Grid>
    <Grid.LayoutTransform>
        <ScaleTransform ScaleX="0.5" ScaleY="0.5" />
    </Grid.LayoutTransform>

    <!-- Contents -->
</Grid>

This zooms out, or scales, the contents of the Grid to 50%.


In our sample app, we're going to let users change this scale value in two ways. One way is to use three buttons: zoom in, zoom out, and reset zoom. The other is to use a slider. We're also going to display the current scale value in percent.


Here's a screenshot of the app when it starts. Notice that the "Reset Zoom" button is disabled. This is because the scale is already at 100% (actual size).


(TODO: Add image.)


Here's what the app looks like when the zoom value has been increased to 300%. Notice that the "Reset Zoom" button is now enabled.


(TODO: Add image.)


The "Zoom In" and "Zoom Out" buttons are enabled in the screenshots above, but they would be disabled when the minimum and maximum scale values, respectively, are reached.


The Visual Studio solution for this sample app is available on GitHub: https://github.com/redcurry/ZoomWpfView.


To get started, create a new WPF application. Open the MainWindow.xaml file and change it to the XAML below:


<Window
    x:Class="ZoomWpfView.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Zoom Views"
    >
    <DockPanel>
        <!-- Zoom controls -->
        <StackPanel
            DockPanel.Dock="Top"
            Orientation="Horizontal"
            >
            <Button
                Content="Zoom In"
                Command="{Binding ZoomInCommand}"
                />
            <Button
                Content="Zoom Out"
                Command="{Binding ZoomOutCommand}"
                />
            <Button
                Content="Reset Zoom"
                Command="{Binding ResetZoomCommand}"
                />
            <Slider
                Minimum="{Binding MinimumScale}"
                Maximum="{Binding MaximumScale}"
                Value="{Binding Scale}"
                TickFrequency="{Binding ScaleStep}"
                IsSnapToTickEnabled="True"
                Width="100"
                />
            <TextBlock
                Text="{Binding Scale, StringFormat=\{0:P0\}}"
                />
        </StackPanel>
            
        <Grid>
            <Grid.LayoutTransform>
                <ScaleTransform ScaleX="{Binding Scale}" ScaleY="{Binding Scale}" />
            </Grid.LayoutTransform>

            <!-- Sample contents -->
            <StackPanel HorizontalAlignment="Left">
                <CheckBox>Option 1</CheckBox>
                <CheckBox>Option 2</CheckBox>
                <Button>OK</Button>
            </StackPanel>
        </Grid>
    </DockPanel>
</Window>

The first three buttons are zoom in, zoom out, and reset zoom, each using a Command to provide their functionality. If you're not familiar with Commands or the Model-View-ViewModel (MVVM) pattern, see this blog post by Kevin Bost.


To the right of the buttons there's the slider, which lets the user change the scale value more quickly and smoothly. The Minimum, Maximum, and current Value of the slider are bound to corresponding properties in the view model (shown below). The TickFrequency and IsSnapToTickEnabled properties makes the slider change by a predefined step amount.


Next, the TextBlock shows the current scale value as a percent.


Finally, the Grid contains the main contents to be scaled. As you saw above, we're using the ScaleTransform property, but instead of hard-coding the scale values, we're binding them to properties in the view model.


We're going to use the MVVM Light Toolkit to simplify our implementation of the MVVM pattern. It is conveniently provided as a NuGet package, so install the package "MvvmLightLibs" to your project. Then add a new class named ViewModel and implement it as follows:


public class ViewModel : ViewModelBase
{
    private const decimal Unity = 1;

    private decimal _scale = Unity;
    public decimal Scale
    {
        get => _scale;
        set => Set(ref _scale, value);
    }

    public decimal ScaleStep => 0.1m;

    public decimal MinimumScale => 0.1m;

    public decimal MaximumScale => 4.0m;

    public ICommand ZoomInCommand => new RelayCommand(
        () => Scale += ScaleStep, () => Scale < MaximumScale);

    public ICommand ZoomOutCommand => new RelayCommand(
        () => Scale -= ScaleStep, () => Scale > MinimumScale);

    public ICommand ResetZoomCommand => new RelayCommand(
        () => Scale = Unity, () => Scale != Unity);
}

The ViewModelBase class (the ViewModel's parent class) comes from the MVVM Light Toolkit. It provides the Set method, which automatically raises the PropertyChanged event for the property from which it's called. We use it for the Scale property.


The ScaleStep, MinimumScale, and MaximumScale properties are simple constants. The ZoomInCommand, ZoomOutCommand, and ResetZoomCommand use the MVVM Light RelayCommand to perform the given action.


For example, the ZoomInCommand adds a ScaleStep value to the current value of Scale. It also provides a second function to RelayCommand. This second function evaluates to true or false, depending on whether the ZoomInCommand should be available. If true, the button that is bound to the command is enabled; if false, the button is disabled.


Notice that we're using the decimal type in all the scale values. Using decimal, instead of double or float, avoids floating point errors.


Finally, we set the Window's DataContext property to a new instance of the ViewModel:


public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }
}

That's it for this sample app. In a more realistic app, you'll probably want to improve the user interface. For example, you can put the zoom buttons in a tool bar and give them useful icons (for ideas, see https://materialdesignicons.com).

Related Posts

See All

ESAPI Essentials 1.1 and 2.0

A few months ago, I introduced ESAPI Essentials—a toolkit for working with ESAPI (available via NuGet). I've recently added one major feature: asynchronous access to ESAPI for binary plugin scripts. Y

Announcement: ESAPI Subreddit

A few months ago, Matt Schmidt started the ESAPI subreddit. It's another useful resource for finding and discussing ESAPI-related topics. According to the description, This community is to post and di

Dump All Patient Data from ESAPI

If, for whatever reason, you need to dump out all of the data for a patient from ESAPI, there's a quick and dirty way to do it. Well, there are probably several ways to do it, but here's one I've foun

Comments


bottom of page