第十三章:网络
第十三章:网络
QtQt QuickQML
https://gist.github.com 来访问。我在下面https://gist.github.com/jryannel/7983492 创建了这个gist的小例子。它会显示一个绿色小框。因为gist的URL将会为浏览器提供HTML编码,所以需要在URL后面添加/raw
来检索原始文件而不是HTML代码。
// GistExample.qml
import QtQuick
Loader {
id: root
source: 'https://gist.github.com/jryannel/7983492/raw'
onLoaded: {
root.width = root.item.width // qmllint disable
root.height = root.item.height // qmllint disable
}
}
// GistExample.qml
import QtQuick
Loader {
id: root
source: 'https://gist.github.com/jryannel/7983492/raw'
onLoaded: {
root.width = root.item.width // qmllint disable
root.height = root.item.height // qmllint disable
}
}
想从RemoteComponent.qml
来通过网络加载另一个文件,需要在服务端的相同文件路径下创建一个专门的qmldir
文件。完成后,就可以通过名称来引用组件了。
http://localhost:8080/main.qml#1234),这里的‘#1234’ 就是URL参数段。HTTP服务总提供相同的文档,但QML将会使用整个URL(包括参数段)来缓存文档。每次访问URL时,参数段需要重新生成,这样QML才不会取到缓存值。参数段可以是当前以毫秒格式时间或随机数。
Loader {
source: 'http://localhost:8080/main.qml#' + new Date().getTime()
}
Loader {
source: 'http://localhost:8080/main.qml#' + new Date().getTime()
}
总的来说,模板可以使用,但真的不推荐,因其没有发挥QML的真正能力。较好的实现方式是使用提供JSON或XML格式数据的web服务。
https://flask.palletsprojects.com) ,一个基于pythonr的简单HTTP应用服务,来创建一个简单的颜色服务。你也可以使用其它服务来接收和返回JSON数据。思路是假定有一组可以通过web服务来进行管理的,已命名的颜色。这里,可管理意味着CRUD(create-read-update-delete)。
在Flask,简单的web服务可以写在一个文件里。我们先从server.py
文件开始。这个文件里,我们创建代码来从外部JSON文件加载初始颜色。参见Flask快速开始文档。
from flask import Flask, jsonify, request
import json
with open('colors.json', 'r') as file:
colors = json.load(file)
app = Flask(__name__)
# Services registration & implementation...
if __name__ == '__main__':
app.run(debug = True)
from flask import Flask, jsonify, request
import json
with open('colors.json', 'r') as file:
colors = json.load(file)
app = Flask(__name__)
# Services registration & implementation...
if __name__ == '__main__':
app.run(debug = True)
当运行此脚本,会在http://localhost:5000 创建web服务,但不提供啥有用的服务。
现在就要在我们的服务中加入CRUD (Create,Read,Update,Delete) 服务节点。
http://localhost:5000/colors ’)。第三个参数是可选的,是将传给服务的JSON数据。最末的参数定义了当返回响应时调用的回调函数。在发送请求前,要通过修改请求头来指明请求和接收JSON格式的数据。
使用这个请求帮助函数,可以实现我们之前定义的简单命令(create, read, update, delete)。以下是服务实现中的代码。
function getColors(cb) {
// GET http://localhost:5000/colors
request('GET', null, null, cb)
}
function createColor(entry, cb) {
// POST http://localhost:5000/colors
request('POST', null, entry, cb)
}
function getColor(name, cb) {
// GET http://localhost:5000/colors/${name}
request('GET', name, null, cb)
}
function updateColor(name, entry, cb) {
// PUT http://localhost:5000/colors/${name}
request('PUT', name, entry, cb)
}
function deleteColor(name, cb) {
// DELETE http://localhost:5000/colors/${name}
request('DELETE', name, null, cb)
}
function getColors(cb) {
// GET http://localhost:5000/colors
request('GET', null, null, cb)
}
function createColor(entry, cb) {
// POST http://localhost:5000/colors
request('POST', null, entry, cb)
}
function getColor(name, cb) {
// GET http://localhost:5000/colors/${name}
request('GET', name, null, cb)
}
function updateColor(name, entry, cb) {
// PUT http://localhost:5000/colors/${name}
request('PUT', name, entry, cb)
}
function deleteColor(name, cb) {
// DELETE http://localhost:5000/colors/${name}
request('DELETE', name, null, cb)
}
在UI里我们使用这个服务来实现命令。有一个id为gridModel
的ListModel
列表模型,为视图GridView
来提供数据。使用UI元素Button
来确认命令。
直接引入服务:
import "colorservice.js" as Service
从服务端读取颜色列表:
Button {
text: 'Read Colors'
onClicked: {
Service.getColors(function(response) {
print('handle get colors response: ' + JSON.stringify(response))
gridModel.clear()
const entries = response.data
for(let i=0; i
在服务端新建一个颜色条目:
Button {
text: 'Create New'
onClicked: {
const index = gridModel.count - 1
const entry = {
name: 'color-' + index,
value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
}
Service.createColor(entry, function(response) {
print('handle create color response: ' + JSON.stringify(response))
gridModel.append(response)
})
}
}
以颜色名来请求一个颜色:
Button {
text: 'Read Last Color'
onClicked: {
const index = gridModel.count - 1
const name = gridModel.get(index).name
Service.getColor(name, function(response) {
print('handle get color response:' + JSON.stringify(response))
message.text = response.value
})
}
}
在服务器上基于名称来更新颜色条目:
Button {
text: 'Update Last Color'
onClicked: {
const index = gridModel.count - 1
const name = gridModel.get(index).name
const entry = {
value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
}
Service.updateColor(name, entry, function(response) {
print('handle update color response: ' + JSON.stringify(response))
gridModel.setProperty(gridModel.count - 1, 'value', response.value)
})
}
}
通过颜色名称删除颜色:
Button {
text: 'Delete Last Color'
onClicked: {
const index = gridModel.count - 1
const name = gridModel.get(index).name
Service.deleteColor(name)
gridModel.remove(index, 1)
}
}
使用REST API实现的CRUD (create, read, update, delete) 至此结束。还有其它方法来生成web服务API。可以是基于模块的,每个模块有一个服务节点。而API可以使用JSON RPC来定义(http://www.jsonrpc.org/) 。当然基于XML的API也是可以的,但JSON方式更有优势,因为JSON解析已经作为JavaScript的一部分内建到了QML/JS里。
http://oauth.net/
Spotify API集成OAuth的例子,这个例子混合使用了 C++ 类和QML/JS。关于更多集成细节,参考《Qt C++ 》一章。
此应用程序的目标是向经过身份验证的用户展示检索最受欢迎的前十位艺术家。
Spotify Developer's portal创建一个专用的app。

应用建好后,会收到两个关键字:一个client id
和一个client secret
。
ws模块的web socket 。首先要安装node js。然后,创建ws_server
文件夹,然后使用包管理器(npm)安装ws包。
该代码将在 NodeJS 中创建一个简单的回显服务器,以将我们的消息回显到我们的 QML 客户端。
cd ws_server
npm install ws

应用建好后,会收到两个关键字:一个
client id
和一个client secret
。
ws模块的web socket 。首先要安装node js。然后,创建ws_server
文件夹,然后使用包管理器(npm)安装ws包。
该代码将在 NodeJS 中创建一个简单的回显服务器,以将我们的消息回显到我们的 QML 客户端。
cd ws_server
npm install ws
cd ws_server
npm install ws
npm工具在本地文件夹下载并安装ws包,以及其它相关依赖的包。
server.js
文件将成我们的服务器端实现文件。服务端代码将在端口3000,创建一个web socket服务,并监听接入的请求。当一个连接接入时,它会发现一段致意,并等待客户端信息。通过socket发送的每个客户端信息将会被发回到客户端。
const WebSocketServer = require('ws').Server
const server = new WebSocketServer({ port : 3000 })
server.on('connection', function(socket) {
console.log('client connected')
socket.on('message', function(msg) {
console.log('Message: %s', msg)
socket.send(msg.toString())
});
socket.send('Welcome to Awesome Chat')
});
console.log('listening on port ' + server.options.port)
你要适应JavaScript的写法和回调函数。
WS Client
在客户端我们需要一个列表视图来显示消息,还需要一个TextInput来接收用户输入的聊天信息。
我们将在示例中使用白色标签。
// Label.qml
import QtQuick
Text {
color: '#fff'
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
聊天视图是个列表视图,文本信息会被添加到列表模型。每个信息条目将是行号前辍加上消息label。我们使用单元格宽度cw
因子将with拆分为24列。
// ChatView.qml
import QtQuick
ListView {
id: root
width: 100
height: 62
model: ListModel {}
function append(prefix, message) {
model.append({prefix: prefix, message: message})
}
delegate: Row {
id: delegate
required property var model
property real cw: width / 24
width: root.width
height: 18
Label {
width: delegate.cw * 1
height: parent.height
text: delegate.model.prefix
}
Label {
width: delegate.cw * 23
height: parent.height
text: delegate.model.message
}
}
}
聊天输入框是在简单的文本框外围加上了颜色边框。
// ChatInput.qml
import QtQuick
FocusScope {
id: root
property alias text: input.text
signal accepted(string text)
width: 240
height: 32
Rectangle {
anchors.fill: parent
color: '#000'
border.color: '#fff'
border.width: 2
}
TextInput {
id: input
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 4
anchors.rightMargin: 4
color: '#fff'
focus: true
onAccepted: function () {
root.accepted(text)
}
}
}
当web socket接收到消息后,它将消息添加到聊天的视图列表。状态变更时也同样处理。此外,当用户输入聊天消息时,副本会附加到客户端的聊天视图中,并将消息发送到服务器。
// ws_client.qml
import QtQuick
import QtWebSockets
Rectangle {
width: 360
height: 360
color: '#000'
ChatView {
id: box
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: input.top
}
ChatInput {
id: input
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
focus: true
onAccepted: function(text) {
print('send message: ' + text)
socket.sendTextMessage(text)
box.append('>', text)
text = ''
}
}
WebSocket {
id: socket
url: "ws://localhost:3000"
active: true
onTextMessageReceived: function (message) {
box.append('<', message)
}
onStatusChanged: {
if (socket.status == WebSocket.Error) {
box.append('#', 'socket error ' + socket.errorString)
} else if (socket.status == WebSocket.Open) {
box.append('#', 'socket open')
} else if (socket.status == WebSocket.Closed) {
box.append('#', 'socket closed')
}
}
}
}
先要运行服务端再运行客户端。这个简例中没有重连机制。
运行服务端
cd ws_server
node server.js
运行客户端
cd ws_client
qml ws_client.qml
当输入文本并按下回车时,将会看到类似如下信息。
总结
关于QML网络的这一章到此结束。请记住,Qt在原生的部分相比QML这部分有更丰富的网络API。但本章的重点是放在QML网络,以及如何与云服务集成的部分。