本例中的大图模式使用图片控件展示,监听控件的鼠标滚轮事件和移动事件,缩略图和鹰眼模式采用装饰器对象IndicatorObject和Canvas布局。百分比使用一个定时器,根据图片的放大倍数计算具体的数值显示。
首先看看效果图:
以下开始绘制图片 定义缩略图上白色的矩形,这其实是一个Indicator,它的外围是一个Canvas,然后缩略图是一个Image控件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
internal class IndicatorObject : ContentControl { private MaskCanvas canvasOwner; public IndicatorObject(MaskCanvas canvasOwner) { this.canvasOwner = canvasOwner; } static IndicatorObject() { var ownerType = typeof(IndicatorObject); FocusVisualStyleProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(null)); DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ownerType)); MinWidthProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(5.0)); MinHeightProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(5.0)); } public void Move(System.Windows.Point offset) { var x = Canvas.GetLeft(this) + offset.X; var y = Canvas.GetTop(this) + offset.Y; x = x < 0 ? 0 : x; y = y < 0 ? 0 : y; x = Math.Min(x, this.canvasOwner.Width - this.Width); y = Math.Min(y, this.canvasOwner.Height - this.Height); Canvas.SetLeft(this, x); Canvas.SetTop(this, y); canvasOwner.UpdateSelectionRegion(new Rect(x, y, Width, Height), true); } }
查看位置所在的矩形定义好了,然后开始定义外围的Canvas,这个作用是可以在Canvas上选中移动到查看的位置
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class MaskCanvas : Canvas { public MaskCanvas() { Loaded += OnLoaded; } public System.Windows.Media.Brush SelectionBorderBrush = new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 255, 255, 255)); public Thickness SelectionBorderThickness = new Thickness(1); public System.Windows.Media.Brush MaskWindowBackground = new SolidColorBrush(System.Windows.Media.Color.FromArgb(5, 0, 0, 0)); public event EventHandler<LoactionArgs> LoationChanged; private void OnLoaded(object sender, RoutedEventArgs e) { maskRectLeft.Fill = maskRectRight.Fill = maskRectTop.Fill = maskRectBottom.Fill = MaskWindowBackground; SetLeft(maskRectLeft, 0); SetTop(maskRectLeft, 0); SetRight(maskRectRight, 0); SetTop(maskRectRight, 0); SetTop(maskRectTop, 0); SetBottom(maskRectBottom, 0); maskRectLeft.Height = ActualHeight; Children.Add(maskRectLeft); Children.Add(maskRectRight); Children.Add(maskRectTop); Children.Add(maskRectBottom); selectionBorder.Stroke = SelectionBorderBrush; selectionBorder.StrokeThickness = 1; Children.Add(selectionBorder); indicator = new IndicatorObject(this); indicator.Visibility = System.Windows.Visibility.Hidden; Children.Add(indicator); CompositionTarget.Rendering += OnCompositionTargetRendering; } private void UpdateSelectionBorderLayout() { if (!selectionRegion.IsEmpty) { SetLeft(selectionBorder, selectionRegion.Left); SetTop(selectionBorder, selectionRegion.Top); selectionBorder.Width = selectionRegion.Width; selectionBorder.Height = selectionRegion.Height; } } private void UpdateMaskRectanglesLayout() { var actualHeight = ActualHeight; var actualWidth = ActualWidth; if (selectionRegion.IsEmpty) { SetLeft(maskRectLeft, 0); SetTop(maskRectLeft, 0); maskRectLeft.Width = actualWidth; maskRectLeft.Height = actualHeight; maskRectRight.Width = maskRectRight.Height = maskRectTop.Width = maskRectTop.Height = maskRectBottom.Width = maskRectBottom.Height = 0; } else { var temp = selectionRegion.Left; if (maskRectLeft.Width != temp) { maskRectLeft.Width = temp < 0 ? 0 : temp; //Math.Max(0, selectionRegion.Left); } temp = ActualWidth - selectionRegion.Right; if (maskRectRight.Width != temp) { maskRectRight.Width = temp < 0 ? 0 : temp; //Math.Max(0, ActualWidth - selectionRegion.Right); } if (maskRectRight.Height != actualHeight) { maskRectRight.Height = actualHeight; } SetLeft(maskRectTop, maskRectLeft.Width); SetLeft(maskRectBottom, maskRectLeft.Width); temp = actualWidth - maskRectLeft.Width - maskRectRight.Width; if (maskRectTop.Width != temp) { maskRectTop.Width = temp < 0 ? 0 : temp; //Math.Max(0, ActualWidth - maskRectLeft.Width - maskRectRight.Width); } temp = selectionRegion.Top; if (maskRectTop.Height != temp) { maskRectTop.Height = temp < 0 ? 0 : temp; //Math.Max(0, selectionRegion.Top); } maskRectBottom.Width = maskRectTop.Width; temp = actualHeight - selectionRegion.Bottom; if (maskRectBottom.Height != temp) { maskRectBottom.Height = temp < 0 ? 0 : temp; //Math.Max(0, ActualHeight - selectionRegion.Bottom); } } } #region Fileds & Props private Rect selectionRegion = Rect.Empty; private bool isMaskDraging; public bool MoveState = false; private IndicatorObject indicator; private System.Windows.Point? selectionStartPoint; private System.Windows.Point? selectionEndPoint; private readonly System.Windows.Shapes.Rectangle selectionBorder = new System.Windows.Shapes.Rectangle(); private readonly System.Windows.Shapes.Rectangle maskRectLeft = new System.Windows.Shapes.Rectangle(); private readonly System.Windows.Shapes.Rectangle maskRectRight = new System.Windows.Shapes.Rectangle(); private readonly System.Windows.Shapes.Rectangle maskRectTop = new System.Windows.Shapes.Rectangle(); private readonly System.Windows.Shapes.Rectangle maskRectBottom = new System.Windows.Shapes.Rectangle(); public System.Drawing.Size? DefaultSize { get; set; } #endregion #region Mouse Managment private bool IsMouseOnThis(RoutedEventArgs e) { return e.Source.Equals(this) || e.Source.Equals(maskRectLeft) || e.Source.Equals(maskRectRight) || e.Source.Equals(maskRectTop) || e.Source.Equals(maskRectBottom); } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { indicator.Visibility = System.Windows.Visibility.Visible; if (e.Source.Equals(indicator)) { HandleIndicatorMouseDown(e); } base.OnMouseLeftButtonDown(e); } protected override void OnMouseMove(MouseEventArgs e) { if (IsMouseOnThis(e)) { UpdateSelectionRegion(e, UpdateMaskType.ForMouseMoving); e.Handled = true; } base.OnMouseMove(e); } protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { if (IsMouseOnThis(e)) { UpdateSelectionRegion(e, UpdateMaskType.ForMouseLeftButtonUp); FinishShowMask(); } base.OnMouseLeftButtonUp(e); } protected override void OnMouseRightButtonUp(MouseButtonEventArgs e) { indicator.Visibility = Visibility.Collapsed; selectionRegion = Rect.Empty; selectionBorder.Width = selectionBorder.Height = 0; // ClearSelectionData(); UpdateMaskRectanglesLayout(); base.OnMouseRightButtonUp(e); } internal void HandleIndicatorMouseDown(MouseButtonEventArgs e) { MoveState = true; } internal void HandleIndicatorMouseUp(MouseButtonEventArgs e) { MoveState = false; } private void PrepareShowMask(System.Drawing.Point mouseLoc) { indicator.Visibility = Visibility.Collapsed; selectionBorder.Visibility = Visibility.Visible; } private void UpdateSelectionRegion() { var startPoint = new System.Drawing.Point(0,0); var endPoint = new System.Drawing.Point(190, 130); var sX = startPoint.X; var sY = startPoint.Y; var eX = endPoint.X; var eY = endPoint.Y; var deltaX = eX - sX; var deltaY = eY - sY; if (Math.Abs(deltaX) >= SystemParameters.MinimumHorizontalDragDistance || Math.Abs(deltaX) >= SystemParameters.MinimumVerticalDragDistance) { double x = sX < eX ? sX : eX;//Math.Min(sX, eX); double y = sY < eY ? sY : eY;//Math.Min(sY, eY); double w = deltaX < 0 ? -deltaX : deltaX;//Math.Abs(deltaX); double h = deltaY < 0 ? -deltaY : deltaY;//Math.Abs(deltaY); selectionRegion = new Rect(x, y, w, h); } else { selectionRegion = new Rect(startPoint.X, startPoint.Y, DefaultSize.Value.Width, DefaultSize.Value.Height); } } private void UpdateSelectionRegion(MouseEventArgs e, UpdateMaskType updateType) { if (updateType == UpdateMaskType.ForMouseMoving && e.LeftButton != MouseButtonState.Pressed) { selectionStartPoint = null; } if (selectionStartPoint.HasValue) { selectionEndPoint = e.GetPosition(this); var startPoint = (System.Windows.Point)selectionEndPoint; var endPoint = (System.Windows.Point)selectionStartPoint; var sX = startPoint.X; var sY = startPoint.Y; var eX = endPoint.X; var eY = endPoint.Y; var deltaX = eX - sX; var deltaY = eY - sY; if (Math.Abs(deltaX) >= SystemParameters.MinimumHorizontalDragDistance || Math.Abs(deltaX) >= SystemParameters.MinimumVerticalDragDistance) { isMaskDraging = true; double x = sX < eX ? sX : eX;//Math.Min(sX, eX); double y = sY < eY ? sY : eY;//Math.Min(sY, eY); double w = deltaX < 0 ? -deltaX : deltaX;//Math.Abs(deltaX); double h = deltaY < 0 ? -deltaY : deltaY;//Math.Abs(deltaY); selectionRegion = new Rect(x, y, w, h); } else { if (DefaultSize.HasValue && updateType == UpdateMaskType.ForMouseLeftButtonUp) { isMaskDraging = true; selectionRegion = new Rect(startPoint.X, startPoint.Y, DefaultSize.Value.Width, DefaultSize.Value.Height); } else { isMaskDraging = false; } } } UpdateIndicator(selectionRegion); } internal void UpdateSelectionRegion(Rect region, bool flag = false) { selectionRegion = region; UpdateIndicator(selectionRegion); if (LoationChanged != null && flag) { LoationChanged(this, new LoactionArgs(region.Left/this.Width, region.Top/this.Height)); } } private void FinishShowMask() { if (IsMouseCaptured) { ReleaseMouseCapture(); } if (isMaskDraging) { UpdateIndicator(selectionRegion); ClearSelectionData(); } } private void ClearSelectionData() { isMaskDraging = false; selectionBorder.Visibility = Visibility.Collapsed; selectionStartPoint = null; selectionEndPoint = null; } private void UpdateIndicator(Rect region) { if (indicator == null) return; if (region.Width < indicator.MinWidth || region.Height < indicator.MinHeight) { return; } indicator.Visibility = Visibility.Visible; indicator.Width = region.Width; indicator.Height = region.Height; SetLeft(indicator, region.Left); SetTop(indicator, region.Top); } private Rect GetIndicatorRegion() { return new Rect(GetLeft(indicator), GetTop(indicator), indicator.ActualWidth, indicator.ActualHeight); } #endregion #region Render private void OnCompositionTargetRendering(object sender, EventArgs e) { UpdateSelectionBorderLayout(); UpdateMaskRectanglesLayout(); } #endregion #region inner types private enum UpdateMaskType { ForMouseMoving, ForMouseLeftButtonUp } #endregion }
缩略图很简单,按照比例缩放图片加载上即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
thumbImage = m_Bitmap.GetThumbnailImage(thumbWidth, thumbHeight, null, IntPtr.Zero) as Bitmap; //thumbWidth指定宽,thumbHeight指定高度
然后我们为大图加上监听事件ScrollChanged和MouseWheel 以及MouseLeftButtonDown、MouseLeftButtonUp、 MouseMove
ScrollChanged用来计算显示的滚动区域范围
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
if (e.ExtentHeight > e.ViewportHeight || e.ExtentWidth > e.ViewportWidth) { offsetX = (e.ExtentWidth - e.ViewportWidth) / 2; offsetY = (e.ExtentHeight - e.ViewportHeight) / 2; svImg.ScrollToVerticalOffset(offsetY); svImg.ScrollToHorizontalOffset(offsetX); } double timeH = svImg.ViewportHeight/ (svImg.ViewportHeight + svImg.ScrollableHeight); double timeW = svImg.ViewportWidth / (svImg.ViewportWidth + svImg.ScrollableWidth); double w = thumbWidth * timeW; double h = thumbHeight * timeH; double offsetx = 0; double offsety = 0; if (svImg.ScrollableWidth == 0) { offsetx = 0; } else { offsetx = (w - thumbWidth) / svImg.ScrollableWidth * svImg.HorizontalOffset; } if (svImg.ScrollableHeight == 0) { offsety = 0; } else { offsety = (h - thumbHeight) / svImg.ScrollableHeight * svImg.VerticalOffset; } Rect rect = new Rect( - offsetx, - offsety, w, h); mask.UpdateSelectionRegion(rect);
MouseWheel计算滚动比例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
var mosePos = e.GetPosition(img); scale = scale * (e.Delta > 0 ? 1.2 : 1 / 1.2); scale = Math.Max(scale, 0.15); scale = Math.Min(16, scale); this.txtZoom.Text = ((int)(scale * 100)).ToString(); img.Width = scale * imgWidth; img.Height = scale * imgHeight; offsetX = svImg.ScrollableWidth / 2; offsetY = svImg.ScrollableHeight / 2;
MouseLeftButtonDown后三个事件在移动图片时使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
以上大部分的工作已经做完了,然后我们加入一个定时器的功能,调节显示百分比的时间。加上一个Timer类即可。
当然在ScrolChanged里面我们加入了鹰眼监控,细心的朋友可以在事件里面看到。