有关Qt的WebEngine模块与JS交互的研究(二)



  • 0_1524459226096_131313.gif
    大家上午好哦,上一篇文章简单介绍了我对WebEngine和JS交互相关的理解,这篇文章则是介绍了如何实验如何制作一个小例子,实现WebEngine和JS的交互。

    这个例子的截图是这样的:
    0_1524455742527_eb728e46-d777-4d39-b620-52a5d7e6c991-image.png

    我们看一下文件的结构:

    webengine_2.pro
    main.cpp
    html/libs/bootstrap.min.css
    html/index.html
    qml/main.qml
    html.qrc
    qml.qrc

    文件的结构还是非常简单的。由于我们对按钮实行了美化,因此需要依赖bootstrap.min.css文件,除此之外,就是普通的html文件了。这里需要关注的核心就是index.html文件和main.qml文件了。因为我们的通信主要是借助Chromium和Qt的C++代码,在QML环境和html环境中做通信。

    首先是index.html文件:

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>bootsrap按钮</title>
        <link rel="stylesheet" href="libs/bootstrap.min.css">
        <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
    </head>
    <body class="vert-center-parent">
        <div class="container text-center vert-center">
            <button id="deleteButton" class="btn btn-danger">删除所有文件</button>
        </div>
    </body>
    <style>
    .vert-center
    {
        line-height: 600px;
    }
    .vert-center-parent
    {
        height: 600px;
    }
    </style>
    <script type="text/javascript">
    window.onload = function ( )
    {
        new QWebChannel( qt.webChannelTransport, function( channel ) {
            //makedialogobjectaccessibleglobally
            var fileField = channel.objects.fileField;
            var deleteButton = document.getElementById( "deleteButton" );
            deleteButton.onclick = function( ) {
                alert( "删除了" + fileField.text + "文件。" );
            }
        } );
    }
    </script>
    </html>
    

    因为我们需要使用WebChannel来找到QML环境中的变量,因此需要通过

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

    的方式引入Qt自己搭建的JS方法。如果想在纯Chrome或者是其它浏览器上运行这个网页,那么src属性不能是以qrc为前缀了,需要将Qt这个文件复制到合适的位置才行。



  • 在页面加载的过程中,通过设置window.onload回调函数来执行我们自己的逻辑。其内容主要是创建QWebChannel对象,并且在回调函数中拿到QML对象的Id,这样就像QML环境中使用该对象了。我为了演示的便利,使用alert方法删除一个文件。文件的内容包含的是fileField的属性text,这和QML环境中访问同名属性一致。
    还有一点值得注意的是,因为我们确信网页是执行在WebEngineView中的,因此我们在建立QWebChannel的时候使用Qt在WebEngine中默认的传输管道qt.webChannelTransport就行了,内部看文档介绍是使用进程间通信(IPC)的方法。如果是在纯浏览器中建立QWebChannel,那么就需要使用WebSocket来达到目的了,查一查目标浏览器是否支持WebSocket

    接下来介绍的就是main.qml文件。

    import QtQuick 2.10
    import QtQuick.Window 2.10
    import QtQuick.Controls 2.3
    import QtQuick.Layouts 1.3
    import QtWebEngine 1.5
    import QtWebChannel 1.0
    
    Window
    {
        id: window
        width: 640
        height: 480
        visible: true
        title: "WebEngine的例子 2"
    
        RowLayout
        {
            anchors.fill: parent
            ColumnLayout
            {
                Layout.fillHeight: true
                Button
                {
                    text: "进入一个地址"
                    onClicked:
                    {
                        webEngineView.url = urlField.text;
                    }
                }
                TextField
                {
                    id: urlField
                    selectByMouse: true
                    text: "qrc:///html/index.html"
                }
                Label
                {
                    text: "服务器中的文件"
                }
                TextField
                {
                    id: fileField
                    WebChannel.id: "fileField"
                    text: "HelloWorld.txt"
                    selectByMouse: true
                }
            }
            WebEngineView
            {
                id: webEngineView
                Layout.fillWidth: true
                Layout.fillHeight: true
                url: "qrc:///html/index.html"
                webChannel: WebChannel
                {
                    registeredObjects: [ fileField ]
                }
            }
        }
    }
    

    Window代码块前面是一些控件,不必多言。注意的是id名为fileField的控件。这个控件有一个附加属性:WebChannel.id。这个id就是我们需要从html中拿到的id或者说属性,不这么设定的话,我们是无法在html中找到这个对象的。
    最后则是WebEngineView,它的后端使用的是WebEngine,并且指定了webChannel,它有一个属性registeredObjects,指向的是已经注册过的对象。这里的对象就包含了fileField,也只有注册过的对象才能暴露在html环境中。



  • 其余的文件则是初始化必要的文件。也不必多言,除了main.cpp,它里面要使用QtWebEngine::initialize( )初始化一下。main.cpp的内容是:

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQuickWindow>
    #include <QtWebEngine>
    
    int main( int argc, char *argv[] )
    {
        QGuiApplication a( argc, argv );
    
        QtWebEngine::initialize( );
    
        // 设置字体渲染方式
        QQuickWindow::setTextRenderType( QQuickWindow::NativeTextRendering );
    
        QQmlApplicationEngine engine;
        engine.load( QUrl( "qrc:///qml/main.qml" ) );
    
        return a.exec();
    }
    

    很简单吧。程序运行的时候,我们点击“删除所有文件”按钮,就会弹出一个对话框,里面显示的是我们在TextField中设定的文字。运行符合预期,说明我们的尝试成功了。
    0_1524459184313_a783721a-2141-4627-b374-b969da346699-image.png



  • 今天没有时间弄了,明天试试



  • @大黄老鼠 嗯,也是非常简单的一个例子啦。



  • @jiangcaiyang 博主您好 谢谢您的贡献,按照您的代码可以运行,但是如果 我想调用qml 页面中的一个函数 function 该怎么办呢,谢谢博主



  • @倦鸟归巢 WebChannel可以让双端解耦。这样的话,通过QML调用Html的JS函数以及JS函数调用QML的函数都是可行的。


Log in to reply
 

走马观花

最近的回复

  • C

    Qt for MCU需要商业授权的

    read more
  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more

关注我们

微博
QQ群