Qt 之 c++/Qml与html交互

项目背景

项目中,遇到在嵌入式下展示html内容,并与之交互,Qt允许使用所谓的混合GUI创建应用程序——在这种GUI中,可以将本机部件与基于html的内容混合在一起。通过WebChannel和WebSockets提供交互,这种混合甚至支持这些本地部分和html端之间的交互。有三种方式:

  1. QWebkit
  2. QWebEngine
  3. QWebView
    QWebkit 在Qt5.6之后已经不再支持,本文只针对QWebEngine和 QWebView。

如何显示HTML内容

使用webEngineView;
使用webView;
使用独立的Web浏览器(不会集成到应用程序中);
这三种方法以不同的方式进行,但都支持QML和HTML之间的通信。

确切的说,WebEngineView以一种方式完成,而WebView(就像网络浏览器一样)以另一种方式完成。WebEngineView和WebView是两码事。

webEngineView
WebEngineView是由Qt自己基于Chromium (Qt WebEngine)的web浏览器引擎提供的web视图。它是一个功能齐全的web浏览器,与Qt捆绑并集成在一起,这很好,但同时这意味着您需要将它与您的应用程序一起拖动,这是一个相当大的东西。

webView
WebView是一个web视图,但不同之处在于它使用平台的本地web浏览器(如果可用的话),因此它不需要将完整的web浏览器堆栈作为应用程序的一部分(WebEngineView就是这种情况),因此您的应用程序更轻量级。另一点是,有些平台根本不允许任何非系统的web浏览器,因此WebView是唯一可用的选项。

webEngineView 和 webView的区别
根据本文,WebEngineView和WebView的关键区别在于Qt如何与这些视图中的html内容通信。由于Chromium IPC功能,WebEngineView提供了最简单的方式-直接通过WebChannel,。而WebView(以及外部web浏览器)要求您首先为WebChannel建立一些传输。

QML/HTML 混合应用程序

本应用用到了 WebChannel 和 WebEngine 两个主要模块,所以要将下面这行添加到 qmake .pro 文件中:

QT += webchannel webengine

QML 服务端
在 QML 服务端,首先导入 Qt WebChannel 模块,以及 Qt WebEngine 模块:

import QtWebChannel 1.0
import QtWebEngine 1.5

然后创建一个想要发布到 HTML/JavaScript 客户端的对象:

QtObject {
    id: myObject

    // 注册方法 1
    // 使用注册方法 2 时不需要此行代码
    WebChannel.id: "foo" //这个 id 可以在 html 中使用
    
    // 以下为 JavaScript 代码可以访问的信号、方法和属性
    signal someSignal(string message);

    function someMethod(message) {
        console.log(message);
        someSignal(message);
        return "foobar";
    }
    property string hello: "world"
}

最后将该对象在 WebView 控件中发布到 HTML 客户端中:

WebEngineView {
    anchors.fill: parent
    url: "file:///C:/test.html"
    
    webChannel: WebChannel {
        id: webChannel

        // 注册方法 1
        registeredObjects: [myObject]
        // 注册方法 2
        //Component.onCompleted: {
        //    // "foo" 是该对象在 JavaScript 端的调用标识
        //    webChannel.registerObject("foo", myObject)
        //}
    }
}

main.qml文件如下

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.4
import QtWebView 1.1
import QtWebChannel 1.14
import QtWebEngine 1.10

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    QtObject {
        id: myObject

        // 注册方法 1
        // 使用注册方法 2 时不需要此行代码
        WebChannel.id: "foo" //这个 id 可以在 html 中使用
        // 以下为 JavaScript 代码可以访问的信号、方法和属性
        signal someSignal(string message);

        function someMethod(message) {
            console.log(message);
            someSignal(message);
            return "foobar";
        }

        property string hello: "world"
    }

    WebEngineView {
        anchors.fill: parent
        url: "qrc:/1.html"
        webChannel: WebChannel {
            id: webChannel

            // 注册方法 1
            registeredObjects: [myObject]
            // 注册方法 2
            //Component.onCompleted: {
            //    // "foo" 是该对象在 JavaScript 端的调用标识
            //    webChannel.registerObject("foo", myObject)
            //}
        }
    }
}

html文件如下

