Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An Enhanced WPF Custom Control for Zooming and Panning

0.00/5 (No votes)
19 Aug 2016 1  
This is an alternative for A WPF Custom Control for Zooming and Panning

Introduction

I looked at this control and thought it worked really well, but I did not like that a lot of the code was in the code-behind of the window containing the control. To see the original control: A WPF Custom Control for Zooming and Panning by Ashley Davis.

Changes

  1. Moved all of the event handlers to respond to mouse movements, and scroll wheel to the control, putting it in a separate file from the existing code.
  2. Eliminated the routed commands and implemented them as ICommand in the control using the same separate file.
  3. Moved the Canvas with the Border used for showing the zoom rectangle into the control's XAML.
  4. Updated the code to the newest framework and used the features of C# 6.0.
  5. Updated the MainWindow for the new Control, removing most of the code behind (some is left for the rectangles that can be moved), and bound the buttons to the ICommand properties of the control, and eliiminted the mouse move events in the XAML.
  6. Added a MinZoomType DependencyProperty so that minimum zoom level can be set to fit screen, fill screen, or the specified MinContentScale.
  7. Made ZoomIn and ZoomOut change zoom level by 10% up or 9.09...% down instead of using addition and subtraction. 
  8. Added a FillScreenCommand.
  9. Replaced the event handlers with the overridden methods (i.e. the method and subscription to the MouseDownEvent has been replaced by the override of the OnMouseLeftButtonDown).
  10. Added a second control (ZoomAndPanViewBox) that gives a full size view of the content of the ZoomAndPanControl, and allows the zoom box to be moved, DoubleClick to center zoom box, and shift drag to create a rectangle to zoom to.examples of using the ZoomAndPanControl directly and using the ZoomAndPanScrollViewer.
    2016-09-13: Fixed viewport border on ZoomAndPanViewBox when viewport shows more than image. Created two new commands that use the CommandParameter to indicate the amount to zoom:  ZoomRatioFromMinimumCommand, and ZoomPercentCommand. Fixed some issues with the ZoomAndPanScrollViewer, in particular the binding to commands which was somewhat problematical, working for this sample but not in other places.
    2016-09-16: Various cleanup.
    2016-09-19: Cleanup. Renaming the ViewportZoom to InternalViewportZoom and making it private. The SliderZoom renamed to ViewportZoom to become the externally visible zoom. InternalViewportZoom is only really be used for the animations. The DependencyProperty used for the slider is ViewportZoom.
    2016-09-21: Added a UseAnimations DependencyPropertry that to disable animations. When resizing control keep it fit or fill if it is at fit or fill. Move some more logic into helpers for determining fit and fill and 
  11. Created enhanced Undo/Redo capability. For the mouse scroll and zoom ICommand code there is a KeepAliveTimer that delays any save until there has been no activity for a specified amount of time. For the scroll is it 750 milliseconds, and the zoom buttons it is 1500 milliseconds.
  12. I have done a fair amount of rearranging, so the organization is quite different.
  13. The Background, BorderThickness and BorderColor for the zoom rectangle inherits from the Control.
  14. Control panel on MainWindow changed to a StackPanel to make it easier to add and remove controls.
  15. Made some classes internal that should have been originally.
  16. Added the feature of being able to specify a different visual for the ZoomAndPanViewBox. In the case of what I was working on, I needed this because there was the Crosshair overlay and did not want that appearing in the thumbnail.
  17. Created a control that derives from the ScrollViewer that contains the ZoomAndPanControl, but work is currently not completed.
  18. Added a MousePosition DependencyProperty.
  19. Created two new commands that use the <font>CommandParameter</font> to indicate the amount to zoom: <font>ZoomRatioFromMinimumCommand</font>, and <font>ZoomPercentCommand</font>.
  20. The zoom slider zooms to the center of the screen.
  21. Have a DependencyProperty to enable animations.
  22. Added an IValueConverter that can be used with the zoom Slider to make the slider seem more linear.

Using the code

It is a lot easier to use this control now that almost all of the functionality is in the control and is not required to be in the container:

        <ScrollViewer x:Name="scroller"
                      CanContentScroll="True"
                      HorizontalScrollBarVisibility="Visible"
                      VerticalScrollBarVisibility="Visible">

            <!--
                This is the control that handles zooming and panning.
            -->
            <zoomAndPan:ZoomAndPanControl Name="ZoomAndPanControl"
                                          Background="LightGray"
                                          MinimumZoomType="FitScreen">

                <!--
                    This is the content that is displayed.
                -->

               </Grid>
            </zoomAndPan:ZoomAndPanControl>
        </ScrollViewer>

It is important to make sure that the CanContentScroll="True" is set since this is how the ScrollViewer knows that the ZoomAndPanControl implements is IScrollInfo. The Name attribute is required to hook up the ZoomAndPanViewBox.

There is a Control that has been added that removes the need to specify the ScrollViewer to contain the ZoomAndPanControl. This is much easier to use:

<zoomAndPan:ZoomAndPanScrollViewer Name="ZoomAndPanControl"
                                   Grid.Row="0"
                                   Background="#AACCCCCC"
                                   MinimumZoomType="FitScreen"
                                   ZoomAndPanInitialPosition="OneHundredPercentCentered">

To use the ZoomAndPanViewBox is also very straight forward:

<zoomAndPan:ZoomAndPanViewBox Height="100"
                              DataContext="{Binding ElementName=ZoomAndPanControl}"
                              Visual="{Binding actualContent}"/>

As can be seen the only real requirement is the Binding of the ZoomAndPanControl to the DataContext. The text in bold is the Binding to the element to be shown in the Background. If it is omitted then the Content of the PanAndZoomControl is used.

The other thing that may be desirable are the KeyBinding entries under InputBindings:

