C# wpf 实现截屏框热键截屏功能

这篇具有很好参考价值的文章主要介绍了C# wpf 实现截屏框热键截屏功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

wpf截屏系列

第一章 使用GDI+实现截屏
第二章 使用DockPanel制作截屏框
第三章 实现截屏框热键截屏(本章)
第四章 实现截屏框实时截屏
第五章 使用ffmpeg命令行实现录屏



前言

在《C# wpf 使用DockPanel实现截屏框》中我们实现了一个截屏框,接下来就要实现相应的截屏功能了。获取截屏区域然后使用GDI+截屏,在这里不少的细节需要处理,比如响应热键弹出截屏界面、点击拖出截屏框、截屏区域任意反向拖动、处理不同dpi下的坐标位置等等。


一、实现步骤

1、响应热键

我们直接使用win32 api的RegisterHotKey和UnregisterHotKey即可。在Window的SourceInitialized事件中注册热键,如下是注册alt+d为热键的示例代码

[System.Runtime.InteropServices.DllImport("user32")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint controlKey, uint virtualKey);

[System.Runtime.InteropServices.DllImport("user32")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

HotKey是对RegisterHotKey、UnregisterHotKey做了封装的对象,网上可以搜到此处略。

 private void Window_SourceInitialized(object sender, EventArgs e)
 {
     //注册alt+d热键,0x44为d,其他虚拟键值请查看:https://learn.microsoft.com/zh-tw/windows/win32/inputdev/virtual-key-codes
     HotKey k = new HotKey(this, HotKey.KeyFlags.MOD_ALT, 0x44);
     k.OnHotKey += K_OnHotKey;
     Visibility = Visibility.Collapsed;
 }

2、截屏显示

(1)获取屏幕区域

我们需要使用win32 api获取屏幕区域,采用wpf的方法取得的屏幕分辨率是基于dpi的,就算是用PointToScreen进行转换,在程序运行过程中改了系统dpi后依然会不准确,所以需要直接取得屏幕的实际像素分辨率,用于gdi+截屏。

  const int DESKTOPVERTRES = 117;
        const int DESKTOPHORZRES = 118;
        [DllImport("gdi32.dll")]
        static extern int GetDeviceCaps(
   IntPtr hdc, // handle to DC  
   int nIndex // index of capability  
   );
        [DllImport("user32.dll")]
        static extern IntPtr GetDC(IntPtr ptr);
        [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
        static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
        /// <summary>  
        /// 获取真实设置的桌面分辨率大小  
        /// </summary>  
        static Size DESKTOP
        {
            get
            {
                IntPtr hdc = GetDC(IntPtr.Zero);
                Size size = new Size();
                size.Width = GetDeviceCaps(hdc, DESKTOPHORZRES);
                size.Height = GetDeviceCaps(hdc, DESKTOPVERTRES);
                ReleaseDC(IntPtr.Zero, hdc);
                return size;
            }
        }

(2)截取并显示

利用上面步骤获取到的截屏区域,结合《C# wpf 使用GDI+实现截屏》里的简单截屏即完成。取得Bitmap对象后,参考我的另一篇文章《C# wpf Bitmap转换成WriteableBitmap(BitmapSource)的方法》将其转换为转换成wpf对象,然后通过ImageBrush赋值为控件的Background即可以显示在控件上。

//截屏并显示到窗口
void Snapshot()
{
    //获取桌面实际分辨率,可以解决程序运行后修改dpi,截图区域不正常的问题
    var leftTop = new Point(0, 0);
    var rightBottom = new Point(DESKTOP.Width, DESKTOP.Height);
    var bitmap = Snapshot((int)leftTop.X, (int)leftTop.Y, (int)(rightBottom.X - leftTop.X), (int)(rightBottom.Y - leftTop.Y));
    var bmp = BitmapToWriteableBitmap(bitmap);
    bitmap.Dispose();
    //显示到窗口
    grdGlobal.Background = new ImageBrush(bmp);
}

3、自动捕获窗口

qq和微信的截屏都有自动捕获窗口功能,我们也可以自己实现这种功能。

(1)获取系统所有窗口

通过win32 api可以枚举系统所有窗口,我们需要将所有窗口的位置大小记录下来,网上可以找到WindowList相关代码此处略。

//获取桌面所有窗口
_windows = WindowList.GetAllWindows();
IntPtr hwnd = new WindowInteropHelper(this).Handle;
//去除不可见窗口以及自己
_windows.RemoveAll((ele) => { return !ele.isVisible || ele.Handle == hwnd; });

(2)根据鼠标位置搜索窗口

//窗口是以z顺序排列的查找到第一个匹配的窗口即可
var screenPoint = grdGlobal.PointToScreen(point);
foreach (var window in _windows)
{
    if (window.rect.Contains(screenPoint))
    //获取在鼠标所在区域的窗口
    {
        try
        {
            if (window.rect.Right > window.rect.Left && window.rect.Bottom > window.rect.Top)
            //
            {
                var topLeft = grdGlobal.PointFromScreen(window.rect.TopLeft);
                var bottomRight = grdGlobal.PointFromScreen(window.rect.BottomRight);
                Thickness thickness = new Thickness(topLeft.X, topLeft.Y, grdGlobal.ActualWidth - bottomRight.X, grdGlobal.ActualHeight - bottomRight.Y);
                 //修正边界
                if (thickness.Left < 0) thickness.Left = 0;
                if (thickness.Top < 0) thickness.Top = 0;
                if (thickness.Right < 0) thickness.Right = 0;
                if (thickness.Bottom < 0) thickness.Bottom = 0;
                //将截屏框显示在窗口位置
                leftPanel.Width = thickness.Left;
                topPanel.Height = thickness.Top;
                rightPanel.Width = thickness.Right;
                bottomPanel.Height = thickness.Bottom;
                break;
            }
        }
        catch { }
    }
}

(3)效果预览

wpf 截屏,# wpf,c#,wpf,开发语言,windows

2、点击拖出截屏框

出现截屏界面之后,参考qq或微信的实现,第一次点击是可以拖出截屏框框选的。如果是采样绘制的方法很简单,直接绘制矩形就可以了。但是基于控件要实现这个功能需要一定的技巧,在《C# wpf 使用DockPanel实现截屏框》的基础上实现这个功能。

(1)移动到点击位置

在鼠标按下事件或移动实现中

//将截屏框移动到点击位置
leftPanel.Width = p.X;
topPanel.Height = p.Y;
rightPanel.Width = grdGlobal.ActualWidth - p.X;
bottomPanel.Height = grdGlobal.ActualHeight - p.Y;

(2)模拟按下事件

接着上面的代码,thumb为右下角拖动点。

//手动触发截屏框滑块拖动事件
MouseButtonEventArgs downEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left)
{ RoutedEvent = FrameworkElement.MouseLeftButtonDownEvent };
thumb.RaiseEvent(downEvent);

(3)修正偏移

由于是模拟的点击事件,可能会出现鼠标不在Thumb上的情况,此时需要对thumb位置进行修正,在Thumb的DragStarted事件中记录偏移。

//滑块需要的偏移量
Point? _thumbOffset;
var thumb = sender as FrameworkElement;
if (!new Rect(0, 0, thumb.ActualWidth, thumb.ActualHeight).Contains(new Point(e.HorizontalOffset, e.VerticalOffset)))
//鼠标起始位置超出了控件范围,则记录中心点偏移在拖动时修正
{
    _thumbOffset = new Point(e.HorizontalOffset - thumb.ActualWidth / 2, e.VerticalOffset - thumb.ActualHeight / 2);
}

在Thumb的DragDelta事件中添加修正逻辑

var horizontalChange = e.HorizontalChange;
var verticalChange = e.VerticalChange;
if (_thumbOffset != null)
//修正偏移
{
    horizontalChange += _thumbOffset.Value.X;
    verticalChange += _thumbOffset.Value.Y;
}

(4)效果预览

wpf 截屏,# wpf,c#,wpf,开发语言,windows

3、反向拖动

这一步不是必须的,但是有的话操作体验会更好,比如qq和微信的截图就支持反向拖动。如果我们使用gdi或gdi+绘制截屏框则天然支持反向拖动,因为RECT的大小可以为负数。但是基于控件则有一定的难度了,由于控件宽高不能为负数,我们需要实现事件转移机制,依然是在《C# wpf 使用DockPanel实现截屏框》的基础上实现这个功能。

(1)判断边界

原本《C# wpf 使用DockPanel实现截屏框》的逻辑的Thumb到了边界就不进行任何操作了,现在要拓展为到达边界则进行事件转移。
横向的Thumb

if (width >= 0)
{
    leftPanel.Width = left >= 0 ? left : 0;
    rightPanel.Width = right >= 0 ? right : 0;
}
else{
//此处将事件转移到反方向的Thumb
}

纵向的Thumb

if (height >= 0)
{
    topPanel.Height = top >= 0 ? top : 0;
    bottomPanel.Height = bottom >= 0 ? bottom : 0;
}
else
{
//此处将事件转移到反方向的Thumb
}

(2)事件转移

//当前的Thumb触发鼠标弹起事件,结束拖动
MouseButtonEventArgs upEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left)
{ RoutedEvent = FrameworkElement.MouseLeftButtonUpEvent };
thumb.RaiseEvent(upEvent);
//反方向的Thumb触发鼠标按下事件,开始拖动
MouseButtonEventArgs downEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left)
{ RoutedEvent = FrameworkElement.MouseLeftButtonDownEvent };
t.RaiseEvent(downEvent);

