【问题标题】:QMovie does not start if started from a thread如果从线程启动,QMovie 不会启动
【发布时间】:2020-09-20 13:16:51
【问题描述】:

开发人员!我有一个关于 Qt 和多线程的问题。

=== 简短版本 =========================================== =

可以用 Qt 做我想做的事吗?即(1)显示装载机; (2)在后台下载一个gif; (3) 下载好的gif在主窗口显示?

=== 长版 ============================================ ==

我有这样的想法,当我按下按钮时,它:

  1. 显示加载器;
  2. 激活从网络下载 gif 的线程;
  3. 将隐藏在主窗口中的默认gif替换为下载的gif并显示
  4. 隐藏加载器;

我遇到的问题是,当下载的 gif 被显示时,它被“冻结”或只显示第一帧。除此之外一切都很好。但是,它需要在加载器隐藏后动画 gif。

提到here那个

因此 Qt 事件循环负责执行代码以响应程序中发生的各种事情,但在执行代码时,它不能做任何其他事情。。 p>

我相信这是问题的核心。还建议

建议在 Qt 中使用 QThread 而不是 Python 线程,但是如果您不需要从函数与主线程回通信,Python 线程将可以正常工作。

由于我的线程替换了默认 gif 的内容,我相信 它确实会回传 :(

请在下面找到我的代码:)

import sys
from time import sleep

from PyQt5.QtCore import QThread
from PyQt5.QtGui import QMovie
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel


class ChangeGif(QThread):
    """
    A worker that:
    1 - waits for 1 second;
    2 - changes the content of the default gif;
    3 - shows the default gif with new contents and hide the loader.
    """

    def __init__(self, all_widgets):
        QThread.__init__(self)
        self.all = all_widgets

    def run(self):
        sleep(1)
        self.new_gif_file = QMovie("files/new.gif")  # This is the "right" gif that freezes :(
        self.new_gif_file.start()
        self.all.gif_label.setMovie(self.new_gif_file)
        self.all.gif_label.show()
        self.all.loader_label.hide()


class MainWindow(QWidget):
    """
    Main window that shows a button. If you push the button, the following happens:
    1 - a loader is shown;
    2 - a thread in background is started;
    3 - the thread changes the contents of the default gif;
    4 - when the gif is replaced, the loader disappears and the default gif with the new content is shown
    """
    def __init__(self):
        super(MainWindow, self).__init__()

        # BUTTON
        self.button = QPushButton("Push me", self)
        self.button.setFixedSize(100, 50)
        self.button.clicked.connect(lambda: self.change_gif())

        # DEFAULT GIF
        self.gif_file = QMovie("files/default.gif")  # This is the "wrong" gif
        self.gif_file.start()
        self.gif_label = QLabel(self)
        self.gif_label.setMovie(self.gif_file)
        self.gif_label.move(self.button.width(), 0)
        self.gif_label.hide()

        # LOADER
        self.loader_file = QMovie("files/loader.gif")
        self.loader_file.start()
        self.loader_label = QLabel(self)
        self.loader_label.setMovie(self.loader_file)
        self.loader_label.move(self.button.width(), 0)
        self.loader_label.hide()

        # WINDOW SETTINGS
        self.setFixedSize(500, 500)
        self.show()

    def change_gif(self):
        self.loader_label.show()
        self.worker = ChangeGif(self)
        self.worker.start()


app = QApplication(sys.argv)
window = MainWindow()
app.exec_()

【问题讨论】:

    标签: python multithreading pyqt5 qthread qmovie


    【解决方案1】:

    除了不应在主 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()
    

    【讨论】:

      【解决方案2】:

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

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

      你的代码应该做什么:

      1. 在主线程中显示一个加载器。
      2. 激活从网络下载 gif 的线程。
      3. 下载完成后 - 在连接信号和插槽时发出一个信号并在 main GUI thread'. Remember to use Qt::QueuedConnection` 中捕获它,但在某些情况下会自动使用它。
      4. 在接收槽中,将主窗口中的默认 gif 替换为下载的 gif 并显示并隐藏加载器。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多