C# wpf利用Clip属性实现截屏框

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要好的,尤其是支持任意形状,这样就有利于后期划线截屏的实现了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/340257.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pnpm使用

文章目录 前言一、安装二、设置镜像三、使用总结如有启发&#xff0c;可点赞收藏哟~ 前言 pnpm 全称 performant npm&#xff0c;意思为 高性能的 npm 速度快、节约磁盘空间、支持 monorepo、安全性高。 一、安装 npm install -g pnpm or brew install pnpm二、设置镜像 #…

每日一题 670. 最大交换(中等,后缀)

先考虑最简单的情况&#xff0c;如果在首位之后有比它大的数字&#xff0c;那么显然交换这两个数字是最优解其次如果比它大的数字在后面不止出现了一次&#xff0c;那面显然是用最后一次出现的那个位置进行交换&#xff08;要使值最大&#xff0c;低位要小&#xff0c;高位要大…

MAXWELL

MAXWELL 一、maxwell是什么 maxwell 官网地址&#xff1a;http://maxwells-daemon.io/ 因为官网是纯英文的&#xff0c;倒是不难懂&#xff0c;但总觉得写的略粗糙&#xff08;也可能笔者英文水平确实拉胯&#xff0c;有待提高&#xff09;。所以还是自己百度了一下。 当my…

WhatsApp会话信息该如何备份以及还原

对于出海企业来说&#xff0c;WhatsApp是和客户沟通的必备软件之一。无论是聊单还是询盘都可以在WhatsApp上进行&#xff0c;所以WhatsApp上通常存储了很多交易信息。但是WhatsApp软件本身是端对端加密&#xff0c;所以它不会像其他社交软件一样帮你自动在后台备份&#xff0c;…

制造业管理软件:为何ERP替代不了MES?

一、ERP和MES的功能区别 ERP是一种综合性的企业管理软件&#xff0c;它涵盖了企业的各个方面&#xff0c;包括财务、采购、库存、销售、人力资源等。它的主要功能是将企业内部的各项业务整合为一个整体进行管理&#xff0c;实现信息共享和协同工作。ERP的主要特点是可以对企业…

HackTheBox - Medium - Linux - Noter

Noter Noter 是一种中型 Linux 机器&#xff0c;其特点是利用了 Python Flask 应用程序&#xff0c;该应用程序使用易受远程代码执行影响的“节点”模块。由于“MySQL”守护进程以用户“root”身份运行&#xff0c;因此可以通过利用“MySQL”的用户定义函数来利用它来获得RCE并…

c语言小游戏之扫雷

目录 一&#xff1a;游戏设计理念及思路 二&#xff1a;初步规划的游戏界面 三&#xff1a;开始扫雷游戏的实现 注&#xff1a;1.创建三个文件&#xff0c;test.c用来测试整个游戏的运行&#xff0c;game.c用来实现扫雷游戏的主体&#xff0c;game.h用来函数声明和包含头文…

【立创EDA-PCB设计基础】5.布线设计规则设置

前言&#xff1a;本文详解布线前的设计规则设置。经过本专栏中的【立创EDA-PCB设计基础】前几节已经完成了布局&#xff0c;接下来开始进行布线&#xff0c;在布线之前&#xff0c;要设置设计规则。 目录 1.间距设置 1.1 安全间距设置 1.2 其它间距设置 2.物理设置 2.1 导…

枚举类型有着一篇足以

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青-CSDN博客 1.关键字enum的定义 enum是C语言中的一个关键字&#xff0c;enum叫枚举数据类型&#…

JavaScript基础之JavaScript引入方式

JavaScript引入方式 JavaScript 程序不能独立运行&#xff0c;它需要被嵌入 HTML 中&#xff0c;然后浏览器才能执行 JavaScript 代码。通过 script 标签将 JavaScript 代码引入到 HTML 中&#xff0c;一般以下方式: 外部方式内部方式JavaScript元素事件通过JavaScript伪URL引…

