WPF 触摸底层 PenImc 是如何工作的
在 WPF 里面有其他软件完全比不上的超快速的触摸,这个触摸是通过 PenImc 获取的。现在 WPF 开源了,本文就带大家来阅读触摸底层的代码,阅读本文需要一点 C# 和 C++ 基础
现在 WPF 开源,所有源代码都可以在官方代码找到,本文只是让大家能够更快的了解整个触摸的代码和更快的了解代码,和知道对应的功能在哪个代码
在WPF的触摸的 PenThreadWorker 调用 ThreadProc 的方法,就通过 MS.Win32.Penimc.UnsafeNativeMethods.GetPenEvent 方法获取触摸。本文仅讨论在 PenThreadWorker 下层的内容,在此上层的内容,请看WPF 触摸到事件
那么在 PenImc 里面做了什么?
在 PenImc 原理里面,其实就是通过共享内存和 COM 的方式通过 RealTimeStylus 的方式快速获取触摸消息
先通过 WISPTIS_SM_SECTION_NAME 和 WISPTIS_SM_MUTEX_NAME 分别拿到共享内存和进程锁这样可以通过锁通知共享内存收到消息,然后通过读取内存的信息返回到上层
整个初始化的代码放在 PimcContext.cpp 里
在 HRESULT CPimcContext::InitNamedCommunications(__in CComPtr<ITabletContextP> pCtxP) 的方法里面,初始 szSectionName 字符串作为命名管道连接方法
TCHAR szSectionName[MAX_PATH + 1];
StringCchPrintf(
szSectionName,
LENGTHOFARRAY(szSectionName),
WISPTIS_SM_SECTION_NAME,
dwPid,
dwFileMappingId);
而 WISPTIS_SM_SECTION_NAME 的定义如下
#define WISPTIS_SM_MORE_DATA_EVENT_NAME _T("wisptis-1-%d-%u")
#define WISPTIS_SM_MUTEX_NAME _T("wisptis-2-%d-%u")
#define WISPTIS_SM_SECTION_NAME _T("wisptis-3-%d-%u")
#define WISPTIS_SM_THREAD_EVENT_NAME _T("wisptis-4-%u")
此时通过打开内存的方式
m_hFileMappingSharedMemory = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, szSectionName);
可以获取内存信息
m_pSharedMemoryHeader = (SHAREDMEMORY_HEADER*)MapViewOfFile(
m_hFileMappingSharedMemory, // handle
FILE_MAP_READ | FILE_MAP_WRITE, // desired access
0, // offset in file, High
0, // offset in file, Low
sizeof(SHAREDMEMORY_HEADER)); // number of bytes to map
m_pbSharedMemoryRawData = (BYTE*)MapViewOfFile(
m_hFileMappingSharedMemory, // handle
FILE_MAP_READ, // desired access
0, // offset in file, High
0, // offset in file, Low
m_pSharedMemoryHeader->cbTotal);// number of bytes to map
关于打开的代码请看
ITabletContextP::UseNamedSharedMemoryCommunications method - Win32 apps
此时就可以通过 m_pbSharedMemoryRawData 获取内存信息
这就是初始化的代码
在 WPF 调用 GetPenEvent 方法,将会进入 PimcContext.cpp 的 GetPenEvent 方法
在这个方法里面先通过 MsgWaitForMultipleObjectsEx 等待 Wisp 服务的收集,在收集完成之后会释放锁,进入 GetPenEventCore 方法
在 GetPenEventCore 使用很长的判断逻辑,其中主要是判断当前是获取数据才会进入到 WPF 的收集到触摸点
switch (dwWait)
{
case WAIT_TIMEOUT:
m_fSingleFireTimeout = FALSE; // (only fire the timeout once before more data shows up)
*pEvt = 1; // timeout event
*pCursorId = 0;
*pcPackets = 0;
*pcbPacket = 0;
*pPackets = NULL;
break;
case WAIT_OBJECT_0 + 0: // update
// 忽略代码
case WAIT_OBJECT_0 + 1: // more data
// 这里就是等待共享内存
DWORD dwWaitAccess = WaitForSingleObject(m_hMutexSharedMemory, INFINITE);
}
通过上面代码可以看到在 m_hMutexSharedMemory 的信息,可以在 m_pSharedMemoryHeader 读取
switch (m_pSharedMemoryHeader->dwEvent)
{
case WM_TABLET_PACKET:
case WM_TABLET_CURSORDOWN:
case WM_TABLET_CURSORUP:
*pEvt = m_pSharedMemoryHeader->dwEvent;
*pCursorId = m_pSharedMemoryHeader->cid;
*pcPackets = m_pSharedMemoryHeader->cPackets;
*pcbPacket = m_pSharedMemoryHeader->cbPackets / m_pSharedMemoryHeader->cPackets;
CHR(EnsurePackets(m_pSharedMemoryHeader->cbPackets));
CopyMemory(m_pbPackets, m_pbSharedMemoryPackets, m_pSharedMemoryHeader->cbPackets);
*pPackets = (INT_PTR)m_pbPackets;
#ifdef DELIVERY_PROFILING
for (INT iPacket = 0; iPacket < *pcPackets; iPacket++)
{
INT iOffset = iPacket * (*pcbPacket) / sizeof(LONG);
switch (m_pSharedMemoryHeader->dwEvent)
{
case WM_TABLET_PACKET: ProfilePackets(/*fDown*/FALSE, /*fUp*/FALSE, ((LONG*)m_pbSharedMemoryPackets)[iOffset + 0], ((LONG*)m_pbSharedMemoryPackets)[iOffset + 1]); break;
case WM_TABLET_CURSORDOWN: ProfilePackets(/*fDown*/TRUE, /*fUp*/FALSE, ((LONG*)m_pbSharedMemoryPackets)[iOffset + 0], ((LONG*)m_pbSharedMemoryPackets)[iOffset + 1]); break;
case WM_TABLET_CURSORUP: ProfilePackets(/*fDown*/FALSE, /*fUp*/TRUE, ((LONG*)m_pbSharedMemoryPackets)[iOffset + 0], ((LONG*)m_pbSharedMemoryPackets)[iOffset + 1]); break;
}
}
#endif
break;
case WM_TABLET_CURSORINRANGE:
case WM_TABLET_CURSOROUTOFRANGE:
*pEvt = m_pSharedMemoryHeader->dwEvent;
*pCursorId = m_pSharedMemoryHeader->cid;
*pcPackets = 0;
*pcbPacket = 0;
*pPackets = NULL;
break;
case WM_TABLET_SYSTEMEVENT:
*pEvt = m_pSharedMemoryHeader->dwEvent;
*pCursorId = m_pSharedMemoryHeader->cid;
*pcPackets = 0;
*pcbPacket = 0;
*pPackets = NULL;
m_sysEvt = m_pSharedMemoryHeader->sysEvt;
m_sysEvtData = m_pSharedMemoryHeader->sysEvtData;
break;
default:
*pEvt = 0;
*pCursorId = 0;
*pcPackets = 0;
*pcbPacket = 0;
*pPackets = NULL;
break;
}
定义的代码放在 pentypes.h 文件
#define WM_TABLET_DEFBASE 0x02C0
#define WM_TABLET_CONTEXTCREATE (WM_TABLET_DEFBASE + 0)
#define WM_TABLET_CONTEXTDESTROY (WM_TABLET_DEFBASE + 1)
#define WM_TABLET_CURSORNEW (WM_TABLET_DEFBASE + 2)
#define WM_TABLET_CURSORINRANGE (WM_TABLET_DEFBASE + 3)
#define WM_TABLET_CURSOROUTOFRANGE (WM_TABLET_DEFBASE + 4)
#define WM_TABLET_CURSORDOWN (WM_TABLET_DEFBASE + 5)
#define WM_TABLET_CURSORUP (WM_TABLET_DEFBASE + 6)
#define WM_TABLET_PACKET (WM_TABLET_DEFBASE + 7)
#define WM_TABLET_ADDED (WM_TABLET_DEFBASE + 8)
#define WM_TABLET_DELETED (WM_TABLET_DEFBASE + 9)
#define WM_TABLET_SYSTEMEVENT (WM_TABLET_DEFBASE + 10)
#define WM_TABLET_MAX (WM_TABLET_DEFBASE + WM_TABLET_MAXOFFSET)
这里的 WM_TABLET_CURSORINRANGE 是 (WM_TABLET_DEFBASE + 3) 也就是 707 对应在 WPF 定义的 PenEventPenInRange 的值
const int PenEventPenInRange = 707;
const int PenEventPenOutOfRange = 708;
const int PenEventPenDown = 709;
const int PenEventPenUp = 710;
const int PenEventPackets = 711;
const int PenEventSystem = 714;
也就是上面的代码就是整个触摸的核心代码
更多代码请看 https://github.com/dotnet/wpf/
IRealTimeStylus::GetPacketDescriptionData (rtscom.h) - Win32 apps
WPF 触摸底层 PenImc 是如何工作的的更多相关文章
- WPF 触摸到事件
原文:WPF 触摸到事件 本文从代码底层告诉大家,在触摸屏幕之后是如何拿到触摸点并且转换为事件 在 WPF 界面框架核心就是交互和渲染,触摸是交互的一部分.在 WPF 是需要使用多个线程来做触摸和渲染 ...
- 通过解读 WPF 触摸源码,分析 WPF 插拔设备触摸失效的问题(问题篇)
在 .NET Framework 4.7 以前,WPF 程序的触摸处理是基于操作系统组件但又自成一套的,这其实也为其各种各样的触摸失效问题埋下了伏笔.再加上它出现得比较早,触摸失效问题也变得更加难以解 ...
- WPF 多点触摸开发[2]:WPF触摸的几个手势的执行顺序
原文:WPF 多点触摸开发[2]:WPF触摸的几个手势的执行顺序 前面我讲了在win7下使用模拟器,进行调试模拟多点触摸,其实际开发中这样也比较麻烦.. 要拿几个鼠标. 所以更多的人会 买个触摸套 套 ...
- WPF 设置窗体大小为显示器工作区域大小
最近做的项目遇到一个问题,窗体在1680*1050分辨率下显示,系统字体设置为小字体时,显示没问题,但是调到中等字体时,窗体显示位置出了问题.主窗体为无边框窗体,拖动及放大缩小事件都是自己写的. ...
- WPF 插拔触摸设备触摸失效
原文:WPF 插拔触摸设备触摸失效 最近使用 WPF 程序,在不停插拔触摸设备会让 WPF 程序触摸失效.通过分析 WPF 源代码可以找到 WPF 触摸失效的原因. 在 Windows 会将所有的 H ...
- 深入了解 WPF Dispatcher 的工作原理(PushFrame 部分)
在上一篇文章 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分) 中我们发现 Dispatcher.Invoke 方法内部是靠 Dispatcher.Pu ...
- WPF 程序无法触摸操作?我们一起来找原因和解决方法!
WPF 自诞生以来就带着微软先生的傲慢.微软说 WPF 支持触摸,于是 WPF 就真的支持触摸了.对,我说的是"支持触摸",那种摸上去能点能动的:偶尔还能带点儿多指的炫酷效果.但是 ...
- WPF 禁用实时触摸
原文:WPF 禁用实时触摸 微软想把 WPF 作为 win7 的触摸好用的框架,所以微软做了很多特殊的兼容.为了获得真实的触摸消息,微软提供了 OnStylusDown, OnStylusUp, 和 ...
- #747 –在WPF程序的触摸操作中使用惯性移动 (Implementing Inertia during Touch Manipulation)
原文:#747 –在WPF程序的触摸操作中使用惯性移动 (Implementing Inertia during Touch Manipulation) 原文地址:https://wpf.2000th ...
- 2018-8-15-WPF-插拔触摸设备触摸失效
title author date CreateTime categories WPF 插拔触摸设备触摸失效 lindexi 2018-08-15 08:12:47 +0800 2018-08-09 ...
随机推荐
- 记录-VUE中常用的4种高级方法
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 1. provide/inject provide/inject 是 Vue.js 中用于跨组件传递数据的一种高级技术,它可以将数据注入到 ...
- PS-AXI-GPIO-流水灯设计
PS-AXI-GPIO-流水灯设计 1.实验目的 在了解了AXI协议的基本内容后,通过已经设计好的AXI的IP核来了解实际设计中AXI的工作原理和设计原理是必要的.这个实验以前实际上按照教程做过,但是 ...
- PSS:你距离NMS-free+提点只有两个卷积层 | 2021论文
论文提出了简单高效的PSS分支,仅需在原网络的基础上添加两个卷积层就能去掉NMS后处理,还能提升模型的准确率,而stop-grad的训练方法也挺有意思的,值得一看 来源:晓飞的算法工程笔记 公众号 ...
- Windows系统使用ODBC驱动访问KingaseES数据库及常见问题解决
Windows系统使用KingbaseES ODBC驱动访问KingaseES数据库及常见问题解决. 一.获取KingbaseES数据库ODBC驱动: 在官网下载KingbaseES数据库安装包,选择 ...
- archlinux xfce 修改用户主目录名称
操作有风险,修改用户主目录名称后一些链接了旧主目录的的链接可能仍未修改.导致链接用不了,需要手动指定链接 1.删除指定用户保存的会话,未删除应该会导致修改用户主目录名称后进不去会话 2.切换到其它用户 ...
- 如何修改 ARM Linux 系统的启动画面
[如何修改 ARM Linux 系统的启动画面 Table of Contents U-Boot 的启动画面 内核的启动画面 PPM 图片格式 显示启动画面的过程 更换启动画面 动态修改启动画面 一些 ...
- Scala mutable.Map可变的Map
1 package chapter07 2 3 import scala.collection.mutable 4 5 object Test09_MutableMap { 6 def main(ar ...
- #斜率优化,二分#CF631E Product Sum
题目 有一个数列 \(a\),其权值为 \(\sum_{i=1}^ni*a_i\), 现在可以任意选择其中一个数字扔到任意位置,使权值和最大. \(n\leq 2*10^5,|a_i|\leq 10^ ...
- SQL HAVING 子句详解:在 GROUP BY 中更灵活的条件筛选
SQL HAVING子句 HAVING子句被添加到SQL中,因为WHERE关键字不能与聚合函数一起使用. HAVING语法 SELECT column_name(s) FROM table_name ...
- 快速加入Health Kit,一文了解审核流程
HUAWEI Health Kit是为华为生态应用打造的基于华为帐号和用户授权的运动健康数据开放平台. 在获取用户授权后,开发者可以使用Health Kit提供的开放能力获取运动健康数据,基于多种类型 ...