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

server/2025/3/19 14:03:30/
webkit-tap-highlight-color: rgba(0, 0, 0, 0);">

查找选择

在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的第一个节点的开头的全局偏移量,以及第二个节点的结尾的全局偏移量,为所选文本的偏移。

代码如下:

javascript">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。

代码如下:

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

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

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

选择所有节点

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

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

javascript">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.ppmy.cn/server/176256.html

相关文章

力扣Hot100——169. 多数元素

解法1&#xff1a;使用HashMap 将nums数组映射到HashMap中&#xff0c;键为nums的值&#xff0c;值为nums中值的数量&#xff1b; 然后遍历哈希表&#xff0c;返回值最大的键 class Solution {private Map<Integer, Integer> countNums(int[] nums) {Map<Integer, Int…

什么是MCP(Model Context Protocol)?对话、意图识别、服务调用和上下文管理

什么是MCP&#xff1f; MCP&#xff08;Model Context Protocol&#xff09; 是一种专为人工智能模型设计的通信协议&#xff0c;旨在解决复杂 AI 系统中多个模型或组件之间的协同、状态管理和资源优化问题。它尤其适用于大型语言模型&#xff08;LLM&#xff09;、多模态系统及…

【从零开始学习计算机科学】软件测试(六)软件开发中的软件测试过程 与 验收测试

【从零开始学习计算机科学】软件测试(六)软件开发中的软件测试过程 与 验收测试 软件开发中的软件测试过程测试计划阶段测试计划阶段中的任务测试设计阶段测试执行阶段测试总结阶段测试用例相关概念测试用例文档测试用例测试脚本测试报告编写测试用例的依据测试用例的作用测试…

【含文档+PPT+源码】基于微信小程序的健康饮食食谱推荐平台的设计与实现

项目介绍 本课程演示的是一款基于微信小程序的健康饮食食谱推荐平台的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本…

味觉传送器E-Taste:开启虚拟世界的味觉之门

味觉传送器E-Taste&#xff1a;开启虚拟世界的味觉之门 一、发明背景与动机 随着虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;技术的飞速发展&#xff0c;人们在虚拟世界中的沉浸感不断提升&#xff0c;视觉和听觉体验已经取得了显著的突破。然而…

火山引擎(豆包大模型)(抖音平台)之火山方舟的Prompt的使用测试

前言 在大模型的使用过程当中&#xff0c;Prompt的使用非常的关键。原来&#xff0c;我对Prompt的理解不深&#xff0c;觉得Prompt的产生并不是很有必要。但是&#xff0c;自从使用了火山方舟中的“Prompt优解”之后&#xff0c;感受加深了&#xff0c;觉得Prompt是我们和大模型…

Shell 脚本中的 `read` 命令:灵活处理用户输入

在 Shell 脚本中&#xff0c;read 是一个内置命令&#xff0c;用于从标准输入&#xff08;通常是键盘&#xff09;读取用户输入&#xff0c;并将其保存到变量中。read 命令支持多种选项&#xff0c;可以灵活地处理用户输入&#xff0c;使得脚本更加交互式和用户友好。本文将详细…

用户行为路径分析(Google Analytics数据挖掘)

目录 用户行为路径分析(Google Analytics数据挖掘)1. 引言2. 项目背景与意义2.1 用户行为路径的重要性2.2 Google Analytics数据概述2.3 数据规模与挑战3. 数据集生成与介绍4. 数据预处理与GPU加速5. 用户行为路径分析方法5.1 用户行为路径构建5.2 行为路径挖掘与模式分析5.3…