前言
接着上周写的截图控件继续更新 缩放操作。
1.WPF实现截屏「仿微信」
2.WPF 实现截屏控件之移动(二)「仿微信」
正文

实现拉伸放大或缩小缩放操作需在矩形四个方向绘制8个Thumb,这里有两种方式
1)可以自行在XAML中硬编写8个Thumb
2)使用装饰器Adorner
本章使用了第二种方式
一、首先新建个项目,然后创建个自定义控件,命名为ScreenCutAdorner,然后让它继承Adorner。
1.1
在装饰器中定义
8个Thumb,对应8个方位点:- const double THUMB_SIZE = 15;
- const double MINIMAL_SIZE = 20;
- Thumb lc, tl, tc, tr, rc, br, bc, bl;
- VisualCollection visCollec;
- public ScreenCutAdorner(UIElement adorned): base(adorned)
- {
- visCollec = new VisualCollection(this);
- visCollec.Add(lc = GetResizeThumb(Cursors.SizeWE, HorizontalAlignment.Left, VerticalAlignment.Center));
- visCollec.Add(tl = GetResizeThumb(Cursors.SizeNWSE, HorizontalAlignment.Left, VerticalAlignment.Top));
- visCollec.Add(tc = GetResizeThumb(Cursors.SizeNS, HorizontalAlignment.Center, VerticalAlignment.Top));
- visCollec.Add(tr = GetResizeThumb(Cursors.SizeNESW, HorizontalAlignment.Right, VerticalAlignment.Top));
- visCollec.Add(rc = GetResizeThumb(Cursors.SizeWE, HorizontalAlignment.Right, VerticalAlignment.Center));
- visCollec.Add(br = GetResizeThumb(Cursors.SizeNWSE, HorizontalAlignment.Right, VerticalAlignment.Bottom));
- visCollec.Add(bc = GetResizeThumb(Cursors.SizeNS, HorizontalAlignment.Center, VerticalAlignment.Bottom));
- visCollec.Add(bl = GetResizeThumb(Cursors.SizeNESW, HorizontalAlignment.Left, VerticalAlignment.Bottom));
- }
复制代码 1.2
重写 ArrangeOverride
在派生类中重写时,为 FrameworkElement派生类定位子元素并确定大小:- protected override Visual GetVisualChild(int index);
- protected override int VisualChildrenCount{get;}
- protected override Size ArrangeOverride(Size finalSize)
- {
- double offset = THUMB_SIZE / 2;
- Size sz = new Size(THUMB_SIZE, THUMB_SIZE);
- lc.Arrange(new Rect(new Point(-offset, AdornedElement.RenderSize.Height / 2 - offset), sz));
- tl.Arrange(new Rect(new Point(-offset, -offset), sz));
- tc.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width / 2 - offset, -offset), sz));
- tr.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width - offset, -offset), sz));
- rc.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width - offset, AdornedElement.RenderSize.Height / 2 - offset), sz));
- br.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width - offset, AdornedElement.RenderSize.Height - offset), sz));
- bc.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width / 2 - offset, AdornedElement.RenderSize.Height - offset), sz));
- bl.Arrange(new Rect(new Point(-offset, AdornedElement.RenderSize.Height - offset), sz));
- return finalSize;
- }
复制代码 1.3
ScreenCutAdorner
完整代码如下:- using System;using System.Windows;using System.Windows.Controls;using System.Windows.Controls.Primitives;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Shapes;namespace WPFDevelopers.Controls{ public class ScreenCutAdorner : Adorner { const double THUMB_SIZE = 15;
- const double MINIMAL_SIZE = 20;
- Thumb lc, tl, tc, tr, rc, br, bc, bl;
- VisualCollection visCollec;
- public ScreenCutAdorner(UIElement adorned): base(adorned)
- {
- visCollec = new VisualCollection(this);
- visCollec.Add(lc = GetResizeThumb(Cursors.SizeWE, HorizontalAlignment.Left, VerticalAlignment.Center));
- visCollec.Add(tl = GetResizeThumb(Cursors.SizeNWSE, HorizontalAlignment.Left, VerticalAlignment.Top));
- visCollec.Add(tc = GetResizeThumb(Cursors.SizeNS, HorizontalAlignment.Center, VerticalAlignment.Top));
- visCollec.Add(tr = GetResizeThumb(Cursors.SizeNESW, HorizontalAlignment.Right, VerticalAlignment.Top));
- visCollec.Add(rc = GetResizeThumb(Cursors.SizeWE, HorizontalAlignment.Right, VerticalAlignment.Center));
- visCollec.Add(br = GetResizeThumb(Cursors.SizeNWSE, HorizontalAlignment.Right, VerticalAlignment.Bottom));
- visCollec.Add(bc = GetResizeThumb(Cursors.SizeNS, HorizontalAlignment.Center, VerticalAlignment.Bottom));
- visCollec.Add(bl = GetResizeThumb(Cursors.SizeNESW, HorizontalAlignment.Left, VerticalAlignment.Bottom));
- } protected override Size ArrangeOverride(Size finalSize) { double offset = THUMB_SIZE / 2; Size sz = new Size(THUMB_SIZE, THUMB_SIZE); lc.Arrange(new Rect(new Point(-offset, AdornedElement.RenderSize.Height / 2 - offset), sz)); tl.Arrange(new Rect(new Point(-offset, -offset), sz)); tc.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width / 2 - offset, -offset), sz)); tr.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width - offset, -offset), sz)); rc.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width - offset, AdornedElement.RenderSize.Height / 2 - offset), sz)); br.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width - offset, AdornedElement.RenderSize.Height - offset), sz)); bc.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width / 2 - offset, AdornedElement.RenderSize.Height - offset), sz)); bl.Arrange(new Rect(new Point(-offset, AdornedElement.RenderSize.Height - offset), sz)); return finalSize; } void Resize(FrameworkElement ff) { if (Double.IsNaN(ff.Width)) ff.Width = ff.RenderSize.Width; if (Double.IsNaN(ff.Height)) ff.Height = ff.RenderSize.Height; } Thumb GetResizeThumb(Cursor cur, HorizontalAlignment hor, VerticalAlignment ver) { var thumb = new Thumb() { Width = THUMB_SIZE, Height = THUMB_SIZE, HorizontalAlignment = hor, VerticalAlignment = ver, Cursor = cur, Template = new ControlTemplate(typeof(Thumb)) { VisualTree = GetFactory(new SolidColorBrush(Colors.White)) } }; thumb.DragDelta += (s, e) => { var element = AdornedElement as FrameworkElement; if (element == null) return; Resize(element); switch (thumb.VerticalAlignment) { case VerticalAlignment.Bottom: if (element.Height + e.VerticalChange > MINIMAL_SIZE) element.Height += e.VerticalChange; break; case VerticalAlignment.Top: if (element.Height - e.VerticalChange > MINIMAL_SIZE) { element.Height -= e.VerticalChange; Canvas.SetTop(element, Canvas.GetTop(element) + e.VerticalChange); } break; } switch (thumb.HorizontalAlignment) { case HorizontalAlignment.Left: if (element.Width - e.HorizontalChange > MINIMAL_SIZE) { element.Width -= e.HorizontalChange; Canvas.SetLeft(element, Canvas.GetLeft(element) + e.HorizontalChange); } break; case HorizontalAlignment.Right: if (element.Width + e.HorizontalChange > MINIMAL_SIZE) element.Width += e.HorizontalChange; break; } e.Handled = true; }; return thumb; } FrameworkElementFactory GetFactory(Brush back) { var fef = new FrameworkElementFactory(typeof(Ellipse)); fef.SetValue(Ellipse.FillProperty, back); fef.SetValue(Ellipse.StrokeProperty, DrawingContextHelper.Brush); fef.SetValue(Ellipse.StrokeThicknessProperty, (double)2); return fef; } protected override Visual GetVisualChild(int index) { return visCollec[index]; } protected override int VisualChildrenCount { get { return visCollec.Count; } } }}
复制代码 二、找到之前的自定义控件ScreenCut修改为Border创建装饰器层
AdornerLayer.GetAdornerLayer(_border);
接着给装饰层添加装饰器 adornerLayer.Add(screenCutAdorner);
2.1 ScreenCut监听Border的大小变化修改四个矩形的大小与位置:- private void _border_SizeChanged(object sender, SizeChangedEventArgs e)
- {
- var left = Canvas.GetLeft(_border);
- var top = Canvas.GetTop(_border);
- var beignPoint = new Point(left, top);
- var endPoint = new Point(left + _border.ActualWidth, top + _border.ActualHeight);
- rect = new Rect(beignPoint, endPoint);
- pointStart = beignPoint;
- MoveAllRectangle(endPoint);
- WrapPanelPosition();
- }
复制代码 2.2 ScreenCut完整代码如下:
[code]using Microsoft.Win32;using System;using System.Windows;using System.Windows.Controls;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Interop;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;using WPFDevelopers.Helpers;namespace WPFDevelopers.Controls{ public enum ScreenCutMouseType { Default, DrawMouse, MoveMouse, } [TemplatePart(Name = CanvasTemplateName, Type = typeof(Canvas))] [TemplatePart(Name = RectangleLeftTemplateName, Type = typeof(Rectangle))] [TemplatePart(Name = RectangleTopTemplateName, Type = typeof(Rectangle))] [TemplatePart(Name = RectangleRightTemplateName, Type = typeof(Rectangle))] [TemplatePart(Name = RectangleBottomTemplateName, Type = typeof(Rectangle))] [TemplatePart(Name = BorderTemplateName, Type = typeof(Border))] [TemplatePart(Name = WrapPanelTemplateName, Type = typeof(WrapPanel))] [TemplatePart(Name = ButtonSaveTemplateName, Type = typeof(Button))] [TemplatePart(Name = ButtonCancelTemplateName, Type = typeof(Button))] [TemplatePart(Name = ButtonCompleteTemplateName, Type = typeof(Button))] public class ScreenCut : Window { private const string CanvasTemplateName = "PART_Canvas"; private const string RectangleLeftTemplateName = "PART_RectangleLeft"; private const string RectangleTopTemplateName = "PART_RectangleTop"; private const string RectangleRightTemplateName = "PART_RectangleRight"; private const string RectangleBottomTemplateName = "PART_RectangleBottom"; private const string BorderTemplateName = "PART_Border"; private const string WrapPanelTemplateName = "PART_WrapPanel"; private const string ButtonSaveTemplateName = "PART_ButtonSave"; private const string ButtonCancelTemplateName = "PART_ButtonCancel"; private const string ButtonCompleteTemplateName = "PART_ButtonComplete"; private Canvas _canvas; private Rectangle _rectangleLeft, _rectangleTop, _rectangleRight, _rectangleBottom; private Border _border; private WrapPanel _wrapPanel; private Button _buttonSave, _buttonCancel, _buttonComplete; private Rect rect; private Point pointStart, pointEnd; private bool isMouseUp = false; private Win32ApiHelper.DeskTopSize size; private ScreenCutMouseType screenCutMouseType = ScreenCutMouseType.Default; private AdornerLayer adornerLayer; private ScreenCutAdorner screenCutAdorner; static ScreenCut() { DefaultStyleKeyProperty.OverrideMetadata(typeof(ScreenCut), new FrameworkPropertyMetadata(typeof(ScreenCut))); } public override void OnApplyTemplate() { base.OnApplyTemplate(); _canvas = GetTemplateChild(CanvasTemplateName) as Canvas; _rectangleLeft = GetTemplateChild(RectangleLeftTemplateName) as Rectangle; _rectangleTop = GetTemplateChild(RectangleTopTemplateName) as Rectangle; _rectangleRight = GetTemplateChild(RectangleRightTemplateName) as Rectangle; _rectangleBottom = GetTemplateChild(RectangleBottomTemplateName) as Rectangle; _border = GetTemplateChild(BorderTemplateName) as Border; _border.MouseLeftButtonDown += _border_MouseLeftButtonDown; _wrapPanel = GetTemplateChild(WrapPanelTemplateName) as WrapPanel; _buttonSave = GetTemplateChild(ButtonSaveTemplateName) as Button; if (_buttonSave != null) _buttonSave.Click += _buttonSave_Click; _buttonCancel = GetTemplateChild(ButtonCancelTemplateName) as Button; if (_buttonCancel != null) _buttonCancel.Click += _buttonCancel_Click; _buttonComplete = GetTemplateChild(ButtonCompleteTemplateName) as Button; if (_buttonComplete != null) _buttonComplete.Click += _buttonComplete_Click; _canvas.Background = new ImageBrush(Capture()); _rectangleLeft.Width = _canvas.Width; _rectangleLeft.Height = _canvas.Height; } public ScreenCut() { Loaded += (s,e)=> { adornerLayer = AdornerLayer.GetAdornerLayer(_border); screenCutAdorner = new ScreenCutAdorner(_border); adornerLayer.Add(screenCutAdorner); _border.SizeChanged += _border_SizeChanged; }; } private void _border_SizeChanged(object sender, SizeChangedEventArgs e) { var left = Canvas.GetLeft(_border); var top = Canvas.GetTop(_border); var beignPoint = new Point(left, top); var endPoint = new Point(left + _border.ActualWidth, top + _border.ActualHeight); rect = new Rect(beignPoint, endPoint); pointStart = beignPoint; MoveAllRectangle(endPoint); WrapPanelPosition(); } private void _border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if(screenCutMouseType == ScreenCutMouseType.Default) screenCutMouseType = ScreenCutMouseType.MoveMouse; } private void _buttonSave_Click(object sender, RoutedEventArgs e) { SaveFileDialog dlg = new SaveFileDialog(); dlg.FileName = $"WPFDevelopers{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg"; dlg.DefaultExt = ".jpg"; dlg.Filter = "image file|*.jpg"; if (dlg.ShowDialog() == true) { BitmapEncoder pngEncoder = new PngBitmapEncoder(); pngEncoder.Frames.Add(BitmapFrame.Create(CutBitmap())); using (var fs = System.IO.File.OpenWrite(dlg.FileName)) { pngEncoder.Save(fs); fs.Dispose(); fs.Close(); } } Close(); } private void _buttonComplete_Click(object sender, RoutedEventArgs e) { Clipboard.SetImage(CutBitmap()); Close(); } CroppedBitmap CutBitmap() { _border.Visibility = Visibility.Collapsed; _rectangleLeft.Visibility = Visibility.Collapsed; _rectangleTop.Visibility = Visibility.Collapsed; _rectangleRight.Visibility = Visibility.Collapsed; _rectangleBottom.Visibility = Visibility.Collapsed; var renderTargetBitmap = new RenderTargetBitmap((int)_canvas.Width, (int)_canvas.Height, 96d, 96d, System.Windows.Media.PixelFormats.Default); renderTargetBitmap.Render(_canvas); return new CroppedBitmap(renderTargetBitmap, new Int32Rect((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height)); } private void _buttonCancel_Click(object sender, RoutedEventArgs e) { Close(); } protected override void OnPreviewKeyDown(KeyEventArgs e) { if (e.Key == Key.Escape) Close(); } protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) { pointStart = e.GetPosition(_canvas); if (!isMouseUp) { screenCutMouseType = ScreenCutMouseType.DrawMouse; _wrapPanel.Visibility = Visibility.Hidden; pointEnd = pointStart; rect = new Rect(pointStart, pointEnd); } } protected override void OnPreviewMouseMove(MouseEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed) { var current = e.GetPosition(_canvas); switch (screenCutMouseType) { case ScreenCutMouseType.DrawMouse: MoveAllRectangle(current); break; case ScreenCutMouseType.MoveMouse: MoveRect(current); break; default: break; } } } void MoveRect(Point current) { if (current != pointStart) { Console.WriteLine($"current:{current}"); Console.WriteLine($"pointStart:{pointStart}"); var vector = Point.Subtract(current, pointStart); var left = Canvas.GetLeft(_border) + vector.X; var top = Canvas.GetTop(_border) + vector.Y; Console.WriteLine($"left:{left}"); if (left = _canvas.ActualHeight) top = _canvas.ActualHeight - _border.ActualHeight; pointStart = current; Canvas.SetLeft(_border, left); Canvas.SetTop(_border, top); rect = new Rect(new Point(left, top), new Point(left + _border.Width, top + _border.Height)); _rectangleLeft.Height = _canvas.ActualHeight; _rectangleLeft.Width = left = _canvas.ActualWidth ? _canvas.ActualWidth : left; Canvas.SetLeft(_rectangleTop, _rectangleLeft.Width); _rectangleTop.Height = top = _canvas.ActualHeight ? _canvas.ActualHeight : top; Canvas.SetLeft(_rectangleRight, left + _border.Width); var wRight = _canvas.ActualWidth - (_border.Width + _rectangleLeft.Width); _rectangleRight.Width = wRight |