- A+
所属分类:.NET技术
前言
接着上周写的截图控件继续更新 缩放操作。
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完整代码如下:
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 <= 0) left = 0; if (top <= 0) top = 0; if (left + _border.Width >= _canvas.ActualWidth) left = _canvas.ActualWidth - _border.ActualWidth; if (top + _border.Height >= _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 <= 0 ? 0 : left >= _canvas.ActualWidth ? _canvas.ActualWidth : left; Canvas.SetLeft(_rectangleTop, _rectangleLeft.Width); _rectangleTop.Height = top <= 0 ? 0 : top >= _canvas.ActualHeight ? _canvas.ActualHeight : top; Canvas.SetLeft(_rectangleRight, left + _border.Width); var wRight = _canvas.ActualWidth - (_border.Width + _rectangleLeft.Width); _rectangleRight.Width = wRight <= 0 ? 0 : wRight; _rectangleRight.Height = _canvas.ActualHeight; Canvas.SetLeft(_rectangleBottom, _rectangleLeft.Width); Canvas.SetTop(_rectangleBottom, top + _border.Height); _rectangleBottom.Width = _border.Width; var hBottom = _canvas.ActualHeight - (top + _border.Height); _rectangleBottom.Height = hBottom <= 0 ? 0 : hBottom; } } protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e) { WrapPanelPosition(); isMouseUp = true; if (screenCutMouseType != ScreenCutMouseType.Default) screenCutMouseType = ScreenCutMouseType.Default; } void WrapPanelPosition() { _wrapPanel.Visibility = Visibility.Visible; Canvas.SetLeft(_wrapPanel, rect.X + rect.Width - _wrapPanel.ActualWidth); var y = Canvas.GetTop(_border) + _border.ActualHeight + _wrapPanel.ActualHeight; if (y > _canvas.ActualHeight) y = Canvas.GetTop(_border) - _wrapPanel.ActualHeight - 8; else y = Canvas.GetTop(_border) + _border.ActualHeight + 8; Canvas.SetTop(_wrapPanel, y); } void MoveAllRectangle(Point current) { pointEnd = current; rect = new Rect(pointStart, pointEnd); _rectangleLeft.Width = rect.X; _rectangleLeft.Height = _canvas.Height; Canvas.SetLeft(_rectangleTop, _rectangleLeft.Width); _rectangleTop.Width = rect.Width; double h = 0.0; if (current.Y < pointStart.Y) h = current.Y; else h = current.Y - rect.Height; _rectangleTop.Height = h; Canvas.SetLeft(_rectangleRight, _rectangleLeft.Width + rect.Width); _rectangleRight.Width = _canvas.Width - (rect.Width + _rectangleLeft.Width); _rectangleRight.Height = _canvas.Height; Canvas.SetLeft(_rectangleBottom, _rectangleLeft.Width); Canvas.SetTop(_rectangleBottom, rect.Height + _rectangleTop.Height); _rectangleBottom.Width = rect.Width; _rectangleBottom.Height = _canvas.Height - (rect.Height + _rectangleTop.Height); _border.Height = rect.Height; _border.Width = rect.Width; Canvas.SetLeft(_border, rect.X); Canvas.SetTop(_border, rect.Y); } BitmapSource Capture() { IntPtr hBitmap; IntPtr hDC = Win32ApiHelper.GetDC(Win32ApiHelper.GetDesktopWindow()); IntPtr hMemDC = Win32ApiHelper.CreateCompatibleDC(hDC); size.cx = Win32ApiHelper.GetSystemMetrics(0); size.cy = Win32ApiHelper.GetSystemMetrics(1); hBitmap = Win32ApiHelper.CreateCompatibleBitmap(hDC, size.cx, size.cy); if (hBitmap != IntPtr.Zero) { IntPtr hOld = (IntPtr)Win32ApiHelper.SelectObject(hMemDC, hBitmap); Win32ApiHelper.BitBlt(hMemDC, 0, 0, size.cx, size.cy, hDC, 0, 0, Win32ApiHelper.TernaryRasterOperations.SRCCOPY); Win32ApiHelper.SelectObject(hMemDC, hOld); Win32ApiHelper.DeleteDC(hMemDC); Win32ApiHelper.ReleaseDC(Win32ApiHelper.GetDesktopWindow(), hDC); var bsource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); Win32ApiHelper.DeleteObject(hBitmap); GC.Collect(); return bsource; } return null; } } }
三、运行效果如下