MFC如何实现屏幕截图

news2024/11/8 12:32:23

目录

    • 一 程序实现效果
    • 二 程序实现思路
    • 三 具体实现

一 程序实现效果

本文描述了MFC中实现屏幕截图的一种方式,程序界面如下:
在这里插入图片描述
单击【开始截屏】,按住鼠标左键,一直拖动到需要截屏的矩形的右下角,松开鼠标左键,完成截图(由于本程序框选屏幕时会影响其他录屏软件的,所以框选过程没有动图)。录制完成后效果如下:
在这里插入图片描述
整个工程文件位于:https://download.csdn.net/download/mary288267/87403939

二 程序实现思路

  1. 如何实现鼠标事件的捕获
    本程序要求用户单击【开始截屏】以后,用户将鼠标放到需要截屏范围的左上角,然后按住左键以后拖动到右下角。这里的难点是:截屏可能在对话框范围以外,对话框如何截获鼠标运动和单击的消息?大家首先想到的可能是SetCapture(),但是这个函数有一定局限性。它能够捕获的鼠标事件包括:当光标位于捕获窗口上方,或者鼠标在捕获窗口上方按下并且持续保持按下状态。可能有点拗口,意思就是鼠标在捕获窗口上方时能够捕获所有鼠标消息,或者在捕获窗口上方按下后保持按下状态移动到窗口以外过程中所产生的鼠标消息均可捕获。
    以下为MSDN原文.想实现窗口外鼠标捕获还需要其他办法。请看第2条。

The SetCapture function sets the mouse capture to the specified window belonging to the current thread. SetCapture captures mouse input either when the mouse is over the capturing window, or when the mouse button was pressed while the mouse was over the capturing window and the button is still down.

  1. 利用钩子函数实现鼠标消息过滤
    利用SetWindowsHookEx 函数在程序中设置一个钩子函数,过滤到所有的鼠标消息。这样,当我们按下【屏幕截图】时,让钩子函数过滤对应的鼠标移动、单击消息,就可以实现对话框外截图的目的。
  2. 位块传输
    在确定截屏范围以后,利用GetDesktopWindow获得屏幕的Cwnd对象,然后获取屏幕的DC,反转截屏范围屏幕的颜色。最后,将这部分屏幕位块用BitBlt传输到对应的其他DC上。

三 具体实现

在VS中,新建一个MFC对话框工程,在资源编辑器中添加一个图片控件和按钮控件,图片控件用来显示截取的屏幕位图,需要设为自绘类型。同时在图片控件右侧和下侧增加滚动条,可使用户滚动查看所截取位图全貌。按钮控件单击后即可截屏。
按钮控件单击的消息响应函数为:

void CScreenShotDlg::OnBnClickedBtnScreenShot()
{
	SetCapture();
	SetCursor(LoadCursor(NULL, IDC_CROSS));	
	g_hHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, GetModuleHandle(NULL), 0);//注册钩子函数
	//隐藏对话框,这里不能隐藏对话框,否则会出错
	//ShowWindow(SW_HIDE);

	//锁定桌面窗口更新
	CWnd* pWndDeskTop = GetDesktopWindow();
	if (!pWndDeskTop && !pWndDeskTop->LockWindowUpdate())
		return;

	//开始消息循环
	MSG msg;
	POINT ptBegin, ptEnd;
	m_bDraging = false;
	bool bSkipLoop = false;
	while (GetMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST))
	{
		if (CWnd::GetCapture() != this)
			break;

		switch (msg.message)
		{
		case WM_LBUTTONDOWN:
			m_bDraging = true;
			ptBegin = msg.pt;
			ptEnd = ptBegin;
			InvertBlock(pWndDeskTop, ptBegin, ptEnd);
			break;
		case WM_MOUSEMOVE:
			if (m_bDraging)
			{
				InvertBlock(pWndDeskTop, ptBegin, ptEnd);//先反转为正常的颜色
				ptEnd = msg.pt;
				InvertBlock(pWndDeskTop, ptBegin, ptEnd);
			}
			break;
		case WM_LBUTTONUP:
			if (m_bDraging)
			{
				InvertBlock(pWndDeskTop, ptBegin, ptEnd);
				ptEnd = msg.pt;
				//画出正确的矩形,保证起始点和终点的正确性
				if (m_bmpMemory.m_hObject)
					m_bmpMemory.DeleteObject();
				CDC* pDcPic = m_wndPicPreview.GetDC();
				m_bmpMemory.CreateCompatibleBitmap(pDcPic, abs(ptEnd.x - ptBegin.x), abs(ptEnd.y - ptBegin.y));
				CDC dcMemory;
				dcMemory.CreateCompatibleDC(pDcPic);
				ReleaseDC(pDcPic);

				CBitmap* pOldBmp = dcMemory.SelectObject(&m_bmpMemory);
				CDC *pDcScreen = pWndDeskTop->GetDCEx(NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE);
				dcMemory.BitBlt(0, 0, abs(ptEnd.x - ptBegin.x), abs(ptEnd.y - ptBegin.y),
					pDcScreen, ptBegin.x, ptBegin.y, SRCCOPY);
				dcMemory.SelectObject(pOldBmp);
				ReleaseDC(pDcScreen);

				//动态设置滚动条滑块的范围和Page
				CRect rect;
				m_wndPicPreview.GetWindowRect(&rect);
				SCROLLINFO infoVert = { sizeof(SCROLLINFO),SIF_ALL,	0,
					max(0, abs(ptEnd.y - ptBegin.y)),rect.Height(),0,0 };
				m_wndVertScroll.SetScrollInfo(&infoVert);

				SCROLLINFO infoHor = { sizeof(SCROLLINFO),SIF_ALL,	0,
					max(0, abs(ptEnd.x - ptBegin.x)),rect.Width(),0,0 };
				m_wndHorScroll.SetScrollInfo(&infoHor);

				bSkipLoop = true;
				m_bDraging = false;
			}
			break;
		default:
			break;
		}
		if (bSkipLoop)
			break;
	}

	if (g_hHook)
	{
		UnhookWindowsHookEx(g_hHook);
		g_hHook = NULL;
	}

	ReleaseCapture();
	SetCursor(LoadCursor(NULL, IDC_ARROW));
	pWndDeskTop->UnlockWindowUpdate();
	m_wndPicPreview.Invalidate();
}

