范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Qt开发操控Web小车案例

  前言
  这次讨论Qt与Web混合开发相关技术。
  这类技术存在适用场景,例如:Qt项目使用Web大量现成的组件/方案做功能扩展,
  Qt项目中性能无关/频繁更新迭代的页面用html单独实现,Qt项目提供Web形式的SDK给
  用户做二次开发等等,或者是Web开发人员齐全而Qt/C++人手不足,此类非技术问题,
  都可以使用Qt + Web混合开发。
  (不适用的请忽略本文) 简介
  上次的文章《Qt与Web混合开发》,讨论了Qt与Web混合开发相关技术。
  这次通过一个web控制小车的案例,继续讨论相关技术。
  本文会先介绍Qt与Web嵌套使用,再介绍Qt与Web分开使用,之后着重讨论分开使用
  的一些实现细节,特别是WebChannel通信、WebChannel在Web/typescript中的使用。 Qt与Web嵌套MiniBrowser
  这里以Qt官方的例子MiniBrowser来说明吧。
  打开方式如下:
  运行效果如下:
  这个例子是在Qml中嵌套了WebView。 半透明测试
  涛哥做了一个简单的半透明测试。
  增加了两个半透明的小方块,蓝色的在WebView上面,红色的在WebView下面。
  运行效果也是正确的:
  代码是这样的:
  红色框中是我增加的代码。
  为什么要做半透明测试呢?根据以往的经验,不同渲染方式的两种窗口/组件嵌套在一起,总会出现透明失效之类的问题,例如 qml与Widget嵌套。 渲染原理
  涛哥翻了一下Qt源码,了解到渲染的实现方式,Windows平台大致如下:
  chromium在单独的进程处理html渲染,并将渲染结果存储在共享内存中;主窗口在需要重绘的时候,从共享内存中获取内容并渲染。 小结
  这里的WebView内部封装好了WebEngine,其本身也是一个Item,就和普通的Qml一样,属性绑定、js function都可以正常使用,暂时不深入讨论了。 Qt与Web分离
  Qt与Web分离,就是字面意思,Web在单独的浏览器或者App中运行,不和Qt堆在一起。两者通过socket进行通信。
  这里用我自己做的例子来说明吧。
  先看看效果:
  左边是Qt实现的一个简易小车,可以前进和转向。右边是Html5实现的控制端,控制左边的小车。
  源码在github上: https://github.com/jaredtao/QtWeb Qt小车原版小车
  小车来自Qt的D-Bus Remote Controller 例子
  原版的例子,实现了通过QDBus 跨进程 控制小车。
  (吐槽:这是一个古老的例子,使用了GraphicsView 和QDBus)
  (知识拓展1: DBus是unix系统特有的一种进程间通信机制,使用有些复杂。Qt对DBus机制进行了封装/简化,即QDBus模块,
  通过xml文件的配置后,把DBus的使用转换成了信号-槽的形式。类似于现在的Qt Remote Objects)
  (知识拓展2: Windows本身不支持DBus,网上有socket模拟DBus的方案。参考: https://www.freedesktop.org/wiki/Software/dbus/) 改进小车
  我做了一些修改,主要如下: 去掉了DBus 增加控制按钮 增加WebChannel 修改Car的实现,导出一些属性和函数。 注册Car到WebChannel
  这里贴一些关键代码
  Car的头文件:
  其中要说明的是:
  speed和angle属性具备 读、写、change信号。
  还有加速、减速、左转、右转四个公开的槽函数。 必要的知识WebSocket和 QWebSocket
  WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
  WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
  Qt为我们封装好了WebSocket,即QWebSocket和QWebSocketServer,简单易用。
  如果你了解socket编程,就看作TCP好了;如果不了解,请先去补充一下知识吧。 WebChannel
  按涛哥的理解,WebChannel是在socket上建立的一种通信协议,这个协议的作用是把QObject暴露给远端的HTML。
  大致使用流程: Qt程序中,要暴露的QObject全部注册到WebChannel。 Qt程序中,启动一个WebSocketServer,等待Html的连接。 Html加载好qwebchannel.js文件, 然后去连接WebSocket。 连接建立以后,Qt程序中,由WebChannel接手这个WebSocket,按协议将QObject的各种"元数据"传输给远端Html。 Html端,qwebchannel.js处理WebSocket收到的各种"元数据",用js的Object 动态创建出对应的QObject。 到这里两边算是做好了准备,可以互相调用了。 Qt端QObject数据变化只要发出信号,就会由WebChannel自动通知Web端; Web端可以主动调用QObject的public的 invok函数、槽函数,以及读、写属性。 Qt启动系统浏览器
  在使用WebChannel的时候,Qt端建立了WebSocketServer,之后要把server的路径(例如:ws://127.0.0.1:12345)告诉Html。
  一般就是在打开Html的时候带上Query参数,例如: F:QtWebindex.html?webChannelBaseUrl=ws://127.0.0.1:12345 Qt的OpenUrl
  Qml中有 Qt.openUrlExternally, C++ 中有 QDesktopServices::openUrl,本质一样, 都可以打开一个本地的html网页。
  其在Windows平台的底层实现是Win32 API。这里有个Win32 API的缺陷,传Query参数会被丢掉。 C# .net的 Process::Start
  涛哥找到了替代的方案:
  .net framework / .net core有个启动进程的函数: System.Diagnostics.Process::Start, 可以调用浏览器并传query参数 //C# 启动chrome System.Diagnostics.Process.Start("chrome", "F:QtWebindex.html?webChannelBaseUrl=ws://127.0.0.1:12345"); //C# 启动firefox System.Diagnostics.Process.Start("firefox", "F:QtWebindex.html?webChannelBaseUrl=ws://127.0.0.1:12345"); //C# 启动IE System.Diagnostics.Process.Start("IExplore", "F:QtWebindex.html?webChannelBaseUrl=ws://127.0.0.1:12345");
  Qt中直接写C#当然不太好,不过呢,Win7/Win10 系统都带有Powershell,而powershell依赖于.net framework, 我们可以调用powershell来间接使用.net framework。
  所以有了下面的代码: ... QString psCmd = QString("powershell -noprofile -command "[void][System.Diagnostics.Process]::Start("%1", "%2")"").arg(browser).arg(url.toString()); bool ok = QProcess::startDetached(psCmd); qWarning() << psCmd; if (!ok) {     qWarning() << "failed"; } ...
  结果完美运行。 Web控制端目录结构
  Web端就按照Web常规流程开发。
  Web部分的源码也在前文提到的github仓库,子路径是QtWebWebChannelCarWeb
  如下是Web部分的目录结构:
  脚本用typescript,包管理用npm,打包用webpack,编辑器用vs code, 都中规中矩。
  内容比较简单,暂时不需要前端框架,手(复)写(制)的html和css。 Html
  html部分比较简单 //index.html                                                                                                                                                    
  样式和布局全靠css,这里就不贴了。 TypeScript
  脚本部分需要细说了。
  src文件夹为全部脚本,目录结构如下:
  TypeScript中的QObject
  从main开始, 加点注释: //main.ts import WebChannelCore from "./webchannelCore"; //window加载时回调,入口 window.onload = () => {     //初始化WebChannel,传参为两个回调,分别对应WebChannel建立连接和连接断开。     WebChannelCore.initialize(onInit, onUninit); } //WebChannel建立连接的处理 function onInit() {     //换图标     (window as any).document.getElementById("img").src = "../img/connected.svg";     //获取QObject对象     let car = WebChannelCore.SDK.car;          //取dom树上的组件      let upBtn = (window as any).document.getElementById("up");     let downBtn = (window as any).document.getElementById("down");     let leftBtn = (window as any).document.getElementById("left");     let rightBtn = (window as any).document.getElementById("right");      let speedLabel = (window as any).document.getElementById("speed");     let angleLabel = (window as any).document.getElementById("angle");     //绑定按钮点击事件     upBtn.onclick = () => {         //调用QObject的接口         car.accelerate();     }     downBtn.onclick = () => {         car.decelerate();     }     leftBtn.onclick = () => {         car.turnLeft();     }     rightBtn.onclick = () => {         car.turnRight();     }     //QObject的信号连接到js 回调     car.speedChanged.connect(onSpeedChanged);     car.angleChanged.connect(onAngleChanged); } //WebChannel断开连接的处理 function onUninit() {     //换图标     (window as any).document.getElementById("img").src = "//img03.bs178.com/cd/rh/53a6a1ee10b02bcb.jpg"; } //异步更新 speed async function onSpeedChanged() {     let speedLabel = (window as any).document.getElementById("speed");     let car = WebChannelCore.SDK.car;     //获取speed,异步等待。     //注意这里改造过qwebchannel.js,才能使用await。     speedLabel.textContent = await car.getSpeed(); } //异步更新 angle async function onAngleChanged() {     let angleLabel = (window as any).document.getElementById("angle");     let car = WebChannelCore.SDK.car;     //获取angle,异步等待。     //注意这里改造过qwebchannel.js,才能使用await。     angleLabel.textContent = await car.getAngle(); }
  可以看到我们从WebChannelCore.SDK 中获取了一个car对象,之后就当作QObject来用了,包括调用它的函数、连接change信号、访问属性等。
  这一切都得益于WebSocket/WebChannel. TypeScript中连接websocket
  接下来看一下WebChannelCore的实现 //WebChannelCore.ts import { QWebChannel } from "./qwebchannel";  type callback = () => void; export default class WebChannelCore {     public static SDK: any = undefined;     private static connectedCb: callback;     private static disconnectedCb: callback;     private static socket: WebSocket;          //初始化函数     public static initialize(connectedCb: callback = () => { }, disconnectedCb: callback = () => { }) {         if (WebChannelCore.SDK != undefined) {             return;         }         //保存两个回调         WebChannelCore.connectedCb = connectedCb;         WebChannelCore.disconnectedCb = disconnectedCb;          try {             //调用link,并传入两个回调参数             WebChannelCore.link(                 (socket) => {                   //socket连接成功时,创建QWebChannel                     QWebChannel(socket, (channel: any) => {                         WebChannelCore.SDK = channel.objects;                         WebChannelCore.connectedCb();                     });                 }                 , (error) => {                   //socket出错                     console.log("socket error", error);                     WebChannelCore.disconnectedCb();                 });         } catch (error) {             console.log("socket exception:", error);             WebChannelCore.disconnectedCb();             WebChannelCore.SDK = undefined;         }     }      private static link(resolve: (socket: WebSocket) => void, reject: (error: Event | CloseEvent) => void) {         //获取Query参数中的websocket地址         let baseUrl = "ws://localhost:12345";         if (window.location.search != "") {             baseUrl = (/[?&]webChannelBaseUrl=([A-Za-z0-9-:/.]+)/.exec(window.location.search)![1]);         }         console.log("Connectiong to WebSocket server at: ", baseUrl);                  //创建WebSocket         let socket = new WebSocket(baseUrl);         WebChannelCore.socket = socket;         //WebSocket的事件处理         socket.onopen = () => {             resolve(socket);         };         socket.onerror = (error) => {             reject(error);         };         socket.onclose = (error) => {             reject(error);         };     } } (window as any).SDK = WebChannelCore.SDK;
  这部分代码不复杂,主要是连接WebSocket,连接好之后创建一个QWebChannel。 TypeScript中的QWebChannel
  观察仔细的同学会发现,src文件夹下面,没有叫‘qwebchannel.ts’的文件,而是‘qwebchannel.js’,和一个‘qwebchannel.d.ts’
  这涉及到另一个话题: TypeScript中使用javaScript
  ‘qwebchannel.js’是Qt官方提供的,在js中用足够了。
  而我们这里是用TypeScript,按照TypeScript的规则,直接引入js是不行的,需要一个声明文件 xxx.d.ts
  所以我们增加了一个qwebchannel.d.ts文件。
  (熟悉C/C++的同学,可以把d.ts看作typescript的头文件)
  内容如下: //qwebchannel.d.ts export declare function QWebChannel(transport: any, initCallback: Function): void;
  只是导出了一个函数。
  这个函数的实现在‘qwebchannel.js’中: //qwebchannel.js "use strict";  var QWebChannelMessageTypes = {     signal: 1,     propertyUpdate: 2,     init: 3,     idle: 4,     debug: 5,     invokeMethod: 6,     connectToSignal: 7,     disconnectFromSignal: 8,     setProperty: 9,     response: 10, };  var QWebChannel = function(transport, initCallback) {     if (typeof transport !== "object" || typeof transport.send !== "function") {         console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +                       " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));         return;     }     ... } function QObject(name, data, webChannel) {   ... }
  这个代码比较长,就不全部贴出来了。主要实现了两个类,QWebChannel和QObject。
  QWebChannel就是用来接管websocket的,而QObject是用js Object模拟的 Qt的 QObject。
  这一块不细说了,感兴趣的同学可以自己去研究源码。 改进qwebchannel.js以支持await
  Qt默认的qwebchannel.js在实际使用过程中,有些不好的地方,就是函数的返回值不是直接返回,而是要在回调函数中获取。
  比如car.getAngle要这样用: let angle = 0; car.getAngle((value:number)=> {   angle = value; });
  我们的实际项目中,有大量带返回值的api,这样的用法每次都嵌套一个回调函数,很不友好,容易造成回调地狱。
  我们同事的解决方案是,在typescript中把这些api再用Promise封装一层,外面用await调用。
  例如这样封装一层: function getAngle () {     return new Promise((resolve)=>{         car.getAngle((value:number)=> {             resolve(value);         });   }); }
  使用和前面的代码一样: //异步更新 angle async function onAngleChanged() {     let angleLabel = (window as any).document.getElementById("angle");     let car = WebChannelCore.SDK.car;     //获取angle,异步等待。     //注意这里改造过qwebchannel.js,才能使用await。     angleLabel.textContent = await car.getAngle(); }
  这种解决方案规避了回调地狱,但是工作量增加了。
  涛哥思考良久,稍微改造一下qwebchannel.js,自动把Promise加进去,也不需要再额外封装了。 QObject to Typescript
  我们在Qt 程序中写了QObject,然后暴露给了ts。
  在ts这边,一般也需要提供一个声明文件,明确有哪些api可用。
  例如我们的car声明: //CarObject.ts declare class Car {     get speed():number;     set speed(value:number);      get angle():number;     set angle(vlaue:number);      public accelerate():void;     public decelerate():void;     public turnLeft():void;     public turnRight():void; }
  这里涛哥写了一个小工具,能够解析Qt中的QObject,并生成对应的ts文件。
  【领QT开发教程 学习资料,点击下方链接莬费领取  ,先码住不迷路~】
  点击这里:「链接」

有些萎缩性胃炎患者,喉咙有异物感,这是什么原因造成的呢?大家好,我是中医黄医生,我想和大家聊一下萎缩性胃炎引起的咽喉异物感这个问题。作为脾胃科20多年的主任医师,我发现许多患者在得了胃病之后,虽然胃并没有明显的不适症状,但却常常感到咽部晨不吐口水,午不泄精水,晚不流汗水什么意思?中老年人需要重视王大爷是一位居住在小镇上的退休老人,为了避免感冒受凉喜欢晚上睡觉时穿得很厚,总是会在睡觉时出汗。他并没有在意。之后王大爷发现自己的皮肤经常瘙痒和发炎,来到医院看诊,医生仔细询问了他维生素C是酸味的吗?水果越酸,维C含量越高?柠檬,山楂,猕猴桃是口感偏酸的水果,很多人把柠檬切片泡水喝,因此有人认为柠檬片泡水可以补充维生素C。维生素C是酸味的吗?并不是!水果里的酸味并不是维生素C,而是有机酸。而且水果是不老年健康科普春季不想肝长霉,这套八段锦不得不练春季是万物生发的季节,是阳气逐渐萌发的季节,也是四季之首,春季养生乃四季养生之根本,正所谓一年之计在于春。素问四气调神大论篇中曰春三月,此谓发陈,天地俱生,万物以荣,夜卧早起,广步咱们身上有个出气筒敲敲打打助睡眠咱们身上有个出气筒敲敲打打助睡眠今天我就教大家如何利用身上的穴位来改善失眠,在说之前建议大家点赞收藏,防止下次想看的时候找不到了。这个穴位,就叫关冲穴,在我们手上无名指末节尺侧,距清明碰上闰二月,提醒爱喝酒的人,这3点禁忌要搞懂清明碰上闰二月,提醒爱喝酒的人,这3点禁忌要搞懂!都说清明时节雨纷纷,大家那里最近下雨了吗?大家都知道今年是比较特殊的一年,不仅是外部环境发生了很大变化,还有一定的内因,今年是20你的眼睛为何涣散无神?原创二哥2014a收录于合集养生家话养生115个精气五藏六府之精气,皆上于目而为睛,骨之精瞳子,筋之精为黑眼,血之精为络,故从人的眼神可以观察到他的健康状态。肾生髓,髓生肝倘若思虑这四位英雄队友不让拿,也害怕对面拿,大家到底顾虑什么?对于一些版本弱势英雄而言,技术难以弥补数据上的弱势,使用版本弱势英雄,只能输多赢少。对于那些技能机制较为特别的英雄而言,确实是没有特别难用的英雄,有些英雄你不希望队友玩,但对面拿出LOL英雄联盟手游终极魔典大招搭配,我变大了,也变强了!了解LOL英雄联盟手游新咨询,关注LOL萌闪闪。大家好,我是闪闪。随着4月的到来,英雄联盟手游也是迎来了各种内容更新。就最近上线的全新终极魔典模式是备受各位召唤师们的喜爱。各种英雄速来花溪,感受越野和激情!2023贵阳花溪高坡山地英雄会即将震撼来袭如果你是这样的玩家不关心开得快不快,自由自在才是正确姿态偏不爱在赛道扎堆,山川河谷就是高速公路那就来孟关汽车越野玩家基地参与2023贵阳花溪高坡山地英雄会系列活动一起跋山涉水,感受英雄联盟格斗游戏ProjectL将登陆PS系主机传言称,拳头公司基于英雄联盟开发的格斗游戏项目ProjectL将会登陆PlayStation平台。格斗游戏职业选手GamerBeeTW受邀参加了拳头公司在日本举办的ProjectL
世锦赛朱婷缺席,李盈莹联手张常宁有悬念,这份14人名单是否可行世联赛结束之后,中国女排的重心就转移到了世锦赛当中来,不过今年的世锦赛朱婷可以确定不参赛了,那么无朱婷的情况下14人名单怎么选,如果您来选择的话,最期待谁能入围?没有朱婷张常宁,李5G版回归!华为智选HiNova10通过认证,骁龙778G芯片加持华为Nova10系列于7月4日发布,流量明星易烊千玺代言,在前置拍照领域再次取得突破,可惜只有4G版本略显遗憾。现在中邮通信旗下两款机型入网,隶属于华为智选HiNova10系列,核蓝牙耳机什么牌子的好用?测评室分享2022年蓝牙耳机排名又到了每周发布测评的时候了,本期我们将聊聊最火的话题蓝牙耳机什么牌子的好用?,我们这几年陆陆续续测评过的蓝牙耳机有几十款,几乎每个品牌都有深入了解。这次我们耗费了一个星期时间整理资鸿蒙3。0真的来了,被曝史诗级增强,让旧华为手机焕发新生距离上一次华为发布会不到一个月时间,新的发布会已经到来了,但是这一次华为带来的绝对是狠货因为鸿蒙3。0终于要来了!虽然早在几个月前,鸿蒙系统就已经成了数码粉茶余饭后的谈资,但这次官苹果首次开卖iPhone12mini官翻机目前,官网在售的iPhone12mini的官翻版仅有一款,它拥有黑色外观和128GB存储空间,售价579美元(约合人民币3908。25元)。作为对比,苹果官网目前在售的全新iPho重现翻盖手机辉煌!诺基亚2660Flip预售开启,真经典现在主流手机厂商中已经很少看到诺基亚的影子了,但作为老牌手机厂商诺基亚从未退出过手机市场。尤其在功能机市场,诺基亚依旧在不断进行新品迭代。在不久前举办的新品发布会上就一口气推出了三主流高性价比旗舰手机推荐这四款买完不后悔随着手游画面越来越精致,对于手机配置的要求也是越来越高。而在这种情况下如果想在一款游戏中拥有最极致的体验只看配置是不够的,还必须考虑到其它配置带来的优质体验。小编选择了四款旗舰级高凯迪拉克纯电概念轿车发布,车内配5块大屏,外观未来感十足近日,凯迪拉克正式发布了旗下全新纯电概念轿车Celestiq的官图,从图片上来看,纯电概念轿车Celestiq的外观极具未来感,车内用样也采用的是科技的设计风格,配备了5块液晶大屏LOLMole佐伊团战完美PokeIG摧枯拉朽2比1力克UP北京时间7月29日,LPL夏季赛继续进行,在昨天共迎来2场比赛,第一场比赛由UP战队对战IG战队,赛前UP是一个3胜8负的战绩,而IG是2胜9负的战绩,本场比赛双方鏖战三局,最终在川河盖旅游区邀请全体现役退役军人免费坐索道秀山网讯为庆祝中国人民解放军建军95周年,进一步密切军民关系,在第95个建军节来临之际,川河盖旅游区专门推出建军节主题活动,诚挚邀请全体现役退役军人畅游川河盖,体验云端花园的美丽风八一到十一,现役军人有机会免费游览八达岭夜长城7月29日晚,八达岭长城脚下,延庆区庆祝八一双拥特色活动拉开序幕,延庆区立功受奖军人家属优秀退役军人代表等300余人与游客们一起观看表演,登夜长城,庆祝建军95周年。今年8月1日至