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

news2025/3/15 9:14:39

查找选择

在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.coloradmin.cn/o/2315357.html

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

相关文章

VUE的脚手架搭建引入类库

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

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…

Matlab 双线性插值(二维)

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

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

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

程序编译生成的文件

目录 .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…

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硬件的完整工程案例…

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

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

STM32 RS232通信开发全解析 | 零基础入门STM32第五十九步

主题内容教学目的/扩展视频RS232串口电路原理&#xff0c;跳线设置&#xff0c;驱动程序。与超级终端通信。了解电路原理和RS232协议。 师从洋桃电子&#xff0c;杜洋老师 &#x1f4d1;文章目录 一、RS232通信系统架构二、RS232核心原理与硬件设计2.1 电气特性对比2.2 典型电路…

C# net deepseek RAG AI开发 全流程 介绍

deepseek本地部署教程及net开发对接 步骤详解&#xff1a;安装教程及net开发对接全流程介绍 DeepSeekRAG 中的 RAG&#xff0c;全称是 Retrieval-Augmented Generation&#xff08;检索增强生成&#xff09;&#xff0c;是一种结合外部知识库检索与大模型生成能力的技术架构。其…

建筑管理(2): 施工承包模式,工程监理,质量监督

文章目录 一. 施工承包模式1. 施工总承包模式1.1 施工总承包的特点1.2 施工总承包模式中的承包方 2. 平行承包模式3. 联合体与合作体承包模式 二. 工程监理1. 强制实行监理的工程范围1.1 国家重点建设工程1.2 大中型公用事业工程(重点)1.3 成片开发建设的住宅小区工程1.4 必须实…

最节省服务器,手搓电子证书查询系统

用户预算150元&#xff0c;想要一个最简单证书查询系统。前台能查询证书、后台管理员能登录能修改密码&#xff0c;证书能够手动输入修改删除、批量导入导出删除数据、查询搜索。能够兼容苹果、安卓、PC三端浏览器&#xff0c;最后帮忙部署到云服务器上。 用户预算不多&#xf…

STM32F407 IIC通信

1、IIC 介绍 IIC(Inter-Integrated Circuit)总线是一种由数据线 SDA 和时钟线 SCL 构成的两线式串行总线,可发送和接收数据,常用于 MPU/MCU 与外部设备连接通信、数据传输。每个连接到总线的设备都有一个独立的地址,主机可以通过该地址来访问不同设备。因为 IIC 协议比较简单…

jupyter无法转换为PDF,HTMLnbconvert failed: Pandoc wasn‘t found.

无法转为PDF 手动下载工具 https://github.com/jgm/pandoc/releases/tag/3.6.3 似乎跟我想的不大一样&#xff0c;还有新的报错 https://nbconvert.readthedocs.io/en/latest/install.html#installing-tex 不知道下的啥玩意儿 sudo apt-get install texlive-xetex texlive-fon…

使用 Excel 实现绩效看板的自动化

引言 在日常工作中&#xff0c;团队的绩效监控和管理是确保项目顺利进行的重要环节。然而&#xff0c;面临着以下问题&#xff1a; ​数据分散&#xff1a;系统中的数据难以汇总&#xff0c;缺乏一个宏观的团队执行情况视图。​看板缺失&#xff1a;系统本身可能无法提供合适…