在此函数中,首先设置了鼠标钩子函数,意思是接下来的鼠标操作都会进入到这个钩子函数中处理,钩子函数处理完之后再投递到本窗口的消息队列中。然后获取桌面的窗口对象及对应的DC,并且通过LockWindowUpdate设置桌面窗口为锁定状态,这可以阻止其他任何程序对屏幕进行更新。之后通过一个循环反复取出队列的消息(这些消息都是钩子函数投递进来的),在鼠标左键按下并且移动时,反转屏幕上对应的像素点;当鼠标左键弹起时,将截获的矩形范围图像保存在m_bmpMemory中。最后,我们刷新图片控件的显示。

这里面所调用的鼠标钩子函数实现为:

// 鼠标钩子
HHOOK g_hHook = NULL;
LRESULT CALLBACK MouseProc(int nCode, WPARAM msg, LPARAM lparam)
{
	CWnd* pMainWnd = AfxGetApp()->GetMainWnd();
	if (pMainWnd && pMainWnd->IsKindOf(RUNTIME_CLASS(CScreenShotDlg)))
	{
		if (WM_LBUTTONDOWN == msg)
		{
			PMOUSEHOOKSTRUCT mh = (PMOUSEHOOKSTRUCT)lparam;
			POINT pt;
			pt.x = mh->pt.x;	//此处的坐标均为屏幕坐标
			pt.y = mh->pt.y;
			pMainWnd->PostMessage(WM_LBUTTONDOWN, 0, MAKELPARAM(pt.x, pt.y));
			return 1;
		}
		else if (WM_MOUSEMOVE == msg)
		{
			PMOUSEHOOKSTRUCT mh = (PMOUSEHOOKSTRUCT)lparam;
			POINT pt;
			pt.x = mh->pt.x;
			pt.y = mh->pt.y;
			pMainWnd->PostMessage(WM_MOUSEMOVE, 0, MAKELPARAM(pt.x, pt.y));
			//return 1; //不能返回1,否则鼠标没法动。原因目前不清楚。
		}
		else if (WM_LBUTTONUP == msg)
		{
			PMOUSEHOOKSTRUCT mh = (PMOUSEHOOKSTRUCT)lparam;
			POINT pt;
			pt.x = mh->pt.x;
			pt.y = mh->pt.y;
			pMainWnd->PostMessage(WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y));
			return 1;
		}
	}

	return CallNextHookEx(g_hHook, nCode, msg, lparam);
}

在钩子函数中,我们首先利用MFC的全局函数 AfxGetApp()和GetMainWnd()获得当前线程的主窗口(也就是这个程序的主窗口了),截获了鼠标WM_LBUTTONDOWN 、WM_MOUSEMOVE 和WM_LBUTTONUP 消息,然后把它们投递到主窗口的消息队列中。

按钮单击响应函数中的反转窗口(可以为屏幕)像素的函数如下。注意成员函数CDC::PatBlt最后一个参数,DSTINVERT。

