using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Bro.Common.ImageCanvas
{
public partial class ZoomAndPanControl
{
private void ZoomAndPanControl_EventHandlers_OnApplyTemplate()
{
_partDragZoomBorder = Template.FindName("PART_DragZoomBorder", this) as Border;
_partDragZoomCanvas = Template.FindName("PART_DragZoomCanvas", this) as Canvas;
}
///
/// The control for creating a zoom border
///
private Border _partDragZoomBorder;
///
/// The control for containing a zoom border
///
private Canvas _partDragZoomCanvas;
///
/// Specifies the current state of the mouse handling logic.
///
private MouseHandlingModeEnum _mouseHandlingMode = MouseHandlingModeEnum.None;
///
/// The point that was clicked relative to the ZoomAndPanControl.
///
private Point _origZoomAndPanControlMouseDownPoint;
///
/// The point that was clicked relative to the content that is contained within the ZoomAndPanControl.
///
private Point _origContentMouseDownPoint;
///
/// Records which mouse button clicked during mouse dragging.
///
private MouseButton _mouseButtonDown;
///
/// Event raised on mouse down in the ZoomAndPanControl.
///
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
SaveZoom();
_content.Focus();
Keyboard.Focus(_content);
_mouseButtonDown = e.ChangedButton;
_origZoomAndPanControlMouseDownPoint = e.GetPosition(this);
_origContentMouseDownPoint = e.GetPosition(_content);
if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0 &&
(e.ChangedButton == MouseButton.Left ||
e.ChangedButton == MouseButton.Right))
{
// Shift + left- or right-down initiates zooming mode.
_mouseHandlingMode = MouseHandlingModeEnum.Zooming;
}
else if (_mouseButtonDown == MouseButton.Left)
{
// Just a plain old left-down initiates panning mode.
_mouseHandlingMode = MouseHandlingModeEnum.Panning;
}
if (_mouseHandlingMode != MouseHandlingModeEnum.None)
{
// Capture the mouse so that we eventually receive the mouse up event.
CaptureMouse();
}
}
///
/// Event raised on mouse up in the ZoomAndPanControl.
///
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
if (_mouseHandlingMode != MouseHandlingModeEnum.None)
{
if (_mouseHandlingMode == MouseHandlingModeEnum.Zooming)
{
if (_mouseButtonDown == MouseButton.Left)
{
// Shift + left-click zooms in on the content.
ZoomIn(_origContentMouseDownPoint);
}
else if (_mouseButtonDown == MouseButton.Right)
{
// Shift + left-click zooms out from the content.
ZoomOut(_origContentMouseDownPoint);
}
}
else if (_mouseHandlingMode == MouseHandlingModeEnum.DragZooming)
{
var finalContentMousePoint = e.GetPosition(_content);
// When drag-zooming has finished we zoom in on the rectangle that was highlighted by the user.
ApplyDragZoomRect(finalContentMousePoint);
}
ReleaseMouseCapture();
_mouseHandlingMode = MouseHandlingModeEnum.None;
}
}
///
/// Event raised on mouse move in the ZoomAndPanControl.
///
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
var oldContentMousePoint = MousePosition;
var curContentMousePoint = e.GetPosition(_content);
MousePosition = curContentMousePoint.FilterClamp(_content.ActualWidth - 1, _content.ActualHeight - 1);
OnPropertyChanged(new DependencyPropertyChangedEventArgs(MousePositionProperty, oldContentMousePoint,
curContentMousePoint));
if (_mouseHandlingMode == MouseHandlingModeEnum.Panning)
{
//
// The user is left-dragging the mouse.
// Pan the viewport by the appropriate amount.
//
var dragOffset = curContentMousePoint - _origContentMouseDownPoint;
ContentOffsetX -= dragOffset.X;
ContentOffsetY -= dragOffset.Y;
e.Handled = true;
}
else if (_mouseHandlingMode == MouseHandlingModeEnum.Zooming)
{
var curZoomAndPanControlMousePoint = e.GetPosition(this);
var dragOffset = curZoomAndPanControlMousePoint - _origZoomAndPanControlMouseDownPoint;
double dragThreshold = 10;
if (_mouseButtonDown == MouseButton.Left &&
(Math.Abs(dragOffset.X) > dragThreshold ||
Math.Abs(dragOffset.Y) > dragThreshold))
{
//
// When Shift + left-down zooming mode and the user drags beyond the drag threshold,
// initiate drag zooming mode where the user can drag out a rectangle to select the area
// to zoom in on.
//
_mouseHandlingMode = MouseHandlingModeEnum.DragZooming;
InitDragZoomRect(_origContentMouseDownPoint, curContentMousePoint);
}
}
else if (_mouseHandlingMode == MouseHandlingModeEnum.DragZooming)
{
//
// When in drag zooming mode continously update the position of the rectangle
// that the user is dragging out.
//
curContentMousePoint = e.GetPosition(this);
SetDragZoomRect(_origZoomAndPanControlMouseDownPoint, curContentMousePoint);
}
}
///
/// Event raised on mouse wheel moved in the ZoomAndPanControl.
///
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
DelayedSaveZoom750Miliseconds();
e.Handled = true;
if (e.Delta > 0)
ZoomIn(e.GetPosition(_content));
else if (e.Delta < 0)
ZoomOut(e.GetPosition(_content));
}
///
/// Event raised with the double click command
///
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0)
{
SaveZoom();
AnimatedSnapTo(e.GetPosition(_content));
}
}
#region private Zoom methods
///
/// Zoom the viewport out, centering on the specified point (in content coordinates).
///
private void ZoomOut(Point contentZoomCenter)
{
ZoomAboutPoint(InternalViewportZoom * 0.90909090909, contentZoomCenter);
}
///
/// Zoom the viewport in, centering on the specified point (in content coordinates).
///
private void ZoomIn(Point contentZoomCenter)
{
ZoomAboutPoint(InternalViewportZoom * 1.1, contentZoomCenter);
}
///
/// Initialise the rectangle that the use is dragging out.
///
private void InitDragZoomRect(Point pt1, Point pt2)
{
_partDragZoomCanvas.Visibility = Visibility.Visible;
_partDragZoomBorder.Opacity = 1;
SetDragZoomRect(pt1, pt2);
}
///
/// Update the position and size of the rectangle that user is dragging out.
///
private void SetDragZoomRect(Point pt1, Point pt2)
{
//
// Update the coordinates of the rectangle that is being dragged out by the user.
// The we offset and rescale to convert from content coordinates.
//
var rect = ViewportHelpers.Clip(pt1, pt2, new Point(0, 0),
new Point(_partDragZoomCanvas.ActualWidth, _partDragZoomCanvas.ActualHeight));
ViewportHelpers.PositionBorderOnCanvas(_partDragZoomBorder, rect);
}
///
/// When the user has finished dragging out the rectangle the zoom operation is applied.
///
private void ApplyDragZoomRect(Point finalContentMousePoint)
{
var rect = ViewportHelpers.Clip(finalContentMousePoint, _origContentMouseDownPoint, new Point(0, 0),
new Point(_partDragZoomCanvas.ActualWidth, _partDragZoomCanvas.ActualHeight));
AnimatedZoomTo(rect);
// new Rect(contentX, contentY, contentWidth, contentHeight));
FadeOutDragZoomRect();
}
//
// Fade out the drag zoom rectangle.
//
private void FadeOutDragZoomRect()
{
AnimationHelper.StartAnimation(_partDragZoomBorder, OpacityProperty, 0.0, 0.1,
delegate { _partDragZoomCanvas.Visibility = Visibility.Collapsed; }, UseAnimations);
}
#endregion
#region Commands
///
/// Command to implement the zoom to fill
///
public ICommand FillCommand => _fillCommand ?? (_fillCommand = new RelayCommand(() =>
{
SaveZoom();
AnimatedZoomToCentered(FillZoomValue);
RaiseCanExecuteChanged();
}, () => !InternalViewportZoom.IsWithinOnePercent(FillZoomValue) && FillZoomValue >= MinimumZoomClamped));
private RelayCommand _fillCommand;
///
/// Command to implement the zoom to fit
///
public ICommand FitCommand => _fitCommand ?? (_fitCommand = new RelayCommand(() =>
{
SaveZoom();
AnimatedZoomTo(FitZoomValue);
RaiseCanExecuteChanged();
}, () => !InternalViewportZoom.IsWithinOnePercent(FitZoomValue) && FitZoomValue >= MinimumZoomClamped));
private RelayCommand _fitCommand;
///
/// Command to implement the zoom to a percentage where 100 (100%) is the default and
/// shows the image at a zoom where 1 pixel is 1 pixel. Other percentages specified
/// with the command parameter. 50 (i.e. 50%) would display 4 times as much of the image
///
public ICommand ZoomPercentCommand
=> _zoomPercentCommand ?? (_zoomPercentCommand = new RelayCommand(value =>
{
SaveZoom();
var adjustedValue = value == 0 ? 1 : value / 100;
AnimatedZoomTo(adjustedValue);
RaiseCanExecuteChanged();
}, value =>
{
var adjustedValue = value == 0 ? 1 : value / 100;
return !InternalViewportZoom.IsWithinOnePercent(adjustedValue) && adjustedValue >= MinimumZoomClamped;
}));
// Math.Abs(InternalViewportZoom - ((value == 0) ? 1.0 : value / 100)) > .01 * InternalViewportZoom
private RelayCommand _zoomPercentCommand;
///
/// Command to implement the zoom ratio where 1 is is the the specified minimum. 2 make the image twices the size,
/// and is the default. Other values are specified with the CommandParameter.
///
public ICommand ZoomRatioFromMinimumCommand
=> _zoomRatioFromMinimumCommand ?? (_zoomRatioFromMinimumCommand = new RelayCommand(value =>
{
SaveZoom();
var adjustedValue = (value == 0 ? 2 : value) * MinimumZoomClamped;
AnimatedZoomTo(adjustedValue);
RaiseCanExecuteChanged();
}, value =>
{
var adjustedValue = (value == 0 ? 2 : value) * MinimumZoomClamped;
return !InternalViewportZoom.IsWithinOnePercent(adjustedValue) && adjustedValue >= MinimumZoomClamped;
}));
private RelayCommand _zoomRatioFromMinimumCommand;
///
/// Command to implement the zoom out by 110%
///
public ICommand ZoomOutCommand => _zoomOutCommand ?? (_zoomOutCommand = new RelayCommand(() =>
{
DelayedSaveZoom1500Miliseconds();
ZoomOut(new Point(ContentZoomFocusX, ContentZoomFocusY));
}, () => InternalViewportZoom > MinimumZoomClamped));
private RelayCommand _zoomOutCommand;
///
/// Command to implement the zoom in by 91%
///
public ICommand ZoomInCommand => _zoomInCommand ?? (_zoomInCommand = new RelayCommand(() =>
{
DelayedSaveZoom1500Miliseconds();
ZoomIn(new Point(ContentZoomFocusX, ContentZoomFocusY));
}, () => InternalViewportZoom < MaximumZoom));
private RelayCommand _zoomInCommand;
private void RaiseCanExecuteChanged()
{
_zoomPercentCommand?.RaiseCanExecuteChanged();
_zoomOutCommand?.RaiseCanExecuteChanged();
_zoomInCommand?.RaiseCanExecuteChanged();
_fitCommand?.RaiseCanExecuteChanged();
_fillCommand?.RaiseCanExecuteChanged();
}
#endregion
///
/// When content is renewed, set event to set the initial position as specified
///
///
///
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
if (oldContent != null)
((FrameworkElement)oldContent).SizeChanged -= SetZoomAndPanInitialPosition;
((FrameworkElement)newContent).SizeChanged += SetZoomAndPanInitialPosition;
}
///
/// When content is renewed, set the initial position as specified
///
///
///
private void SetZoomAndPanInitialPosition(object sender, SizeChangedEventArgs e)
{
switch (ZoomAndPanInitialPosition)
{
case ZoomAndPanInitialPositionEnum.Default:
break;
case ZoomAndPanInitialPositionEnum.FitScreen:
InternalViewportZoom = FitZoomValue;
break;
case ZoomAndPanInitialPositionEnum.FillScreen:
InternalViewportZoom = FillZoomValue;
ContentOffsetX = (_content.ActualWidth - ViewportWidth / InternalViewportZoom) / 2;
ContentOffsetY = (_content.ActualHeight - ViewportHeight / InternalViewportZoom) / 2;
break;
case ZoomAndPanInitialPositionEnum.OneHundredPercentCentered:
InternalViewportZoom = 1.0;
ContentOffsetX = (_content.ActualWidth - ViewportWidth) / 2;
ContentOffsetY = (_content.ActualHeight - ViewportHeight) / 2;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}