wpf截屏系列
第一章 使用GDI+实现截屏
第二章 制作截屏框(本章)
______第一节 使用DockPanel制作截屏框
______第二节 利用Clip属性实现截屏框(本节)
第三章 实现截屏框热键截屏
第四章 实现截屏框实时截屏
第五章 使用ffmpeg命令行实现录屏
文章目录
- wpf截屏系列
- 前言
- 一、实现步骤
-
- 1、Clip穿透
- 2、子控件同步Clip区域
- 3、子控件实现拖动
- 4、子控件实现拖动调整大小
- 5、鼠标事件传递
- 二、完整代码
-
- 1、自行整合
- 2、简化的实现
- 三、效果预览
-
- 1、矩形框
- 2、圆形框
- 总结
前言
第一节已经实现过截屏框,实现方法相对简单,也仅支持矩形框。最近使用wpf的clip时发现了一种用法,可以实现穿透效果。那显然我们基于clip也能实现截屏窗口,而且支持任意形状。
一、实现步骤
1、Clip穿透
使用GeometryGroup 且FillRule为EvenOdd就可以做出穿透的效果。
<Grid> <Grid Width="200" Height="200" Background="SeaGreen" > <Grid.Clip> <GeometryGroup FillRule="EvenOdd"> <!--底下的rect必须保持和容器大小一致--> <RectangleGeometry Rect="0 0 200 200" /> <!--上层形状即为穿透区域--> <RectangleGeometry x:Name="foreRect" Rect="0 0 200 200" RadiusX="100" RadiusY="100"/> </GeometryGroup> </Grid.Clip> </Grid> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text= "Clip区域会穿透" Foreground="Black"></TextBlock> </Grid>
2、子控件同步Clip区域
单纯Clip无法实现拖动和改变大小,尤其是改变大小直接通过鼠标事件结合clip也不好实现。比较简单的方法是定义一个子控件,子控件的位置和大小与Clip关联,这样只要实现子控件的拖动和调整大小功能就能控制Clip区域了。
通过LayoutUpdated事件就可以实时同步Clip区域。子控件就相当于截屏框。
<Border x:Name="clipBorder" LayoutUpdated="Border_LayoutUpdated">
下列代码的GetPosition是自定义拓展方法,自己实现获取控件坐标即可。
private void Border_LayoutUpdated(object sender, EventArgs e) { //截屏框与上层clip rect保持一致 foreRect.Rect = new Rect(clipBorder.GetPosition(), new Size(clipBorder.ActualWidth, clipBorder.ActualHeight));; }
3、子控件实现拖动
参考《wpf拖动系列》,根据不同的容器选择不同实现(直接拷贝网页代码),此处使用第六章的功能简化实现。
<Border x:Name="clipBorder" ac:Move.IsDragMoveable="True" >
4、子控件实现拖动调整大小
参考《wpf拖动调整大小系列》,根据不同的容器选择不同实现(直接拷贝网页代码),此处使用第五章的功能简化实现。
<Border x:Name="clipBorder" ac:Resize.IsDragResizeable="True" >
5、鼠标事件传递
由于使用了Clip穿透,穿透区域的子控件是无法响应鼠标的,有幸的是穿透区域不会影响装饰层,所以我们需要在子控件里定义一个装饰器用于捕获鼠标消息。我们通过《wpf 附加属性实现界面上定义装饰器》简化装饰器的定义。
<Border x:Name="clipBorder"> <!--截屏框的装饰层,用于捕获鼠标消息--> <local:AdornerHelper.AdornerContent> <Grid MouseDown="Grid_MouseDown" Background="Transparent"></Grid> </local:AdornerHelper.AdornerContent> </Border>
private void Grid_MouseDown(object sender, MouseButtonEventArgs e) { //事件转移,触发拖动 clipBorder.RaiseEvent(e); }
二、完整代码
1、自行整合
通过上述步骤,将《wpf拖动系列》、《wpf拖动调整大小系列》、《wpf 附加属性实现界面上定义装饰器》网页上的代码(对应容器类型)整合到一起即可实现所有功能。
2、简化的实现
下列是使用wpf拖动系列第六章和wpf拖动调整大小系列第五章的实现,需要下载。
xaml
<Window x:Class="WpfClip.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfClip" xmlns:ac="clr-namespace:AC" mc:Ignorable="d" WindowStyle="None" Background="Transparent" ResizeMode="NoResize" Topmost="True" WindowState="Maximized" Title="MainWindow" Height="450" Width="800"> <WindowChrome.WindowChrome> <WindowChrome GlassFrameThickness="-1" CaptionHeight="0" /> </WindowChrome.WindowChrome > <Grid x:Name="grid" Background="#80000000" LayoutUpdated="Grid_LayoutUpdated"> <Grid.Clip> <!--利用clip实现穿透--> <GeometryGroup FillRule="EvenOdd"> <RectangleGeometry x:Name="backRect"/> <!--实际的截屏形状--> <RectangleGeometry x:Name="foreRect" /> </GeometryGroup> </Grid.Clip> <!--截屏框--> <Border x:Name="clipBorder" Width="200" Height="200" ac:Move.IsDragMoveable="True" ac:Resize.IsResizeable="True" LayoutUpdated="Border_LayoutUpdated"> <ac:Resize.ThumbsTemplate> <ControlTemplate> <Border Width="16" Height="16" Background=" Green" BorderBrush="White" BorderThickness="2" CornerRadius="8"></Border> </ControlTemplate> </ac:Resize.ThumbsTemplate> <ac:Resize.ThumbsPanel> <ItemsPanelTemplate> <Grid Margin="-8"></Grid> </ItemsPanelTemplate> </ac:Resize.ThumbsPanel> <!--截屏框的装饰层,用于捕获鼠标消息--> <local:AdornerHelper.AdornerContent> <Grid MouseDown="Grid_MouseDown" Background="Transparent"></Grid> </local:AdornerHelper.AdornerContent> </Border> </Grid> </Window>
cs
using AC; using System.Windows; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; namespace WpfClip { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Grid_LayoutUpdated(object sender, EventArgs e) { //确保底层clip rect与容器大小相同 backRect.Rect=new Rect(0,0, grid.ActualWidth, grid.ActualHeight); } private void Border_LayoutUpdated(object sender, EventArgs e) { //截屏框与上层clip rect保持一致 foreRect.Rect = new Rect(clipBorder.GetPosition(), new Size(clipBorder.ActualWidth, clipBorder.ActualHeight)); //圆形效果,去掉则是矩形 foreRect.RadiusX = clipBorder.Width/2; foreRect.RadiusY= clipBorder.Height/2; } private void Grid_MouseDown(object sender, MouseButtonEventArgs e) { //事件转移,触发拖动 clipBorder.RaiseEvent(e); } } internal class AdornerHelper { public static UIElement GetAdornerContent(DependencyObject obj) { return (UIElement)obj.GetValue(AdornerContent); } public static void SetAdornerContent(DependencyObject obj, UIElement value) { obj.SetValue(AdornerContent, value); } // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... public static readonly DependencyProperty AdornerContent = DependencyProperty.RegisterAttached("AdornerContent", typeof(UIElement), typeof(AdornerHelper), new PropertyMetadata(null, (d, e) => { var c = d as FrameworkElement; if (c == null) return; var adronerContent = e.NewValue as UIElement; if (!c.IsLoaded) { if (adronerContent != null) { RoutedEventHandler l = null; l = (s, E) => { var content = GetAdornerContent(c); if (content != null) { var layer = AdornerLayer.GetAdornerLayer(c); if (layer == null) throw new Exception("获取控件装饰层失败,控件可能没有装饰层!"); layer.Add(new NormalAdorner((UIElement)c, (UIElement)e.NewValue)); } c.Loaded -= l; }; c.Loaded += l; } } else { var layer = AdornerLayer.GetAdornerLayer(d as Visual); if (layer == null) throw new Exception("获取控件装饰层失败,控件可能没有装饰层!"); if (e.OldValue != null) { var adorners = layer.GetAdorners(c); foreach (var i in adorners) { if (i is NormalAdorner) { var na = i as NormalAdorner; if (na.Child == e.OldValue) { layer.Remove(i); break; } } } } if (adronerContent != null) { layer.Add(new NormalAdorner((UIElement)c, (UIElement)e.NewValue)); } } })); class NormalAdorner : Adorner { UIElement _child; /// <summary> /// 构造方法 /// </summary> /// <param name="adornedElement">被添加装饰器的元素</param> /// <param name="child">放到装饰器中的元素</param> public NormalAdorner(UIElement adornedElement, UIElement child) : base(adornedElement) { _child = child; AddVisualChild(child); } public UIElement Child { get { return _child; } } protected override Visual GetVisualChild(int index) { return _child; } protected override int VisualChildrenCount { get { return 1; } } protected override Size ArrangeOverride(Size finalSize) { _child.Arrange(new Rect(new Point(0, 0), finalSize)); return finalSize; } } } }
三、效果预览
我们可以通过设置foreRect的类型改变形状,当然Border_LayoutUpdated的逻辑也要相应的修改。
1、矩形框
2、圆形框
总结
以上就是今天要讲的内容,有了之前的基础本文实现起来相对容易,当前目前的也是比较初步的功能,但灵活性是比DockPanel要好的,尤其是支持任意形状,这样就有利于后期划线截屏的实现了。