(3)修正边界

完成上述两步之后已经可以做到反向拖动了,但是会有个问题,当多动过快的时截屏框的位置会发生移动,要解决这个问题则需要在事件转移时修正边界位置,即使两条边重合。
横向的Thumb

if (thumb.HorizontalAlignment == HorizontalAlignment.Left)
//从左到右转移的修正
{
    leftPanel.Width = grdGlobal.ActualWidth - rightPanel.Width;
}
else
//从右到左转移的修正
{
    rightPanel.Width = grdGlobal.ActualWidth - leftPanel.Width;
}

纵向的Thumb

 if (thumb.VerticalAlignment == VerticalAlignment.Top)
 //从上到下转移的修正
 {
     topPanel.Height = grdGlobal.ActualHeight - bottomPanel.Height;
 }
 else
 //从下到上转移的修正
 {
     bottomPanel.Height = grdGlobal.ActualHeight - topPanel.Height;
 }

(4)效果预览

wpf 截屏,# wpf,c#,wpf,开发语言,windows

4、截取图片

由于前面截取是整个桌面的图像,保存时需要根据截屏框截取画面,我们使用WriteableBitmap对象就可以实现。

//获取截屏框的图片
WriteableBitmap GetClipImage()
{
    var bursh = grdGlobal.Background as ImageBrush;
    if (bursh != null)
    {
        //裁剪
        //全屏图片
        var screenWb = bursh.ImageSource as WriteableBitmap;
        //获取截取区域
        var leftTop = clipRect.PointToScreen(new Point(0, 0));
        var rightBottom = clipRect.PointToScreen(new Point(clipRect.ActualWidth, clipRect.ActualHeight));
        var rect = new Int32Rect((int)leftTop.X, (int)leftTop.Y, (int)(rightBottom.X - leftTop.X), (int)(rightBottom.Y - leftTop.Y));
        //创建截取图片对象
        var wb = new WriteableBitmap(rect.Width, rect.Height, 0, 0, screenWb.Format, null);
        //写入截取区域数据
        wb.WritePixels(rect, screenWb.BackBuffer, screenWb.PixelHeight * screenWb.BackBufferStride, screenWb.BackBufferStride, 0, 0);
        return wb;
    }
    return null;
}

