本例中的大图模式使用图片控件展示,监听控件的鼠标滚轮事件和移动事件,缩略图和鹰眼模式采用装饰器对象IndicatorObject和Canvas布局。百分比使用一个定时器,根据图片的放大倍数计算具体的数值显示。
首先看看效果图:
以下开始绘制图片 定义缩略图上白色的矩形,这其实是一个Indicator,它的外围是一个Canvas,然后缩略图是一个Image控件
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上选中移动到查看的位置
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
}
缩略图很简单,按照比例缩放图片加载上即可
thumbImage = m_Bitmap.GetThumbnailImage(thumbWidth, thumbHeight, null, IntPtr.Zero) as Bitmap; //thumbWidth指定宽,thumbHeight指定高度
然后我们为大图加上监听事件ScrollChanged和MouseWheel 以及MouseLeftButtonDown、MouseLeftButtonUp、 MouseMove
ScrollChanged用来计算显示的滚动区域范围
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计算滚动比例
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后三个事件在移动图片时使用
MouseLeftButtonDown、MouseLeftButtonUp、MouseMove
以上大部分的工作已经做完了,然后我们加入一个定时器的功能,调节显示百分比的时间。加上一个Timer类即可。
当然在ScrolChanged里面我们加入了鹰眼监控,细心的朋友可以在事件里面看到。