<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
</head>
<script type="text/javascript" src="qrc:/qwebchannel.js"></script>
<script type="text/javascript">
function test(){
    new QWebChannel(qt.webChannelTransport, function(channel) {
        // 所有通过 WebChannel 发布的对象都可以在 channel.objects 中访问的到
        window.foo = channel.objects.foo;

        // 访问一个属性
        alert(foo.hello);

        // Writing a property will instantly update the client side cache.
        // The remote end will be notified about the change asynchronously
        foo.hello = "Hello World!";

        // 连接信号
        foo.someSignal.connect(function(message) {
            alert("Got signal: " + message);
        });

        // 调用方法,并*异步*接收返回值
        foo.someMethod("bar", function(ret) {
            alert("Got return value: " + ret);
        });

        // One can also access enums that are marked with Q_ENUM:
        //console.log(foo.MyEnum.MyEnumerator);
    });
}
    </script>

<body>
    <div><button type="button" onClick="test()>Click Me!</button></div>   
</body>
</html>

HTML / JavaScript 客户端
在客户端,首先,通过 Qt 资源 URL 包含客户端 qwebchannel.js 库(该文件可以在 %QtDir%\Src\qtwebchannel\examples\webchannel\shared\qwebchannel.js 中找到,经过测试在 Qt 5.12 以后不将该文件加入资源也可),并在 HTML 文件中插入一段 JavaScript:

<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>

然后,在 JavaScript 代码中实例化 QWebChannel 对象并设置回调函数。当 web 通道的初始化成功时,将调用回调函数。此外,您可以传递 qt.webChannelTransport 对象到 channel 中,详见下文:

new QWebChannel(qt.webChannelTransport, function(channel) {
// 所有通过 WebChannel 发布的对象都可以在 channel.objects 中访问的到
window.foo = channel.objects.foo;

// 访问一个属性
alert(foo.hello);

// Writing a property will instantly update the client side cache.
// The remote end will be notified about the change asynchronously
foo.hello = "Hello World!";

// 连接信号
foo.someSignal.connect(function(message) {
    alert("Got signal: " + message);
});

// 调用方法,并*异步*接收返回值
foo.someMethod("bar", function(ret) {
    alert("Got return value: " + ret);
});

// One can also access enums that are marked with Q_ENUM:
//console.log(foo.MyEnum.MyEnumerator);

});

C++/QML/HTML 混合应用程序
利用在 QML 应用程序中可以引用 C++ 类的这个特点,我们也可以实现在 HTML 页面中调用 C++ 类的需求。

首先定义 C++ 对象并将它注册到 QML 中,我们在 main.cpp 中添加下面的一行,注意, TestObejct 类必须直接继承自 Object:

qmlRegisterType<TestObejct>("TestObejct", 1, 0, "TestObejct");

然后在 QML 文件中将这个类对象注册到 WebChannel,你可以使用上节中提到的方法直接调用 C++ 类中已存在的信号、方法、属性和枚举类型,也可以在 QML 中继续扩展其他方法:

import TestObejct 1.0

TestObejct {
id: myObject
WebChannel.id: “foo”

// 在 QML中可以继续扩展信号、方法和属性
signal someSignal2(string message);

function someMethod2(message) {
    console.log(message);
    someSignal2(message);
    return "foobar";
}

property string hello2: "world"

}
Qt WebChannel 不只可以在 QML 应用程序中使用,在纯 Qt/C++ 应用程序中也可以创建一个 QWebChannel 并发布 QObject 实例

项目需求(html页面定位)

项目用到了html页面定位功能,即点击一个导航按钮,使页面直接跳转到该位置(滚动条直接滚到到位置)
有两种方式实现:
1 借用a标签的href定位,瞄点

<div id="content">
  <div class="btn-container">
    <a class="btn" href="#anchor1">锚点1</a>
    <a class="btn" href="#anchor2">锚点2</a>
    <a class="btn" href="#anchor3">锚点3</a>
  </div>
  <div id="anchor1" class="anchor-con">anchor1</div>
  <div id="anchor2" class="anchor-con">anchor2</div>
  <div id="anchor3" class="anchor-con">anchor3</div>
</div>

2 简单的window.scrollTo方法使用

该方法需要在html文件中加载jquery.min.js:

<script type=\"text/javascript\" src=\"qrc:/jquery.min.js\"></script>

html 中js方法

