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