//反转一个矩形屏幕范围的像素
void InvertBlock(CWnd* pWndSrc, POINT ptBeg, POINT ptEnd)
{
	CDC* pDC = pWndSrc->GetDCEx(NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE);
	pDC->PatBlt(ptBeg.x, ptBeg.y, ptEnd.x - ptBeg.x, ptEnd.y - ptBeg.y, DSTINVERT);
	pWndSrc->ReleaseDC(pDC);
}

图片控件的自绘函数如下,请注意将m_bmpMemory的像素位块blit到图片控件的DC时,由于滚动条位置不同,位块传输的起始点也不相同,起始的横纵坐标为m_nScrolHPos和m_nScrolVPos。

void CScreenShotDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	if (nIDCtl == IDC_PIC_PREVIEW)
	{
		if (m_bmpMemory.m_hObject)
		{
			CDC* pDcPic = m_wndPicPreview.GetDC();
			CDC dcMemory;
			dcMemory.CreateCompatibleDC(pDcPic);
			CBitmap* pOldBmp = dcMemory.SelectObject(&m_bmpMemory);

			CRect rect;
			m_wndPicPreview.GetWindowRect(&rect);			
			pDcPic->PatBlt(0, 0, rect.Width(), rect.Height(), PATCOPY);//首先,清空背景
			pDcPic->BitBlt(0, 0, rect.Width(), rect.Height(), &dcMemory,
				m_nScrolHPos, m_nScrolVPos, SRCCOPY);
			dcMemory.SelectObject(pOldBmp);

			ReleaseDC(pDcPic);
		}
	}
}

滚动条滚动时的位置信息保存在类的成员变量m_nScrolHPos和m_nScrolVPos中,滚动条滚动消息响应函数为:

void CScreenShotDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	// Get the minimum and maximum scroll-bar positions.
	int minpos;
	int maxpos;
	pScrollBar->GetScrollRange(&minpos, &maxpos);
	maxpos = pScrollBar->GetScrollLimit();

	// Get the current position of scroll box.
	m_nScrolHPos = pScrollBar->GetScrollPos();

	// Determine the new position of scroll box.
	switch (nSBCode)
	{
	case SB_LEFT:      // Scroll to far left.
		m_nScrolHPos = minpos;
		break;
	case SB_RIGHT:      // Scroll to far right.
		m_nScrolHPos = maxpos;
		break;
	case SB_ENDSCROLL:   // End scroll.
		break;
	case SB_LINELEFT:      // Scroll left.
		if (m_nScrolHPos > minpos)
			m_nScrolHPos--;
		break;
	case SB_LINERIGHT:   // Scroll right.
		if (m_nScrolHPos < maxpos)
			m_nScrolHPos++;
		break;
	case SB_PAGELEFT:    // Scroll one page left.
	{
		// Get the page size. 
		SCROLLINFO   info;
		pScrollBar->GetScrollInfo(&info, SIF_ALL);

		if (m_nScrolHPos > minpos)
			m_nScrolHPos = max(minpos, m_nScrolHPos - (int)info.nPage);
	}
	break;
	case SB_PAGERIGHT:      // Scroll one page right.
	{
		// Get the page size. 
		SCROLLINFO   info;
		pScrollBar->GetScrollInfo(&info, SIF_ALL);

		if (m_nScrolHPos < maxpos)
			m_nScrolHPos = min(maxpos, m_nScrolHPos + (int)info.nPage);
	}
	break;
	case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
		m_nScrolHPos = nPos;      // of the scroll box at the end of the drag operation.
		break;
	case SB_THUMBTRACK:   // Drag scroll box to specified position. nPos is the
		m_nScrolHPos = nPos;     // position that the scroll box has been dragged to.
		break;
	}

	// Set the new position of the thumb (scroll box).
	pScrollBar->SetScrollPos( m_nScrolHPos);
	m_wndPicPreview.Invalidate();
}


void CScreenShotDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	// Get the minimum and maximum scroll-bar positions.
	int minpos;
	int maxpos;
	pScrollBar->GetScrollRange(&minpos, &maxpos);
	maxpos = pScrollBar->GetScrollLimit();

	// Get the current position of scroll box.
	m_nScrolVPos = pScrollBar->GetScrollPos();

	// Determine the new position of scroll box.
	switch (nSBCode)
	{
	case SB_TOP:      // Scroll to far left.
		m_nScrolVPos = minpos;
		break;
	case SB_BOTTOM:      // Scroll to far right.
		m_nScrolVPos = maxpos;
		break;
	case SB_ENDSCROLL:   // End scroll.
		break;
	case SB_LINEUP:      // Scroll left.
		if (m_nScrolVPos > minpos)
			m_nScrolVPos--;
		break;
	case SB_LINEDOWN:   // Scroll right.
		if (m_nScrolVPos < maxpos)
			m_nScrolVPos++;
		break;
	case SB_PAGEUP:    // Scroll one page left.
	{
		// Get the page size. 
		SCROLLINFO   info;
		pScrollBar->GetScrollInfo(&info, SIF_ALL);

		if (m_nScrolVPos > minpos)
			m_nScrolVPos = max(minpos, m_nScrolVPos - (int)info.nPage);
	}
	break;
	case SB_PAGEDOWN:      // Scroll one page right.
	{
		// Get the page size. 
		SCROLLINFO   info;
		pScrollBar->GetScrollInfo(&info, SIF_ALL);

		if (m_nScrolVPos < maxpos)
			m_nScrolVPos = min(maxpos, m_nScrolVPos + (int)info.nPage);
	}
	break;
	case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
		m_nScrolVPos = nPos;      // of the scroll box at the end of the drag operation.
		break;
	case SB_THUMBTRACK:   // Drag scroll box to specified position. nPos is the
		m_nScrolVPos = nPos;     // position that the scroll box has been dragged to.
		break;
	}

	// Set the new position of the thumb (scroll box).
	pScrollBar->SetScrollPos(m_nScrolVPos);
	m_wndPicPreview.Invalidate();
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工业设备数据采集调研要点

一、概述 当一家客户提出需要采集设备的数据&#xff0c;通常需要对设备、通讯、采集方案进行确认。此时我们需要做两件事&#xff1a; 1、向客户要设备清单&#xff0c;便于确认设备的数量。&#xff08;客户提供&#xff09; 2、确认设备信息、通讯接口信息、采集方案。&a…

图文详解 Java 泛型,写得太好了!

一、泛型的引入我们都知道&#xff0c;继承是面向对象的三大特性之一&#xff0c;比如在我们向集合中添加元素的过程中add()方法里填入的是Object类&#xff0c;而Object又是所有类的父类&#xff0c;这就产生了一个问题——添加的类型无法做到统一 由此就可能产生在遍历集合取…

OpenShift 4 - 在单节点 OpenShift 上部署 ODF 存储软件

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在支持 OpenShift 4.12 的 OpenShift Local 环境中验证 文章目录什么是 ODF LVM &#xff1f;为 OpenShift Local 增加额外存储设备安装并配置 ODF LVM Operator使用 ODF 创建 PVC/PV 验证什么是 ODF LVM &…

Mock的接口自动化测试如何测?

1.Mock实现原理和实现机制 在某些时候&#xff0c;后端在开发接口的时候&#xff0c;处理逻辑非常复杂&#xff0c;在测试的时候&#xff0c;后端在未完成接口的情况下该如何去测试呢&#xff1f; 我们需要测试&#xff0c;但是有些请求又需要修改一下参数&#xff0c;或者改…

寻找适合程序员的笔记软件

做为一个程序员,有两个东西是我们必需的.一个是搜索,另一个则是记录. 当我们遇到不会或解决不了的困难点时,我们会第一时间使用搜索(如Google)来寻找解决方案,而当我们积累与在技术上有任何心得时,我们会记录它. 因而,寻找一个合适的笔记软件,对程序员非常重要. 一) 程序员…

Apache Spark 机器学习 特征转换 1

分词器&#xff08;Tokenizer&#xff09; 分词是一个处理过程&#xff0c;其将文本句子分割成一系列独立的单词词汇集合&#xff0c;Spark提供Tokenizer分词器类&#xff0c;其提供的功能是使用分隔符的方式处理文本句子的特征转换&#xff0c;Spark提供RegexTokenizer分词器…

在PC上安装OpenSSL,生成证书

文章目录一.在编程 PC 上安装 OpenSSL1. 下载安装 OpenSSL2.生成CA认证3. 生成 Broker 证书4. 生成各个 Client 的证书一.在编程 PC 上安装 OpenSSL 为了使用带 TLS 安全证书的 ADS Over MQTT&#xff0c;在 MQTT 的 Server 和 Client 侧都需要证书以进行安全通信&#xff0c;…

安装vue-cli2和3以及创建vue2和vue3项目的步骤及区别

文章目录安装vue-cli2版本步骤1.下载vue-cli2问题1&#xff1a;安装Vue Cli出现EEXIST: file already exists, cmd shim ‘C:\Users\2.vue-cli2构建vue项目问题2&#xff1a;报错&#xff1a; vue-cli Failed to download repo vuejs-templates/webpack: connect ETIMEDOUT 19…