<Window.InputBindings>
    <KeyBinding Key="Minus"
    Command="{Binding ElementName=ZoomAndPanControl, Path=ZoomOutCommand}" />
    <KeyBinding Key="Subtract"
    Command="{Binding ElementName=ZoomAndPanControl, Path=ZoomOutCommand}" />
    <KeyBinding Key="Add"
    Command="{Binding ElementName=ZoomAndPanControl, Path=ZoomInCommand}" />
    <KeyBinding Key="OemPlus"
    Command="{Binding ElementName=ZoomAndPanControl, Path=ZoomInCommand}" />
    <KeyBinding Key="Back"
    Command="{Binding ElementName=ZoomAndPanControl, Path=UndoZoomCommand}" />
    <KeyBinding Command="{Binding ElementName=ZoomAndPanControl, Path=UndoZoomCommand}"
    Gesture="CTRL+Z" />
    <KeyBinding Command="{Binding ElementName=ZoomAndPanControl, Path=RedoZoomCommand}"
    Gesture="CTRL+Y" />
    <KeyBinding Command="{Binding ElementName=ZoomAndPanControl, Path=ZoomOutCommand}"
    Gesture="SHIFT+Minus" />
    <KeyBinding Command="{Binding ElementName=ZoomAndPanControl, Path=ZoomInCommand}"
    Gesture="SHIFT+OemPlus" />
</Window.InputBindings>

These definitions allow the keyboard to be used to control the display. Originally these were not as comprehensive and there were some issues in them, but these should all work, and provide more functionality.

Image 1

The picture above uses the ZoomAndPanControl.

Image 2

The picture above uses the ZoomAndPanScrollViewer.

Notes:

Looking at the XAML for the ScrollViewer containing the ZoomAndPanControl the element CanContentScroll="True". If this is not in the XAML, the control will not work right.

It should be noted also that the property IsHitTestVisible is set to false on the CenteredCrossHairCanvas so that button actions are visible in the underlying control.

The keyboard shortcuts do not work for the Sample because of the TabControl. Have it working in other circumstances. See http://www.codeproject.com/Articles/38507/Using-the-WPF-FocusScope.

History

  • 2016-08-19: Initial Version
  • 2016-08-22: Bug fix for Zoom Rectangle
  • 2016-08-22: Added disable of Size and Zoom commands on proper conditions
  • 2016-08-23: Added additional contraint options for minimum zoom
  • 2016-08-23: Added ZoomAndPanViewBox control, use TemplateBinding for zoom Border and some rename and cleanup:
  • 2016-08-24: Added enhanced Undo/Redo capability for panning including hooking up the ZoomAndPanViewBox to this capability and some refactoring, and improved CanExecute for ICommand methods.
  • 2016-08-24: Added Undo/Redo for zoom functions that only save after a delay with no activity.
  • 2016-08-25: Fixed bug in ZoomAndPanViewBox that let it shadow border extend beyond the Canvas
  • 2016-08-26: Fixed loss of focus to ScrollViewer, fixed InputBindings and added more, added keyboard arrow keys scrolling for Undo/Redo, made a number of the classes internal, cleanup of the MainWindow, including removing unnessary converter
  • 2016-08-29: Added information on InputBindings changes.
  • 2016-08-30: Fixed small bug and some refactoring
  • 2016-08-31: Fixed small bug and some refactoring and added crosshair control
  • 2016-09-01: Added alternate Binding DependencyProperty for content of ZoomAndPanViewBox.
  • 2016-09-02: Refactoring and fixed the drag rectangle so specifying the viewport on the ZoomAndPanViewBox. and removed the Border around the ZoomAndPanViewBox in the sample since it was not needed anymore to constrain the viewport selection Border.
  • 2016-09-06: Enumeration renaming, fixing zooming implemented by buttons wrt to centering of zoom, separating helpers into specific files, adding a property to specify the initial zoom and pan, CrossHair control now uses Show DependencyProperty to make CrossHair visible.
  • 2016-09-07: Included a ZoomAndPanScrollViewer control derived from the ScrollViewer control that wraps the ZoomAndPanControl, and replaced subscription to an event with the override methods.
  • 2016-09-08: Cleanup on ZoomAndPanScrollViewer control, Added a MousePosition <span style="margin: 0px; color: rgb(17, 17, 17); line-height: 107%; font-family: "Segoe UI", sans-serif; font-size: 10.5pt">DependencyProperty</span>.
  • 2016-09-09: Bug fixes, particularly on ZoomAndPanScrollViewer. Updated the sample so that it has examples of using the ZoomAndPanControl directly and using the ZoomAndPanScrollViewer.
  • 2016-09-13: Fixed viewport border on ZoomAndPanViewBox when viewport shows more than image. Created two new commands that use the CommandParameter to indicate the amount to zoom:  ZoomRatioFromMinimumCommand, and ZoomPercentCommand. Fixed some issues with the ZoomAndPanScrollViewer, in particular the binding to commands which was somewhat problematical, working for this sample but not in other places.
  • 2016-09-16: Various cleanup.
  • 2016-09-19: Cleanup. Renaming the ViewportZoom to InternalViewportZoom and making it private. The SliderZoom renamed to ViewportZoom to become the externally visible zoom. InternalViewportZoom is only really be used for the animations. The DependencyProperty used for the slider is ViewportZoom.
  • 2016-09-21: Added a UseAnimations DependencyPropertry that to disable animations. When resizing control keep it fit or fill if it is at fit or fill. Move some more logic into helpers for determining fit and fill and if a zoom level is within 1% of another zoom level.'
  • 2016-09-29: Added IValueConverter to make slider seem more linear. Fixed bug that stopped thumbnail from working right and bug that caused problems when window resized to nothing, Article cleanup

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here