QT Demo 之 threading

本文深入探讨了在QML中利用Timer和WorkerScript完成异步处理的技术,通过实例展示了如何在不阻塞UI线程的情况下执行后台任务,并在UI更新视图。重点介绍了Timer组件的使用、WorkerScript的异步处理特性,以及如何在QML中调用JS文件以实现复杂逻辑。文章旨在帮助开发者掌握在QML中进行后台任务处理的技巧,确保用户体验流畅。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在学习了MouseArea、Text、Image这些基本组件后,我们这一章学习如何在QML中完成一些异步处理。

这一章我们通过下述两个例子来分别讲解一下Timer和WorkerScript的使用。

threadedlistmodel/timedisplay.qml

这一个示例的原始代码中同时使用了Timer和WorkerScript来完成一个比较简单的工作,为了简化处理,我针对代码做了一些小改动,去掉了WorkerScript部分(同时包括dataloader.js部分),只使用Timer来完成是示例一样的效果。修改后的代码如下(原始代码请参考Qt的示例代码):

Rectangle {
    color: "white"
    width: 200
    height: 300

    ListView {
        anchors.fill: parent
        model: listModel
        delegate: Component {
            Text { text: time }
        }

        ListModel { id: listModel }

        Timer {
            id: timer
            interval: 2000; repeat: true
            running: true
            triggeredOnStart: true

            onTriggered: {
                var data = {'time': new Date().toTimeString()};
                listModel.append(data);
                listModel.sync();   // updates the changes to the list
            }
        }
    }
}
那么从上面的代码可以看到,ListView、ListModel都是我们之前已经了解的组件,那么我们就把焦点放到Timer组件上。

Timer简述

A Timer can be used to trigger an action either once, or repeatedly at a given interval.

其实Timer组件还是很好理解的,就是一个定时器,按照给定的interval定时触发,我们只需要在onTriggered事件响应函数中完成自己需要的操作即可。

  • interval指定了触发的事件间隔是2s
  • repeat和running都是true,表示该Timer循环进行触发,而不是只触发一次
  • triggeredOnStart表示上来就触发一次,而不是等interval之后才触发第一次
  • onTriggered事件相应函数中,我们通过listModel的数据来刷新ListView中显示

Timer的使用也就是上面的几个属性和事件通知了,示例中没有使用到的还有几个函数:restart()、start()和stop(),其意义和使用方法也很简单。

workerscript/workerscript.qml

在上一个示例中,我们只关注了Timer,去掉了WorkerScript相关的代码。在这一节中,我们就需要完全使用WorkerScript来完成我们的需求了。

先看代码整体结构:

Rectangle {
    width: 320; height: 480

//! [1]
    WorkerScript {...}
//! [1]
    Row {...}

    Text {...}

    Text {...}
}
其中两个Text分别是显示用户提示("Pascal's Triangle Calculator")和计算结果(resultText),而Row部分则是水平排列的两个Spinner。

这里用的Spinner是一个自定义的Component,具体实现是在:/threading/workerscript/Spinner.qml文件中,这里暂时先不详细分析这个文件,我们先看一看如何使用Spinner:

        Spinner {
            id: rowSpinner
            label: "Row"
            onValueChanged: {
                resultText.text = "Loading...";
                myWorker.sendMessage( { row: rowSpinner.value, column: columnSpinner.value } );
            }
        }
注:另外一个Spinner除了id和label不同之外,onValueChanged事件响应函数是一模一样的,此处不再重复贴上代码。

从代码上看,一个Spinner的属性包括label(即显示在Spinner上面的文字),一个value(即当前Spinner显示的数据),如下图所示:

当Spinner中的数据发送变化时,则会触发onValueChanged事件相应函数。关于自定义组件Spinner的讲解暂时先到这里,后面再详细展开。

WorkerScript简述

Use WorkerScript to run operations in a new thread. This is useful for running operations in the background so that the main GUI thread is not blocked.

从上面官方说明上,我们可以了解WorkerScript就是设计成可以在后台执行操作,而不会阻塞UI线程的一种组件。通过上面的说明,我们也可以了解WorkerScript必须有的几个元素:

  • source:既然是后台执行操作,这里使用了source指定一个js文件运行后台的操作
  • onMessage:既然是异步处理,那么肯定涉及到的message,这里的message是双向的,从js文件到qml文件使用onMessage事件相应函数
  • sendMessage(jsobject message):从qml到js文件,使用sendMessage函数发送给js文件需要执行的操作

下面就是代码中的WorkerScript部分:

    WorkerScript {
        id: myWorker
        source: "workerscript.js"

        onMessage: {
            if (messageObject.row == rowSpinner.value && messageObject.column == columnSpinner.value){ //Not an old result
                if (messageObject.result == -1)
                    resultText.text = "Column must be <= Row";
                else
                    resultText.text = messageObject.result;
            }
        }
    }
