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(); } } } }