using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace Bro.Common.ImageCanvas { /// /// A class that wraps up zooming and panning of it's content. /// public class ZoomAndPanViewBox : ContentControl { #region local fields /// /// The control for creating a drag border /// private Border _dragBorder; /// /// The control for creating a drag border /// private Border _sizingBorder; /// /// The control for containing a zoom border /// private Canvas _viewportCanvas; /// /// Specifies the current state of the mouse handling logic. /// private MouseHandlingModeEnum _mouseHandlingMode = MouseHandlingModeEnum.None; /// /// The point that was clicked relative to the content that is contained within the ZoomAndPanControl. /// private Point _origContentMouseDownPoint; #endregion #region constructor and overrides /// /// Static constructor to define metadata for the control (and link it to the style in Generic.xaml). /// static ZoomAndPanViewBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(ZoomAndPanViewBox), new FrameworkPropertyMetadata(typeof(ZoomAndPanViewBox))); } public override void OnApplyTemplate() { base.OnApplyTemplate(); _dragBorder = Template.FindName("PART_DraggingBorder", this) as Border; _sizingBorder = Template.FindName("PART_SizingBorder", this) as Border; _viewportCanvas = Template.FindName("PART_Content", this) as Canvas; SetBackground(Visual); } protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { base.OnRenderSizeChanged(sizeInfo); if (ActualWidth > 0 && _viewportCanvas != null) { _sizingBorder.BorderThickness = _dragBorder.BorderThickness = new Thickness( _viewportCanvas.ActualWidth / ActualWidth * BorderThickness.Left, _viewportCanvas.ActualWidth / ActualWidth * BorderThickness.Top, _viewportCanvas.ActualWidth / ActualWidth * BorderThickness.Right, _viewportCanvas.ActualWidth / ActualWidth * BorderThickness.Bottom); } } #endregion #region Mouse Event Handlers /// /// Event raised on mouse down in the ZoomAndPanControl. /// protected override void OnMouseDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); GetZoomAndPanControl().SaveZoom(); _mouseHandlingMode = MouseHandlingModeEnum.Panning; _origContentMouseDownPoint = e.GetPosition(_viewportCanvas); if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0) { // Shift + left- or right-down initiates zooming mode. _mouseHandlingMode = MouseHandlingModeEnum.DragZooming; _dragBorder.Visibility = Visibility.Hidden; _sizingBorder.Visibility = Visibility.Visible; Canvas.SetLeft(_sizingBorder, _origContentMouseDownPoint.X); Canvas.SetTop(_sizingBorder, _origContentMouseDownPoint.Y); _sizingBorder.Width = 0; _sizingBorder.Height = 0; } else { // 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. _viewportCanvas.CaptureMouse(); e.Handled = true; } } /// /// Event raised on mouse up in the ZoomAndPanControl. /// protected override void OnMouseUp(MouseButtonEventArgs e) { base.OnMouseLeftButtonUp(e); if (_mouseHandlingMode == MouseHandlingModeEnum.DragZooming) { var zoomAndPanControl = GetZoomAndPanControl(); var curContentPoint = e.GetPosition(_viewportCanvas); var rect = ViewportHelpers.Clip(curContentPoint, _origContentMouseDownPoint, new Point(0, 0), new Point(_viewportCanvas.Width, _viewportCanvas.Height)); zoomAndPanControl.AnimatedZoomTo(rect); _dragBorder.Visibility = Visibility.Visible; _sizingBorder.Visibility = Visibility.Hidden; } _mouseHandlingMode = MouseHandlingModeEnum.None; _viewportCanvas.ReleaseMouseCapture(); e.Handled = true; } /// /// Event raised on mouse move in the ZoomAndPanControl. /// protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (_mouseHandlingMode == MouseHandlingModeEnum.Panning) { var curContentPoint = e.GetPosition(_viewportCanvas); var rectangleDragVector = curContentPoint - _origContentMouseDownPoint; // // When in 'dragging rectangles' mode update the position of the rectangle as the user drags it. // _origContentMouseDownPoint = e.GetPosition(_viewportCanvas).Clamp(); Canvas.SetLeft(_dragBorder, Canvas.GetLeft(_dragBorder) + rectangleDragVector.X); Canvas.SetTop(_dragBorder, Canvas.GetTop(_dragBorder) + rectangleDragVector.Y); } else if (_mouseHandlingMode == MouseHandlingModeEnum.DragZooming) { var curContentPoint = e.GetPosition(_viewportCanvas); var rect = ViewportHelpers.Clip(curContentPoint, _origContentMouseDownPoint, new Point(0, 0), new Point(_viewportCanvas.Width, _viewportCanvas.Height)); ViewportHelpers.PositionBorderOnCanvas(_sizingBorder, rect); } e.Handled = true; } /// /// Event raised with the double click command /// protected override void OnMouseDoubleClick(MouseButtonEventArgs e) { base.OnMouseDoubleClick(e); if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0) { var zoomAndPanControl = GetZoomAndPanControl(); zoomAndPanControl.SaveZoom(); zoomAndPanControl.AnimatedSnapTo(e.GetPosition(_viewportCanvas)); } } #endregion #region Background--Visual Brush /// /// The X coordinate of the content focus, this is the point that we are focusing on when zooming. /// public FrameworkElement Visual { get { return (FrameworkElement)GetValue(VisualProperty); } set { SetValue(VisualProperty, value); } } public static readonly DependencyProperty VisualProperty = DependencyProperty.Register("Visual", typeof(FrameworkElement), typeof(ZoomAndPanViewBox), new FrameworkPropertyMetadata(null, OnVisualChanged)); private static void OnVisualChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var c = (ZoomAndPanViewBox)d; c.SetBackground(e.NewValue as FrameworkElement); } private void SetBackground(FrameworkElement frameworkElement) { frameworkElement = frameworkElement ?? (DataContext as ContentControl)?.Content as FrameworkElement; var visualBrush = new VisualBrush { Visual = frameworkElement, ViewboxUnits = BrushMappingMode.RelativeToBoundingBox, ViewportUnits = BrushMappingMode.RelativeToBoundingBox, TileMode = TileMode.None, Stretch = Stretch.Fill }; if (frameworkElement != null) frameworkElement.SizeChanged += (s, e) => { _viewportCanvas.Height = frameworkElement.ActualHeight; _viewportCanvas.Width = frameworkElement.ActualWidth; _viewportCanvas.Background = visualBrush; }; } #endregion private ZoomAndPanControl GetZoomAndPanControl() { var zoomAndPanControl = (DataContext as ZoomAndPanControl) ?? (DataContext as ZoomAndPanScrollViewer)?.ZoomAndPanContent; if (zoomAndPanControl == null) throw new NullReferenceException("DataContext is not of type ZoomAndPanControl"); return zoomAndPanControl; } } }