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 the Border
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 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. - Added a
MinZoomType
DependencyProperty
so that minimum zoom level can be set to fit screen, fill screen, or the specified MinContentScale
. - Made
ZoomIn
and ZoomOut
change zoom level by 10% up or 9.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 the MouseDownEvent
has been replaced by the override
of the OnMouseLeftButtonDown
). - 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 - 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. - I have done a fair amount of rearranging, so the organization is quite different.
- The
Background
, BorderThickness
and BorderColor
for the zoom rectangle inherits from the Control
. - Control panel on
MainWindow
changed to a StackPanel
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 the ZoomAndPanControl
, 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 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.
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, 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