5、设置粘贴板

直接使用Clipboard.SetImage即可,参数类型为BitmapSource,是WriteableBitmap的基类。

 Clipboard.SetImage(GetClipImage());

二、关于dpi

1、适配不同dpi

有处理dpi不同的情况,在任意dpi下都能正常截图。

2、不支持dpi实时修改

(1)现象

程序启动后实时修改dpi,截屏显示的画面会模糊,主要原因是不同api之间的dpi计算不统一。系统dpi实时修改后wpf界面会响应oloaded自动调整大小,但部分程序内部的dpi(比如getWindowRect)是不会变化的,尤其是渲染图片依然按照程序启动时的dpi去计算,所以会进行缩放,显示的画面必然模糊。
这里举一个具体的例子流程如下:
win11 分辨率1920x1080
1、初始系统dpi为120(1.25倍)
2、程序启动
3、程序dpi为120
5、全屏窗口大小1536x864,通过win32 api获取则是1920x1080,截屏1920x1080显示,截屏画面无损
6、系统dpi设置为96(1倍)
7、此时程序dpi为120
8、全屏窗口大小1920x1080,通过win32 api获取则是2400x1350,截屏1920x1080显示,截屏画面模糊。
按像素点绘制,画面显示在左上角无法充满窗口。

(2)尝试的解决方案

笔者采样了多种方式尝试解决
1、提前缩放图片再显示。
2、参考微软解决dpi问题的方法。
3、使用gdi+的graphics直接通过hdc以像素点为单位绘制。
4、使用gdi的bitblt进行hdc拷贝。
以上方法都没效果画面依然模糊

3、建议

需要支持dpi实时改变,可以将截图功能作为单独的程序,响应热键后再启动。


三、完整代码

https://download.csdn.net/download/u013113678/88308050
说明:截屏的操作方式和qq、微信差不多,目前设置的热键为alt+d。


四、效果预览

1、截屏粘贴到qq

wpf 截屏,# wpf,c#,wpf,开发语言,windows

