An Enhanced WPF Custom Control for Zooming and Panning






4.96/5 (29 votes)
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
- 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.
- Eliminated the routed commands and implemented them as
ICommand
in the control using the same separate file. - Moved the
Canvas
with theBorder
used for showing the zoom rectangle into the control's XAML. - Updated the code to the newest framework and used the features of C# 6.0.
- Updated the
MainWindow
for the newControl
, removing most of the code behind (some is left for the rectangles that can be moved), and bound the buttons to theICommand
properties of the control, and eliiminted the mouse move events in the XAML. - Added a
MinZoomType
DependencyProperty
so that minimum zoom level can be set to fit screen, fill screen, or the specifiedMinContentScale
. - Made
ZoomIn
andZoomOut
change zoom level by 10% up or9.09...%
down instead of using addition and subtraction. - Added a
FillScreenCommand
. - Replaced the
event
handlers with the overridden methods (i.e. the method and subscription to theMouseDownEvent
has been replaced by theoverride
of theOnMouseLeftButtonDown
). - Added a second control (
ZoomAndPanViewBox
) that gives a full size view of the content of theZoomAndPanControl
, 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 - Created enhanced Undo/Redo capability. For the mouse scroll and zoom
ICommand
code there is aKeepAliveTimer
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. - I have done a fair amount of rearranging, so the organization is quite different.
- The
Background
,BorderThickness
andBorderColor
for the zoom rectangle inherits from theControl
. - Control panel on
MainWindow
changed to aStackPanel
to make it easier to add and remove controls. - Made some classes
internal
that should have been originally. - 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. - Created a control that derives from the
ScrollViewer
that contains theZoomAndPanControl
, but work is currently not completed. - Added a
MousePosition
DependencyProperty
. - Created two new commands that use the
<font>CommandParameter</font>
to indicate the amount to zoom:<font>ZoomRatioFromMinimumCommand</font>
, and<font>ZoomPercentCommand</font>
. - The zoom slider zooms to the center of the screen.
- Have a DependencyProperty to enable animations.
- Added an
IValueConverter
that can be used with the zoomSlider
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.
The picture above uses the ZoomAndPanControl
.
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, useTemplateBinding
for zoomBorder
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 improvedCanExecute
forICommand
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 theCanvas
- 2016-08-26: Fixed loss of focus to
ScrollViewer
, fixedInputBindings
and added more, added keyboard arrow keys scrolling for Undo/Redo, made a number of the classesinternal
, cleanup of theMainWindow
, 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 ofZoomAndPanViewBox
. - 2016-09-02: Refactoring and fixed the drag rectangle so specifying the viewport on the
ZoomAndPanViewBox
. and removed theBorder
around theZoomAndPanViewBox
in the sample since it was not needed anymore to constrain the viewport selectionBorder
. - 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 theScrollViewer
control that wraps theZoomAndPanControl
, and replaced subscription to anevent
with theoverride
methods. - 2016-09-08: Cleanup on
ZoomAndPanScrollViewer
control, Added aMousePosition <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 theZoomAndPanControl
directly and using theZoomAndPanScrollViewer
. - 2016-09-13: Fixed viewport border on
ZoomAndPanViewBox
when viewport shows more than image. Created two new commands that use theCommandParameter
to indicate the amount to zoom:ZoomRatioFromMinimumCommand
, andZoomPercentCommand
. Fixed some issues with theZoomAndPanScrollViewer
, 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
toInternalViewportZoom
and making itprivate
. The SliderZoom renamed toViewportZoom
to become the externally visible zoom.InternalViewportZoom
is only really be used for the animations. TheDependencyProperty
used for the slider isViewportZoom
. - 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