当前位置: 首页 > article >正文

Qt开发:QtWebEngine中操作选择文本

查找选择

在QtWebEngine中,可以使用QWebEnginePage的findText方法来查找文本,查找成功以后,将自动选择当前文本。

QWebEnginePage可以通过QWebEngineView的page()来取得。

比如,如下代码可以在页面中查找hello,world并选择。

// view是一个QWebEngineView
auto page = view->page();
page->findText("hello,world");

findText方法的原型为:

void QWebEnginePage::findText(const QString &subString, QWebEnginePage::FindFlags options = {}, const std::function<void (const QWebEngineFindTextResult &)> &resultCallback = std::function<void(const QWebEngineFindTextResult &)>());

可以通过resultCallback这个参数,传递一个回调函数,根据QWebEngineFindTextResult变量,处理查找到的结果。

QWebEngineFindTextResult有两个方法,分别是activeMatch()和numberOfMatches(),分别用来表示当前激活的结果,以及一共查找到的结果总数。

获取选择文本

除了查找这种编程的方式以外,QWebEngineView作为一款浏览器控件,也支持用户手动选择。

当用户通过鼠标选择文本以后,可以通过QWebEnginePage的selectedText()方法来获得文本。

如:

auto text = mPage->selectedText ();
qDebug () << "user selected" << text;

获取选择位置

用户选择文本以后,我们除了想知道这段文本的内容以外,可能还需要知道这段文本的位置。即,这段文本在整个页面中处于什么位置。

QWebEngine并没有直接的方法,来取得一段文本的位置,但是QWebEnginePage有一个runJavaScript()方法,所以我们可以通过执行一些JavaScript,来间接地取得这些信息。

QWebEnginePage的runJavaScript()方法的原型为:


void QWebEnginePage::runJavaScript(const QString &scriptSource, const std::function<void (const QVariant &)> &resultCallback);void QWebEnginePage::runJavaScript(const QString &scriptSource, quint32 worldId = 0, const std::function<void (const QVariant &)> &resultCallback = {})

即,我们可以通过回调函数,取得执行的JavaScript的结果。

基本的原理如下:

  1. 通过window.getSelection()取得所选区域Selection。
  2. 通过Selection的getRangeAt取得第一个Range。
  3. 分别返回Range的第一个节点的开头的全局偏移量,以及第二个节点的结尾的全局偏移量,为所选文本的偏移。

代码如下:

function getSelectionOffset() {const selection = window.getSelection();const createOffsetRange = (container, offset) => {const range = document.createRange();range.setStart(document.documentElement, 0);range.setEnd(container, offset);return range.toString().length;};try {const range = selection.getRangeAt(0);return [createOffsetRange(range.startContainer, range.startOffset), createOffsetRange(range.endContainer, range.endOffset)];} catch (error) {console.error('Error accessing selection range:', error);return [null, null];}
}

有了上面的JavaScript,我们就可以通过runJavaScript来获取结果了。

需要注意的是,runJavaScript是异步执行的。

即,如果我们需要在执行JavaScript结束之后 ,再接着执行runJavaScript后面的过程,需要手动加入同步代码。其中一个方法,是使用一个QEventLoop。

QEventLoop调用exec方法以后,遇到quit才会返回。

