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);  }  }  
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git使用命令总结

文章目录 Git 复制创建提交步骤Git 全局设置:创建 git 仓库:已有仓库? 遇到问题解决办法&#xff1a;问题一先git pull一下&#xff0c;具体流程为以下几步&#xff1a; 详细步骤 Git 复制 git clone -b RobotModelSetting/develop https://gitlab.123/PROJECT/123.git创建提…

解锁 AI 核心:神经网络与机器学习知名算法全解析

引言​ 在人工智能蓬勃发展的当下&#xff0c;神经网络与机器学习算法作为核心驱动力&#xff0c;广泛应用于各个领域。了解这些知名算法&#xff0c;能让我们更好地把握 AI 技术的精髓。接下来&#xff0c;一同深入探寻。​ 机器学习知名算法​ 线性回归&#xff08;Linear…

基于SpringBoot + Vue 的房屋租赁系统

基于springboot的房屋租赁管理系统-带万字文档 SpringBootVue房屋租赁管理系统 送文档 本项目有前台和后台两部分、多角色模块、不同角色权限不一样 共分三种角色&#xff1a;用户、管理员、房东 管理员&#xff1a;个人中心、房屋类型管理、房屋信息管理、预约看房管理、合…

30天学习Java第六天——Object类

Object类 java.lang.Object时所有类的超类。Java中所有类都实现了这个类中的方法。 toString方法 将Java对象转换成字符串的表示形式。 public String toString() {return getClass().getName() "" Integer.toHexString(hashCode()); }默认实现是&#xff1a;完…

DeepSeek在金融行业应用

引言 随着人工智能技术的快速发展&#xff0c;DeepSeek作为一款国产大模型&#xff0c;凭借其强大的语义理解、逻辑推理和多模态处理能力&#xff0c;在金融行业迅速崭露头角。其低成本、高效率和开源特性使其成为金融机构智能化转型的重要工具。本文旨在分析DeepSeek在金融行业…

【Unity】 HTFramework框架(六十二)Agent编辑器通用智能体(AI Agent)

更新日期&#xff1a;2025年3月14日。 Github源码&#xff1a;[点我获取源码] Gitee源码&#xff1a;[点我获取源码] 索引 编辑器通用智能体AIAgent类Friday&#xff08;星期五&#xff09;启用智能体设置智能体类型开放智能体权限智能体交互资源优化批处理运行代码联网搜索休闲…

以太坊AI代理与PoS升级点燃3月市场热情,2025年能否再创新高?

币热网深度报道&#xff1a;以太坊AI代理与PoS升级引爆3月热潮&#xff0c;2025年能否再攀历史新高&#xff1f; 原文来源&#xff1a;币热网 - 区块链信息资讯平台 以太坊升级&#xff0c;市场热情高涨 近期&#xff0c;以太坊市场犹如被一股神秘力量点燃&#xff0c;掀起了…

【赵渝强老师】达梦数据库的目录结构

达梦数据库安装成功后&#xff0c;通过使用Linux的tree命令可以非常方便地查看DM 8的目录结构。 tree -L 1 -d /home/dmdba/dmdbms#输出的信息如下&#xff1a; /home/dmdba/dmdbms ├── bin 存放DM数据库的可执行文件&#xff0c;例如disql命令等。 ├── bin2 ├── d…

2025探索短剧行业新可能报告40+份汇总解读|附PDF下载

原文链接&#xff1a;https://tecdat.cn/?p41043 近年来&#xff0c;短剧以其紧凑的剧情、碎片化的观看体验&#xff0c;迅速吸引了大量用户。百度作为互联网巨头&#xff0c;在短剧领域积极布局。从早期建立行业专属模型冷启动&#xff0c;到如今构建完整的商业生态&#xf…

基于java(springboot+mybatis)汽车信息管理系统设计和实现以及文档

基于java(springbootmybatis)汽车信息管理系统设计和实现以及文档 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各…

线程同步:多线程编程的核心机制

一、线程同步的意义 线程同步的主要目的是避免数据竞争、保证数据一致性、控制线程执行顺序&#xff0c;并提高程序的性能和稳定性。具体意义包括&#xff1a; ​避免数据竞争&#xff1a;防止多个线程同时修改共享资源&#xff0c;导致不可预测的行为。​保证数据一致性&…

Qt QML实现弹球消砖块小游戏

前言 弹球消砖块游戏想必大家都玩过&#xff0c;很简单的小游戏&#xff0c;通过移动挡板反弹下落的小球&#xff0c;然后撞击砖块将其消除。本文使用QML来简单实现这个小游戏。 效果图&#xff1a; 正文 代码目录结构如下&#xff1a; 首先是小球部分&#xff0c;逻辑比较麻…

Android自动化测试工具

细解自动化测试工具 Airtest-CSDN博客 以下是几种常见的Android应用自动化测试工具&#xff1a; Appium&#xff1a;支持多种编程语言&#xff0c;如Java、Python、Ruby、JavaScript等。可以用于Web应用程序和原生应用程序的自动化测试&#xff0c;并支持iOS和Android平台。E…

消息队列实现 Exactly Once,看 Pulsar 是怎样实现的。

大家好 &#xff0c;我是君哥。 在使用消息队列时&#xff0c;我们希望消息能够精准推送&#xff08;Exactly Once&#xff09;&#xff0c;不会丢失、也不会重复。Exactly Once 其实是很难实现的&#xff0c;Pulsar 这款消息中间件使用事务消息实现了 Exactly Once&#xff0…

Audacity的安装和使用

安装 下载地址&#xff1a;官方网站&#xff1a;Audacity 软件开源免费&#xff0c;但部分功能可能需要额外插件。 一.介绍 Audacity 是一款免费、开源的音频编辑软件&#xff0c;适用于Windows、macOS、Linux等操作系统。它支持多轨编辑、录音、音频效果处理、格式转换等功…

C++:类和对象(从底层编译开始)详解[前篇]

目录 一.inline内联的详细介绍 &#xff08;1&#xff09;为什么在调用内联函数时不需要建立栈帧&#xff1a; &#xff08;2&#xff09;为什么inline声明和定义分离到两个文件会产生链接错误&#xff0c;链接是什么&#xff0c;为什么没有函数地址&#xff1a; 二.类&…

【蓝桥】-动态规划-倒水

目录 一、问题描述​ 二、解题思路 三、完整代码 二维dp 使用滚动数组 一、问题描述 二、解题思路 一个变种的01背包问题&#xff1a; 不选该物品&#xff1a;获得固定收益 e 选择方案1&#xff1a;消耗体积 a&#xff0c;获得价值 b 选择方案2&#xff1a;消耗体积 c&…

【软考网工-实践篇】DHCP 动态主机配置协议

一、DHCP简介 DHCP&#xff0c;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议。 位置&#xff1a;DHCP常见运行于路由器上&#xff0c;作为DHCP服务器功能&#xff1a;用于自动分配IP地址及其他网络参数给网络中的设备作用&#xff1a;简化网络管理&…

使用 Arduino 和 ThingSpeak 通过互联网进行实时温度和湿度监测

使用 ThingSpeak 和 Arduino 通过 Internet 进行温度和湿度监控 湿度和温度是许多地方(如农场、温室、医疗、工业家庭和办公室)非常常见的测量参数。我们已经介绍了使用 Arduino 进行湿度和温度测量,并在 LCD 上显示数据。 在这个物联网项目中,我们将使用ThingSpeak在互联…

电子电子架构 --- 车载ECU信息安全

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…