2、截屏保存到文件

wpf 截屏,# wpf,c#,wpf,开发语言,windows


总结

以上就是今天要讲的内容,本文介绍了wpf截屏框热键截屏的方法。需要实现的功能还是比较多的,而且有些功能难度也不小,几经尝试才找到合适的实现方法,至于实时改变dpi的模糊的问题,这个目前的结论是无法解决,这并不是wpf的局限,用c++ mfc也不行,除非存在一个设置程序全局dpi的win32 api接口笔者没有发现。所以这个问题目前只能通过独立程序启动解决。但是总的来说实现的效果是很不错的,尤其是反向拖动,通过事件转移的方式实现,界面操作还是很流畅。文章来源地址https://www.toymoban.com/news/detail-708865.html

到了这里,关于C# wpf 实现截屏框热键截屏功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C#创建DataTable并填充数据,按钮事件实现全选,并到全选的值。wpf开发

    wpf开发中,用事件创建一个datatable度填充到datagird里面,在datagrid里面有第一列是复选框。用一单击事件实现全选,用一个按钮事件得到所选中的值。 Window x:Class=\\\"WpfApp4.MainWindow\\\"         xmlns=\\\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\\\"         xmlns:x=\\\"http://schemas.microsoft.com

    2024年02月08日
    浏览(35)
  • 记录--产品:请给我实现一个在web端截屏的功能!

    最近产品又开始整活了,本来是毫无压力的一周,可以小摸一下鱼的,但是突然有一天跟我说要做一个在网页端截屏的功能。 作为一个工作多年的前端,早已学会了尽可能避开麻烦的需求,只做增删改查就行! 我立马开始了我的反驳,我的理由是市面上截屏的工具有很多的

    2024年02月05日
    浏览(56)
  • 【windows程序设计作业】用c#语言实现记事本

    使用Visual C# 2015设计一个Windows应用程序——记事本,通过本章的学习,可以进一步掌握MenuStrip(菜单)、ToolStrip(工具栏)、RichTextBox(高级文本框)和StatusStrip(状态栏控件)等控件的使用,以及如何使用CommonDialog(公共对话框)实现对文本的存取、格式设置等操作。 实现

    2024年02月05日
    浏览(62)
  • C#时间轴曲线图形编辑器开发2-核心功能实现

    目录 三、关键帧编辑 1、新建Winform工程 (1)界面布局  (2)全局变量 2、关键帧添加和删除 (1)鼠标在曲线上识别 (2)键盘按键按下捕捉 (3)关键帧添加、删除  (4)修改关键帧值 3、曲线插值 (1)三次样条插值 (2)工程代码下载链接 四、曲线数据导出和读取 1、数

    2024年02月15日
    浏览(47)
  • App防止恶意截屏功能的方法:iOS、Android和鸿蒙系统的实现方案

    防止应用被截图是一个比较常见的需求,主要是出于安全考虑。下面将分别为iOS(苹果系统)、Android(安卓系统)及HarmonyOS(鸿蒙系统)提供防止截屏的方法和示例代码。 在企业内部使用的应用中,防止员工恶意截屏是一个重要的安全需求。本文将详细介绍iOS、Android和鸿蒙

    2024年02月04日
    浏览(41)
  • 前端js如何实现截屏功能,插件推荐js-web-screen-shot

    读取dom结构转换成canvas,最后转成图片形式展示 缺点 :没有编辑功能 链接:html2canvas 大佬模仿qq截图实现的,也可以搭配webrtc实现web端远程桌面共享 链接: github gitee 简单使用 注意点:

    2024年02月06日
    浏览(47)
  • C# WPF上位机开发(键盘绘图控制)

    【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】         在软件开发中,如果存在canvas图像的话,一般有几种控制方法。一种是鼠标控制;一种是键盘控制;还有一种是定时器控制。定时器控制,多常见动画、游戏、3d视频当中。而鼠标控制

    2024年02月02日
    浏览(41)
  • C#开发winform&wpf后台捕获鼠标移动事件

    做 WPF和winform的时候,可以在界面上设置鼠标移动事件来检测鼠标移动,如果项目为后期改造这样做的话改动量很大,今天通过另外一种后台调用windows api的方式进行快速捕获和触发,提高开发效率分享给大家。

    2024年02月16日
    浏览(49)
  • C# WPF上位机开发(Web API联调)

    【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】         很多时候,客户需要开发的不仅仅是一个上位机系统,它还有其他很多配套的系统或设备,比如物流小车、立库、数字孪生等一整套系统。这个时候,上位机系统就需要和各个子系统

    2024年01月20日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包