前言 接著上周寫的截圖控制項繼續更新 縮放操作。 1.WPF實現截屏「仿微信」 2.WPF 實現截屏控制項之移動(二)「仿微信」 正文 實現拉伸放大或縮小縮放操作需在矩形四個方向繪製8個Thumb,這裡有兩種方式 1)可以自行在XAML中硬編寫8個Thumb 2)使用裝飾器Adorner 本章使用了第二 ...
前言
接著上周寫的截圖控制項繼續更新 縮放操作。
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;
}
}
}
三、運行效果如下