「兔了个兔」福兔贺春,纯CSS实现超精美月兔404界面(附源码)

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

搭建一个FAQ智能问答系统/服务

FAQ智能问答系统 介绍 项目传送门&#xff1a;https://github.com/wzzzd/FAQ_system 构建了一个FAQ智能问答系统。 使用多种方法&#xff0c;实现FAQ的问题-模板匹配功能。 使用Tornado框架&#xff0c;部署成轻量级的Web服务应用。 整体框架如下。 流程 1.初始化流程 1.…

这福利给你要不要 — 用Python采集相亲网站女生数据

前言 俗话说学咱这行的男同志 找对象容易吗 这马上就要过完年了 是时候找找女朋友了 我在这里摸索到了个网站 或许你们可以来看看 送一波单身福利 不需要的也可以学学怎么采集这些数据呗 环境与模块 环境开发 Python 3.8Pycharm 模块使用 import parsel --> p…

类与对象的原理

前言 在JavaScript中&#xff0c;类的实现是基于原型继承机制的。 JavaScript中的类的一个重要特性是“动态可继承”。 类与原型 在JavaScript中&#xff0c;类的所有实例对象都从同一个原型对象上继承属性&#xff0c;因此原型对象是类的核心。 所有的类都有一个共同的根…

关系数据库——关系操作和关系完整性

文章目录一、关系操作1.基本的关系操作2.关系数据语言的分类关系代数语言关系演算语言具有关系代数和关系演算双重特点的语言二、关系的完整性1.实体完整性(Entity Integrity)实体完整性规则2.参照完整性(Referential Integrity)参照完整性规则引用关系3.用户定义的完整性(User…

WebRTC 的连接过程

经过前面几部分的铺垫&#xff0c;你应该对P2P音视频互动的过程有了一个大概的了解&#xff0c;有可能你会觉得过程比较繁琐&#xff0c;甚至涉及到了网络底层。但是&#xff0c;不要担心&#xff0c;WebRTC已经帮我们做了很多的事情&#xff0c;让我们在音视频开发时变得轻而易…

【Spring源码】22. 属性填充populateBean()详解

进入populateBean()对bean的属性进行填充&#xff0c;将各个属性值注入&#xff08;存在其他bean的属性&#xff0c;则会递归初始化依赖的bean&#xff09;一开始会先对传入的参数进行判断&#xff08;如下图红框框中的逻辑&#xff09;如果传入的BeanWrapper和RootBeanDefinit…

SpringBoot3+最新MybatisPlus+Mysql与TDengine双数据源

前言 昨天写的ideaApifox uploader插件apifox新年第一天上班就上榜了&#xff0c;真是不错。今天来补一篇&#xff0c;本来应该是在前一篇之前发的。实际上就是最新的springBoot集成最新的mybatisPlus&#xff0c;加双数据源&#xff1a;mysql、TDengine&#xff0c;一个关系型…

kvm虚拟机克隆

kvm虚拟机克隆链接克隆和完整克隆的区别完整克隆自动克隆手动克隆的步骤1.复制模板vm磁盘为新vm的磁盘2.复制模板vm配置文件为新vm的配置文件3.修改新vm配置文件中的信息为新vm的信息4.导入新vm的配置文件5.启动新vm链接克隆1.生成链接克隆虚拟机磁盘文件2.后续流程与手动克隆一…

【算法练习】链表中环的入口结点

题源&#xff1a;牛客描述给一个长度为n链表&#xff0c;若其中包含环&#xff0c;请找出该链表的环的入口结点&#xff0c;否则&#xff0c;返回null。数据范围&#xff1a;n≤10000&#xff0c;1<结点值<10000要求&#xff1a;空间复杂度 O(1)&#xff0c;时间复杂度O(…

1、数据库概述

文章目录1 为什么要使用数据库2 数据库与数据库管理系统2.1 数据库的相关概念2.2 数据库与数据库管理系统的关系2.3 常见的数据库管理系统排名(DBMS)2.4 常见的数据库介绍3 MySQL介绍3.1 概述3.2 MySQL发展史重大事件3.3 关于MySQL 8.03.4 为什么选择MySQL3.5 Oracle vs MySQL4…

学成在线项目开发技巧整理---第二部分

1.静态资源处理通常项目会采用动静分离架构,利用Nginx作为静态资源服务器,存放所有静态资源:#访问动态资源时,将请求负载均衡到多个服务器实例或者多个网关实例 upstream webservice{server 192.168.200.146:8080; }server {listen 80;server_name localhost;#动态资源l…