如:

  int begin = -1;int end = -1;QEventLoop loop;mPage->runJavaScript (R"(var selection = window.getSelection();if (selection.rangeCount > 0) {let range = selection.getRangeAt(0);let start = document.createRange();start.setStart(document.documentElement, 0);start.setEnd(range.startContainer, range.startOffset);let startOffset = start.toString().length;let end = document.createRange();end.setStart(document.documentElement, 0);end.setEnd(range.endContainer, range.endOffset);let endOffset = end.toString().length;[startOffset, endOffset];} else {[null, null];})",[&loop, &begin, &end] (const QVariant &result) {if (result.isValid () && result.typeId () == QMetaType::QVariantList){auto offsets = result.toList ();begin = offsets[0].toInt ();end = offsets[1].toInt ();qDebug () << "Begin offset:" << offsets[0].toInt();qDebug () << "End offset:" << offsets[1].toInt();loop.quit();  //结束QEventLoop}});loop.exec ();  //上面的loop.quit()之后,这里才返回。return std::make_tuple (begin, end);

通过位置选择

能够通过选择取得位置,反过来就可以通过位置,进行选择。方法仍然是通过runJavaScript,这里不再示意runJavaScript的用法,只演示JavaScript代码。

选择的时候,需要根据上一步的全局偏移量,对整个页面的DOM进行遍历,找到相应的节点偏移量。

所以,这里分成三个函数实现:

选择一个节点的部分文本

选中的方法,是新建一个DocumentFragment,把不需要选择的文本,与选择的文本作为子节点加入,之后替换原来的节点为新建的DocumetFragment。

代码如下:

function underlineTextNode(textNode, startOffset, endOffset = -1) {  if (!(textNode instanceof Text)) {  throw new Error('Invalid text node provided');  }  const textContent = textNode.nodeValue;  const validEndOffset = endOffset === -1 ? textContent.length : endOffset;  if (startOffset < 0 || validEndOffset > textContent.length || startOffset > validEndOffset) {  throw new Error('Invalid offset values');  }  const parent = textNode.parentNode;  if (!parent) {  throw new Error('Text node has no parent element');  }  const beforeText = textContent.slice(0, startOffset);  const underlinedText = textContent.slice(startOffset, validEndOffset);  const afterText = textContent.slice(validEndOffset);  const underlineElement = document.createElement('u');  underlineElement.textContent = underlinedText;  const fragment = document.createDocumentFragment();  if (beforeText) fragment.appendChild(document.createTextNode(beforeText));  fragment.appendChild(underlineElement);  if (afterText) fragment.appendChild(document.createTextNode(afterText));  parent.replaceChild(fragment, textNode);  
}  

遍历函数,在回调中确定提前返回

function traverseTextNodes(root, callback) {  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null,);  let node;  while ((node = walker.nextNode())) {  if (callback(node) === false) break;  }  
}

选择所有节点

方法是通过遍历,在回调函数中找到需要选择的所有节点。

然后,依次对每个节点调用第一个选择的函数。

function underlineByOffset(startOffset, endOffset) {  if (startOffset >= endOffset || startOffset < 0) {  throw new Error('Invalid offset range');  }  let currentOffset = 0;  const nodesInfo = {  start: {node: null, offset: 0}, end: {node: null, offset: 0}, between: []  };  traverseTextNodes(document.documentElement, (textNode) => {  const nodeLength = textNode.nodeValue.length;  const nodeEnd = currentOffset + nodeLength;  if (!nodesInfo.start.node && currentOffset <= startOffset && nodeEnd > startOffset) {  nodesInfo.start.node = textNode;  nodesInfo.start.offset = startOffset - currentOffset;  }  if (!nodesInfo.end.node && currentOffset <= endOffset && nodeEnd > endOffset) {  nodesInfo.end.node = textNode;  nodesInfo.end.offset = endOffset - currentOffset;  return false;  }  if (nodesInfo.start.node && !nodesInfo.end.node && textNode !== nodesInfo.start.node) {  nodesInfo.between.push(textNode);  }  currentOffset = nodeEnd;  return true;  });  if (nodesInfo.start.node && nodesInfo.end.node) {  underlineTextNode(nodesInfo.start.node, nodesInfo.start.offset, nodesInfo.start.node === nodesInfo.end.node ? nodesInfo.end.offset : -1);  nodesInfo.between.forEach(node => {  underlineTextNode(node, 0);  });  if (nodesInfo.start.node !== nodesInfo.end.node) {  underlineTextNode(nodesInfo.end.node, 0, nodesInfo.end.offset);  }  }  
}

相关文章:

Qt开发:QtWebEngine中操作选择文本

查找选择 在QtWebEngine中&#xff0c;可以使用QWebEnginePage的findText方法来查找文本&#xff0c;查找成功以后&#xff0c;将自动选择当前文本。 QWebEnginePage可以通过QWebEngineView的page()来取得。 比如&#xff0c;如下代码可以在页面中查找hello,world并选择。 …...

VUE的脚手架搭建引入类库

VUE的小白脚手架搭建 真的好久好久自己没有发布自己博客了,对于一直在做后端开发的我 ,由于社会卷啊卷只好学习下怎么搭建前端,一起学习成长吧~哈哈哈(最终目的,能够懂并简易开发) 文章目录 VUE的小白脚手架搭建1.下载node.js2.安装vue脚手架3.创建一个项目4.代码规范约束配置(…...

分布式系统日志排查综合场景

排查背景 在一个大型分布式电商系统中&#xff0c;用户反馈在进行商品结算时出现了报错。系统由多个子系统构成&#xff0c;包括商品管理系统、订单系统、支付系统等&#xff0c;各子系统分布在不同服务器上&#xff0c;且日志文件分散存储。 排查过程 确定当前位置并切换到可…...

android lmkd.rc 介绍

service service lmkd /system/bin/lmkdclass coreuser lmkdgroup lmkd system readproccapabilities DAC_OVERRIDE KILL IPC_LOCK SYS_NICE SYS_RESOURCEcriticalsocket lmkd seqpacketpasscred 0660 system systemtask_profiles ServiceCapacityLow属于核心服务组&#xff0…...

Android Studio执行Run操作报Couldn‘t terminate previous instance of app错误

步骤1、在项目根目录下build.gradle文件最后添加如下内容 //自定义任务名&#xff1a;assembleAndInstall tasks.register(assembleAndInstall, Exec.class, new Action<Exec>() {Overridevoid execute(Exec exec) {//设置自定义任务组名exec.setGroup(custom task)//当…...

Matlab 双线性插值(二维)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 双线性插值是一种 二维插值方法,用于计算 栅格(Grid) 或 像素点 之间的插值值。它主要用于 图像缩放、旋转、变换 等操作,以在新像素位置估算灰度值或颜色值。 如上图所示,假设存在一个二维离散函数(如图像)…...

1700. 无法吃午餐的学生数量

无法吃午餐的学生数量 题目描述尝试做法推荐做法 题目描述 学校的自助午餐提供圆形和方形的三明治&#xff0c;分别用数字 0 和 1 表示。所有学生站在一个队列里&#xff0c;每个学生要么喜欢圆形的要么喜欢方形的。 餐厅里三明治的数量与学生的数量相同。所有三明治都放在一个…...

uv命令介绍(高性能Python包管理工具,旨在替代pip、pip-tools和virtualenv等传统工具)

文章目录 **主要功能**1. **快速安装和管理 Python 包**2. **生成和管理锁文件 (requirements.lock)**3. **创建虚拟环境**4. **与 poetry 兼容** **核心优势**1. **极快的速度**&#xff1a;基于 Rust 实现&#xff0c;利用多线程和缓存大幅加速依赖解析。2. **轻量且独立**&a…...

杨辉三角形(信息学奥赛一本通-2043)

【题目描述】 例5.11 打印杨辉三角形的前n(2≤n≤20)行。杨辉三角形如下图&#xff1a; 当n5时 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 输出&#xff1a; 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 【输入】 输入行数n。 【输出】 输出如题述三角形。n行&#…...

使用easyexcel实现单元格样式设置和下拉框设置

1.单元格样式设置 1.1实体类 public class DemoData {ExcelProperty("PK")private String name;ExcelProperty("年龄")private int age;// 必须提供无参构造方法public DemoData() {}public DemoData(String name, int age) {this.name name;this.age …...

TCP 三次握手四次挥手过程详解

注&#xff1a;本文为 “TCP 的三次握手与四次挥手” 相关文章合辑。 英文引文&#xff0c;机翻未校。 中文引文&#xff0c;未整理去重。 英文引文第二篇&#xff0c;实为国内《稀土掘金技术社区》文章&#xff0c;没检索到原文&#xff0c;此处 “出口转内销” 。 如有内…...

射频相关概念

射频&#xff08;Radio Frequency, RF) 是电磁波谱中频率范围在 3 kHz 到 300GHz的电磁波&#xff0c;广泛应用于通信、雷达、广播、医疗等领域。其基本原理涉及电磁波的产生、传播、调制与解调&#xff0c;以及射频系统的设计。以下是射频技术的核心要点&#xff1a; 1. 电磁…...

几款可用于绘制工艺原理图的开源框架

一、LogicFlow 由滴滴团队开发的开源流程图框架&#xff0c;支持高度定制的工艺原理图绘制。 • 核心特性&#xff1a; • 提供拖拽式界面和丰富的节点类型&#xff08;矩形、圆形、多边形等&#xff09;&#xff0c;支持自定义节点形状、样式和交互逻辑。 • 支持插件扩展&am…...

27.卷2的答案

CSP-J离我们不远了&#xff0c;加加油啦&#xff01; 1.堆排序最坏时间复杂度是&#xff1f; 解析&#xff1a;平时多多练习可知&#xff0c;最坏时间复杂度是O(n log n)。 2.哪条能将s中的数值保留一位&#xff0c;并将第二位四舍五入&#xff1f; 解析&#xff1a;经过试…...

程序编译生成的文件

目录 .i 文件 .s 文件 .o文件 总结 在 C 编程中&#xff0c;.i、.s和 .o 文件是编译过程中生成的不同阶段的文件&#xff0c;它们代表不同的含义&#xff1a; .i 文件 全称 &#xff1a;预处理后的文件&#xff08;Intermediate File&#xff09;。 含义&#xff1a;.i文件…...

C++类的基础题(4)

练习1&#xff1a;&#xff08;简单&#xff09; 基于如下程序&#xff0c;按要求修改和完善。 #include <iostream> using namespace std; class Student {public: Student(int n,float s):num(n),score(s){} void change(int n,float s) {numn;scores;} void displ…...

浏览器中输入某个地址后发生了什么

首先浏览器会进行DNS解析&#xff0c;将网址中的域名&#xff08;比如&#xff1a;jcm.com&#xff09;解析为IP地址。理解&#xff1a;DNS为电话本&#xff0c;域名为名字&#xff0c;IP地址为电话号码&#xff1b;其次浏览器需要和网站服务器建立连接&#xff0c;也就是通过三…...

MindGYM:一个用于增强视觉-语言模型推理能力的合成数据集框架,通过生成自挑战问题来提升模型的多跳推理能力。

2025-03-13&#xff0c;由中山大学和阿里巴巴集团的研究团队提出了MindGYM框架&#xff0c;通过合成自挑战问题来增强视觉-语言模型&#xff08;VLMs&#xff09;的推理能力。MindGYM框架通过生成多跳推理问题和结构化课程训练&#xff0c;显著提升了模型在推理深度和广度上的表…...

WPS 搭配 Zotero 插件使用

安装Zotero后&#xff0c;Word自动引入了插件&#xff0c;但WPS却没有&#xff0c;做为WPS的重度用户&#xff0c;这是不行的。 解决方案&#xff1a; 1.找到 Zotero.dotm 一般在安装目录下&#xff0c; 2.然后复制到WPS的startup下 我的目录是&#xff1a;C:\Users\lianq…...

汽车NVH诊断案例 | 纯电车急加速过大弯底盘异响

引言 失去发动机的掩蔽效应后&#xff0c;新能源电车的NVH问题&#xff0c;成为了困扰维修技师新难点。风噪、胎噪、电机高频啸叫等问题更容易车主识别&#xff0c;根源却难以被有效分辨。如何更精准且高效地识别电车NVH问题根源&#xff1f;今天分享的这个案例&#xff0c;内…...

万字长文详解嵌入式电机软件开发

第一章&#xff1a;嵌入式电机概述 1.1 电机类型&#xff1a;选对 “主角” 有多重要&#xff1f; 在嵌入式电机控制系统里&#xff0c;电机就如同故事中的主角&#xff0c;选对了方能使整个剧情顺利推进。不同应用场景对精度、速度、功率以及成本的需求各异&#xff0c;因而了…...

电机控制常见面试问题(十二)

文章目录 一.电机锁相环1.理解锁相环2.电机控制中的锁相环应用3.数字锁相环&#xff08;DPLL&#xff09; vs 模拟锁相环&#xff08;APLL&#xff09;4.锁相环设计的关键技术挑战5.总结 二、磁链观测1.什么是磁链&#xff1f;2.为什么要观测磁链&#xff1f;3.怎么观测磁链&am…...

卡尔曼滤波算法从理论到实践:在STM32中的嵌入式实现

摘要&#xff1a;卡尔曼滤波&#xff08;Kalman Filter&#xff09;是传感器数据融合领域的经典算法&#xff0c;在姿态解算、导航定位等嵌入式场景中广泛应用。本文将从公式推导、代码实现、参数调试三个维度深入解析卡尔曼滤波&#xff0c;并给出基于STM32硬件的完整工程案例…...

添加 ChatGPT/Grok/Gemini 到浏览器搜索引擎

添加 ChatGPT/Grok/Gemini 到浏览器搜索引擎 添加 ChatGPT/Grok/Gemini 到浏览器搜索引擎如何添加步骤 1: 打开浏览器设置步骤 2: 添加新搜索引擎步骤 3: 保存设置 注意事项 添加 ChatGPT/Grok/Gemini 到浏览器搜索引擎 在使用 ChatGPT/Grok/Gemini 进行对话时&#xff0c;每次…...

【SpringMVC】常用注解:@RequestBody

1.作用 用于获取请求实体内容&#xff0c;直接使用得到的是keyvalue&keyvalue的数据。获取请求实体内容不适用get请求。 2.属性 required 描述是否有请求体&#xff0c;默认值为true。当取值为true时&#xff0c;get 请求方式会报错。如果取值为false&#xff0c;get请…...

数学建模之数学模型-3:动态规划

文章目录 动态规划基本概念阶段状态决策策略状态转移方程指标函数最优指标函数 动态规划的求解前向算法后向算法二者比较 应用案例 一种中文分词的动态规划模型摘要引言动态规划的分词模型问题的数学描述消除状态的后效性选择优化条件 算法描述和计算实例算法的效率分析和评价结…...

Amazon Quantum Ledger Database (QLDB):革新数据可信记录的终极解决方案

在数字化浪潮中&#xff0c;企业数据的安全性与可信性成为核心挑战。无论是金融交易的透明审计、供应链的全程追踪&#xff0c;还是医疗记录的真实性验证&#xff0c;如何确保数据不可篡改且可追溯&#xff0c;已成为企业亟待解决的难题。Amazon Quantum Ledger Database (QLDB…...

Navicat SqlServer 设置自增主键

Navicat是一款优秀的数据库管理工具&#xff0c;可以连接很多类型的数据库。使用它可以极大的提高工作效率。 Navicat 不能设置SqlServer自增字段&#xff0c;只能通过sql语句来实现 建表时设置 create table <表名> ( <字段1-主键> int identity (1,1) primar…...

开源后台管理系统推荐

前言 在当今数字化时代&#xff0c;企业和组织对于管理和运营资源的需求日益增加。开源后台管理系统应运而生&#xff0c;为用户提供了一个灵活、可定制化的管理平台。本文将介绍开源后台管理系统的概念和优势&#xff0c;探讨常见的开源后台管理系统&#xff0c;以及如何选择…...

韦伯望远镜的拉格朗日点计算推导过程,包含MATLAB和python运动轨迹仿真代码

研究过程 起源与提出&#xff1a;1687 年牛顿提出 “三体问题”&#xff0c;旨在研究三个可视为质点的天体在相互之间万有引力作用下的运动规律&#xff0c;但因运动方程过于复杂&#xff0c;难以得到完全解。欧拉的贡献1&#xff1a;1767 年&#xff0c;瑞士数学家莱昂哈德・…...