Click here to Skip to main content
15,867,704 members
Articles / Web Development / HTML
Alternative
Article

An Enhanced WPF Custom Control for Zooming and Panning

Rate me:
Please Sign up or sign in to vote.
4.96/5 (29 votes)
19 Aug 2016MIT7 min read 60K   3.3K   23   33
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, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
QuestionHow would use the control to just scale in the x directio and just in the y direction? Pin
Member 1529457919-Jul-21 7:06
Member 1529457919-Jul-21 7:06 
QuestionMessage Closed Pin
6-Jun-21 15:49
Member 152336646-Jun-21 15:49 
QuestionViewport size Pin
djdingley18-Jan-20 5:57
djdingley18-Jan-20 5:57 
PraiseThis is fantastic Pin
User 113576977-Jun-19 7:58
User 113576977-Jun-19 7:58 
QuestionBrilliant - thank you for this. One query... Pin
Stigzler7414-Oct-18 2:01
Stigzler7414-Oct-18 2:01 
AnswerRe: Brilliant - thank you for this. One query... Pin
Stigzler7414-Oct-18 5:30
Stigzler7414-Oct-18 5:30 
QuestionTwo Questions and two possible additions Pin
Member 138548601-Jun-18 9:19
Member 138548601-Jun-18 9:19 
QuestionBug found in ZoomAndPanControl_EventHandlers.cs Pin
Member 1258905623-May-18 3:34
Member 1258905623-May-18 3:34 
QuestionUsing this control for data plotting. Good idea or not? (negative coordinates and inverted Y-axis) Pin
Member 1127893810-May-18 23:45
Member 1127893810-May-18 23:45 
QuestionWould like mouse wheel to drive scroll viewer too. Pin
Harold Chattaway8-Jan-18 11:08
professionalHarold Chattaway8-Jan-18 11:08 
AnswerRe: Would like mouse wheel to drive scroll viewer too. Pin
Clifford Nelson3-Mar-18 7:01
Clifford Nelson3-Mar-18 7:01 
GeneralMy vote of 5 Pin
outbred9-May-17 5:51
professionaloutbred9-May-17 5:51 
Fantastic control and design. thanks for sharing!
AnswerRe: My vote of 5 Pin
Clifford Nelson24-May-17 5:25
Clifford Nelson24-May-17 5:25 
GeneralRe: My vote of 5 Pin
outbred24-May-17 5:44
professionaloutbred24-May-17 5:44 
SuggestionBeware of MeasureOverride on Infinite Size Pin
alex feinberg3-Mar-17 3:14
alex feinberg3-Mar-17 3:14 
AnswerRe: Beware of MeasureOverride on Infinite Size Pin
Clifford Nelson3-Mar-17 5:57
Clifford Nelson3-Mar-17 5:57 
GeneralCommand binding Pin
Sven Bardos4-Sep-16 11:00
Sven Bardos4-Sep-16 11:00 
AnswerRe: Command binding Pin
Clifford Nelson5-Sep-16 12:27
Clifford Nelson5-Sep-16 12:27 
GeneralRe: Command binding Pin
Sven Bardos6-Sep-16 10:05
Sven Bardos6-Sep-16 10:05 
QuestionAvoid Panning Pin
Sven Bardos29-Aug-16 9:35
Sven Bardos29-Aug-16 9:35 
AnswerRe: Avoid Panning Pin
Clifford Nelson29-Aug-16 9:37
Clifford Nelson29-Aug-16 9:37 
AnswerRe: Avoid Panning Pin
Clifford Nelson2-Sep-16 5:16
Clifford Nelson2-Sep-16 5:16 
GeneralRe: Avoid Panning Pin
Sven Bardos2-Sep-16 5:30
Sven Bardos2-Sep-16 5:30 
AnswerRe: Avoid Panning Pin
Clifford Nelson2-Sep-16 7:16
Clifford Nelson2-Sep-16 7:16 
GeneralRe: Avoid Panning Pin
Sven Bardos2-Sep-16 7:27
Sven Bardos2-Sep-16 7:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.