function textClicked(){
       $(window).scrollTop($(\"#pos0\").offset().top)}

html 标签

<div id=\"pos1\"><span class=\"Title\">考点解析</span></div>

单击导肮按钮 “文字解析”即可实现定位

<button id=\"textPos\" class=\"topnavi\" onClick=\"textClicked()\">文字解析</button>

遇到的问题:

1 qwebchannel.js 版本不一样 ,项目中刚开始始终在html文件中调用QtObject方法不可行,后来发现qwebchannel.js文件不对
2 html中显示中文问题,webengine找不到中文字库,因为在嵌入式下,需要自己手动编译
3 Uncaught ReferenceError: $ is not defined报错;$未定义是为什么呢?
原因一:你未引用jquery库jquery.min.js文件,或者说路径错误;

扩展 :Chrome打开PDF报错:Not allowed to navigate top frame to data URL

Chrome打开PDF报错:Not allowed to navigate top frame to data URL

var pdf = "data:application/pdf;base64," + data;
window.open(pdf);

在Chrome使用window.open()打开pdf报错:

Not allowed to navigate top frame to data URL: data:application/pdf;base64,JVBERi0xLjMKMyAwIG9iago8PC9UeXBlIC9QYWdlCi9QYXJlbnQgMSAwIFIKL1…
原因分析
Chrome 60 开始禁止页面使用data:url的方式跳转导航,禁止data:url导航的使用包括:

<a href="data:xxxxx"> 点击跳转
window.open(“data:xxx”)
window.location="data:xxx"

对于使用data:url直接来加载数据的场景不会禁止,如

直接加载图片

解决方案
Chrome并没有禁止直接使用data:url的方式加载数据,如iframe,所以可以把数据放到iframe的属性src里。

var win = window.open();
var pdf = "data:application/pdf;base64," + data;
win.document.write('&lt;iframe src="' + pdf + '" frameborder="0" style="border:0; top:0px; left:0px; bottom:0px; right:0px; width:100%; height:100%;" allowfullscreen&gt;&lt;/iframe&gt;')

扩展:JS控制div跳转到指定的位置的解决方案总结

1.给链接a加个#的方式来实现跳转

html页面:

<div id="container">
       <a href="#div1">div1</a>
       <a href="#div2">div2</a>
       <a href="#div3">div3</a>
</div>
   <div id="div1">div1</div>
   <div id="div2">div2</div>
   <div id="div3">div3</div>

css样式:

div {
           height: 800px;
           width: 400px;
           border: 2px solid black;
       }
  #container{
           position: fixed;
           margin:50px  500px;
 }

该锚点法,不需要任何的js代码,即可实现跳转的方法。缺点:点击链接url发生变化,刷新的话会有问题。此方法貌似只能在.html后缀的页面才能起作用,对于.cshtml页面不起作用。
  
2.用animate属性,当点击锚点后,页面滚动到相应的DIV
接着上面的代码,具体添加如下代码:

html页面:

<div id="container">
          <p id="p1">div1</p>
          <p id="p2">div2</p>
          <p id="p3">div3</p>
</div>
   <div id="div1">div1</div>
   <div id="div2">div2</div>
   <div id="div3">div3</div>

css样式页面同上,看下js代码如下:

<script type="text/javascript" src="http://code.jquery.com/jquery-1.8.0.min.js"></script>
$(document).ready(function() {
    $("#p1").click(function() {
        $("html, body").animate({
            scrollTop: $("#div1").offset().top }, {duration: 500,easing: "swing"});
        return false;
    });
    $("#p2").click(function() {
        $("html, body").animate({
            scrollTop: $("#div2").offset().top }, {duration: 500,easing: "swing"});
        return false;
    });
    $("#p3").click(function() {
        $("html, body").animate({
            scrollTop: $("#div3").offset().top }, {duration: 500,easing: "swing"});
        return false;
    });
});

3.简单的window.scrollTo方法使用
即滚动到坐标为(100,500)的地方。比较单一,且没有缓慢的效果

function scrollWindow(){
    window.scrollTo(100,500);
}

4.用js的srollIntoView方法进行使用。这里贴下代码:

html页面:

<ul>
        <li><a href="javascript:;" data-tab="eat">吃饭</a></li>
        <li><a href="javascript:;" data-tab="sleep">睡觉</a></li>
        <li><a href="javascript:;" data-tab="walk">逛街</a></li>
 </ul>
<div >
    <div data-tab="eat" style="background:cyan; height:500px;">
        吃饭
    </div>
    <div data-tab="sleep" style="background:lightgreen;height:2000px;">
        睡觉
    </div>
    <div data-tab="walk" style="background:LightSalmon;height:1000px;">
        逛街
    </div>
</div>

js代码如下:

[].slice.call(document.querySelectorAll('a')).forEach(function(el){
           el.addEventListener('click', function(){
               var target = document.querySelector('div[data-tab=' + this.getAttribute('data-tab')+ ']' )
               target.scrollIntoView(true);
           })
 })

注意一个问题,object.scrollIntoView(bool),前面是对象,通常原生的获取对象都是下面的写法:

document.getElementById('#xxx' ).scrollIntoView(true);

那么如果用jquery来调用该方法的话,需要写成下面这样的:

$('#xxx')[0].scrollIntoView(true);
  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值