而workerscript.js中的onMessage函数如下:

WorkerScript.onMessage = function(message) {
    //Calculate result (may take a while, using a naive algorithm)
    var calculatedResult = triangle(message.row, message.column);
    //Send result back to main thread
    WorkerScript.sendMessage( { row: message.row,
                                column: message.column,
                                result: calculatedResult} );
}

从上面代码可以很明显的看到:
从qml到js使用的是myWorker.sendMessage( { row: rowSpinner.value, column: columnSpinner.value } );,数据包含row和column,在workerscript.js中的onMessage函数中也是使用参数message的两个属性值;
从js到qml使用的是WorkerScript.sendMessage( { row: message.row,column: message.column,result: calculatedResult} );,数据包含row、colume和result,在qml的onMessage函数中也是使用了这三个参数,不过需要注意的是这里的messageObject应该是内置变量(吐槽+1)。

总结

本节学到的知识点:

  1. Timer组件的使用方法
  2. WorkerScript组件的使用方法
  3. 学习如何通过Timer组件或WorkerScript组件来完成后台线程执行操作,而在UI线程中刷新的方法
  4. 稍微了解如何在qml中如何使用js文件

从今天开始,我们开始了学习如何在qml中完成一些逻辑,也算是摆脱了只是学习UI组件的不爽。但是无论后台逻辑完成什么复杂的处理,绝不能造成UI线程的阻塞,如果不能及时响应用户的操作,甚至出现假死的现象,对于应用开发都是大忌。

### 使用海康相机SDK进行基于Qt和Python的二次开发 #### 开发环境准备 为了使用海康威视工业相机SDK进行基于Qt和Python的二次开发,首先需要准备好相应的开发环境。这包括安装必要的依赖库以及配置开发工具链。 1. **安装依赖库** - 安装 `PyQt` 或 `PySide` 库来支持Qt框架在Python中的应用[^5]。 ```bash pip install PyQt5 PySide2 ``` - 安装 `opencv-python` 和 `numpy` 以便于图像处理和矩阵运算[^6]。 ```bash pip install opencv-python numpy ``` 2. **下载并解压海康威视SDK** - 下载适用于目标操作系统的海康威视SDK版本,并按照官方文档完成初始化配置[^7]。 - 将SDK动态链接库文件路径加入到系统环境变量中,确保Python能够正确加载所需的DLL或SO文件[^8]。 #### 示例代码结构设计 以下是基于Qt和Python实现的一个简单示例程序架构: ```python import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget from PyQt5.QtGui import QImage, QPixmap import cv2 import threading class CameraWidget(QWidget): def __init__(self, camera_id=0, parent=None): super().__init__(parent) self.camera_id = camera_id self.cap = None self.thread = None self.init_ui() def init_ui(self): layout = QVBoxLayout() self.label = QLabel(self) layout.addWidget(self.label) self.setLayout(layout) def start_capture(self): self.cap = cv2.VideoCapture(self.camera_id) if not self.cap.isOpened(): print(f"Failed to open camera {self.camera_id}") return self.thread = threading.Thread(target=self.update_frame, daemon=True) self.thread.start() def update_frame(self): while True: ret, frame = self.cap.read() if not ret: break rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb_image.shape bytes_per_line = ch * w qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(qt_image.scaled(self.width(), self.height())) self.label.setPixmap(pixmap) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Hikvision Camera Demo") main_layout = QVBoxLayout() widget_container = QWidget() widget_container.setLayout(main_layout) self.cam_widget_1 = CameraWidget(camera_id=0) self.cam_widget_2 = CameraWidget(camera_id=1) main_layout.addWidget(self.cam_widget_1) main_layout.addWidget(self.cam_widget_2) self.setCentralWidget(widget_container) def showEvent(self, event): self.cam_widget_1.start_capture() self.cam_widget_2.start_capture() if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.showMaximized() sys.exit(app.exec_()) ``` 此代码实现了通过两个摄像头采集视频流并将其实时显示在一个简单的GUI界面中[^9]。 #### 关键技术点解析 1. **多线程机制** 在上述代码中采用了多线程的方式运行摄像头捕捉逻辑,从而避免阻塞主线程导致UI卡顿现象发生[^10]。 2. **图像格式转换** OpenCV默认返回BGR颜色空间下的图片数组,在将其传递给QLabel之前需先转成RGB模式再创建对应的QImage对象[^11]。 3. **跨平台适配性考虑** 虽然本例子仅展示了如何连接本地物理设备作为输入源的情况;但在实际部署过程中可能还需要针对网络摄像机或者特定硬件接口做额外调整[^12]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值