为了账号安全,请及时绑定邮箱和手机立即绑定

如果从线程启动,QMovie 不会启动

如果从线程启动,QMovie 不会启动

蓝山帝景 2023-08-22 15:51:42
各位开发者!我有一个关于 Qt 和多线程的问题。=== 简短版本 =============================================Qt 可以做我想做的事吗?即(1)显示一个加载器;(2)在后台下载gif;(3) 下载后在主窗口中显示下载的gif?===长版==============================================我有一个想法,当我按下按钮时,它:显示装载机;激活一个从网络下载 gif 的线程;用下载的 gif 替换主窗口中隐藏的默认 gif 并显示它隐藏装载机;我遇到的问题是,当显示下载的 gif 时,它被“冻结”或仅显示第一帧。除此之外一切都很好。然而,它需要在隐藏加载器后将 gif 动画化。这里提到的是所以Qt事件循环负责执行你的代码以响应程序中发生的各种事情,但是当它执行你的代码时,它不能做任何其他事情。我相信这是问题的核心。还建议建议在 Qt 中使用 QThread 而不是 Python 线程,但如果您不需要从函数与主线程通信,则 Python 线程可以正常工作。由于我的线程替换了默认 gif 的内容,我相信它确实进行了通信:(
查看完整描述

2 回答

?
哆啦的时光机

TA贡献1779条经验 获得超6个赞

除了不应在 Qt 主线程之外访问或创建任何 UI 元素这一事实之外,这对于使用在其他线程中创建的对象的 UI 元素也有效。


在您的具体情况下,这不仅意味着您无法在单独的线程中设置影片,而且也无法在那里创建 QMovie。


在下面的示例中,我打开一个本地文件,并使用信号将数据发送到主线程。从那里,我创建一个 QBuffer 将数据存储在 QMovie 可以使用的 IO 设备中。请注意,缓冲区和电影都必须有持久引用,否则函数返回后它们将被垃圾回收。


from PyQt5.QtCore import QThread, QByteArray, QBuffer


class ChangeGif(QThread):

    dataLoaded = pyqtSignal(QByteArray)

    def __init__(self, all_widgets):

        QThread.__init__(self)

        self.all = all_widgets


    def run(self):

        sleep(1)

        f = QFile('new.gif')

        f.open(f.ReadOnly)

        self.dataLoaded.emit(f.readAll())

        f.close()



class MainWindow(QWidget):

    # ...

    def change_gif(self):

        self.loader_label.show()

        self.worker = ChangeGif(self)

        self.worker.dataLoaded.connect(self.applyNewGif)

        self.worker.start()


    def applyNewGif(self, data):

        # a persistent reference must be kept for both the buffer and the movie, 

        # otherwise they will be garbage collected, causing the program to 

        # freeze or crash

        self.buffer = QBuffer()

        self.buffer.setData(data)

        self.newGif = QMovie()

        self.newGif.setCacheMode(self.newGif.CacheAll)

        self.newGif.setDevice(self.buffer)

        self.gif_label.setMovie(self.newGif)

        self.newGif.start()

        self.gif_label.show()

        self.loader_label.hide()

请注意,上面的示例仅用于解释目的,因为下载过程可以使用 QtNetwork 模块完成,该模块异步工作并提供简单的信号和插槽来下载远程数据:


from PyQt5.QtCore import QUrl

from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest


class MainWindow(QWidget):

    def __init__(self):

        # ...

        self.downloader = QNetworkAccessManager()


    def change_gif(self):

        self.loader_label.show()

        url = QUrl('https://path.to/animation.gif')

        self.device = self.downloader.get(QNetworkRequest(url))

        self.device.finished.connect(self.applyNewGif)


    def applyNewGif(self):

        self.loader_label.hide()

        self.newGif = QMovie()

        self.newGif.setDevice(self.device)

        self.gif_label.setMovie(self.newGif)

        self.newGif.start()

        self.gif_label.show()


查看完整回答
反对 回复 2023-08-22
?
繁华开满天机

TA贡献1816条经验 获得超4个赞

使用 Qt 的主要规则是只有一个主线程负责操作 UI 小部件。它通常被称为GUI thread。您永远不应该尝试从其他线程访问小部件。例如,Qt 计时器不会从另一个线程开始激活,并且 Qt 会在运行时控制台中打印警告。在你的情况下 - 如果你把 QMovie 的所有操作都放在 GUI 线程中,很可能一切都会按预期工作。

该怎么办?使用信号和槽 - 它们也被设计为在线程之间工作。

你的代码应该做什么:

  1. 从主线程显示加载程序。

  2. 激活从网络下载 gif 的线程。

  3. 下载准备就绪后 -GUI thread'. Remember to use 在连接信号和插槽时发出信号并在主 Qt::QueuedConnection` 中捕获它,尽管在某些情况下会自动使用它。

  4. 在接收槽中,将主窗口中的默认 gif 替换为下载的 gif,并显示它并隐藏加载程序。

您必须使用某种同步机制来避免数据竞争。一个互斥体就足够了。或者您可以将数据作为信号槽参数传递,但如果是 gif,它可能会太大。


查看完整回答
反对 回复 2023-08-22
  • 2 回答
  • 0 关注
  • 1658 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信