Spring Boot 4.0:构建云原生Java应用的前沿工具

目录 前言 Spring Boot简介 Spring Boot 的新特性 1. 支持JDK 17 2. 集成云原生组件 3. 响应式编程支持 4. 更强大的安全性 5. 更简化的配置 Spring Boot 的应用场景 1. 云原生应用开发 2. 响应式应用程序 3. 安全性要求高的应用 4. JDK 17的应用 总结 作…

【射影几何11】完全四边形和交比研究

一、说明 对于交比的灵活应用&#xff0c;尚有许多情况需要讨论&#xff0c;首先引出完全四边形的例子&#xff0c;该关键词的应用非常普遍&#xff1b;其次&#xff0c;我们尝试用交比证明一些事实&#xff1b;随后我们又引出交比射影案例的特殊情况。 二、完全四边形 2.1 完…

Excel 动态可视化图表分享

AIGC ChatGPT 职场案例 AI 绘画 与 短视频制作 PowerBI 商业智能 68集 数据库Mysql 8.0 54集 数据库Oracle 21C 142集 Office 2021实战应用 Python 数据分析实战&#xff0c; ETL Informatica 数据仓库案例实战 Excel 2021实操 100集&#xff0c; Excel 2021函数大全 80集 Exc…

SqlAlchemy使用教程(五) ORM API 编程入门

SqlAlchemy使用教程(一) 原理与环境搭建SqlAlchemy使用教程(二) 入门示例及编程步骤SqlAlchemy使用教程(三) CoreAPI访问与操作数据库详解SqlAlchemy使用教程(四) MetaData 与 SQL Express Language 的使用SqlAlchemy使用教程(五) ORM API 编程入门 前一章用SQL表达式(SQL Expr…

Android:JNI实战,加载三方库、编译C/C++

一.概述 Android Jni机制让开发者可以在Java端调用到C/C&#xff0c;也是Android应用开发需要掌握的一项重要的基础技能。 计划分两篇博文讲述Jni实战开发。 本篇主要从项目架构上剖析一个Android App如何通过Jni机制加载三方库和C/C文件。 二.Native C Android Studio可…

高防IP如何保护服务器

首先我们要知道什么是高防IP~ 高防IP是指高防机房所提供的ip段&#xff0c;主要是针对互联网服务器遭受大流量DDoS攻击时进行的保护服务。高防IP是目前最常用的一种防御DDoS攻击的手段&#xff0c;用户可以通过配置DDoS高防IP&#xff0c;将攻击流量引流到高防IP&#xff0c;防…

样品前处理国产微波消解罐的优势

国产微波消解罐参数&#xff1a; 型号 MARS5、MARS6等 内罐材质 TFM 外罐材质 宇航纤维复合材料 耐温 -200&#xff5e;260℃ 规格 25ml、55ml、75ml、100ml、110ml 厂家秉承 “客户、服务、技术”为根本的理念国产替代微波罐受到众多用户的青睐。 特性&#xff1a…

【LeetCode】每日一题 2024_1_21 分割数组的最大值(二分)

文章目录 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01;题目&#xff1a;分割数组的最大值题目描述代码与解题思路 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 今天是 hard&#xff0c;难受&#xff0c;还好有题解大哥的清晰讲解 题目&a…

论文阅读:Vary论文阅读笔记

目录 引言整体结构图数据集构造Vary-tiny部分Document Data数据构造Chart Data构造Negative natural image选取 Vary-base部分 引言 论文&#xff1a;Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models Paper | Github | Demo 许久不精读论文了&#x…

文件操作与IO(3)

文件内容的读写--数据流 这里我们将要讲到文件操作中的重要概念--流. 之前也在C语言讲解中提到了文件流的概念---读写文件内容 分为这几步:(1)打开文件;(2)读/写文件;(3)关闭文件. 数据流主要分为字节流和字符流. 字节流:以字节为单位进行读写(代表:InputStream,OutputStrea…