在PyQt里面如何做长操作时同时更新GUI


类型:Python,C++ & Qt4,创建时间:Jan. 17, 2012, 3:53 p.m.

标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/78/。

经常看到有人问在PyQt里面如何做长操作的时候更新GUI。我自己总结了几种方案,以及他 们的适用场合:

  1. 使用processEvents(),适合每次处理时间短的情况,缺点是CPU占用可能很大:

    for filename in os.listdir(pathToYourDir):
        doSomething()
        updateGui()
        qApp.processEvents()
    
  2. threading,再用QMetaObject.invokeMethod()向主线程传递跨线程的信息

    class MyWindow(QMainWindow):
        @pyqtSlot(int)
        def updateProgress(self, value):
            self.lblProgress=str(value)
    
        def startProgress(self): #点击按钮的时候调用
            t = ProcessorThread()
            t.start()
    
    class ProcessorThread(threading.Thread:
        def __init__(self, myWindow):
            threading.Thread.__init__(self)
            self.myWindow=myWindow
    
        def run(self):
            for filename in os.listdir():
                doSomething()
                progress=29 #计算百分比
                QMetaObject.invokeMethod(self.myWindow, "updateProgress", \
                    Qt.QueuedConnection, Q_ARG("int", progress)
    

    此方案还有一个变种是使用跨线程的signal/slot(此方法还需要再参考QThread的文档,并非正规用法):

    class MyWindow(QMainWindow):
        def __init__(self):
            self.processorThread=ProcessorThread()
            self.processorThread.progressUpdated.connect( \
                    self.updateProgress, Qt.QueuedConnection)
    
        @pyqtSlot(int)
        def updateProgress(self, value):
            self.lblProgress=str(value)
    
    class ProcessorThread(QThread):
        progressUpdated=pyqtSignal(int)
    
        def __init__(self):
            QThread.__init__(self) #没有parent参数
            self.moveToThread(self)
    
        def run(self):
            for filename in os.listdir():
                doSomething()
                progress=29 #计算百分比
                self.progressUpdated.emit(progress)
    
  3. QProgressDialog。有同步与异步两种方法。先看看同步。

    buf=io.StringIO()
    progress=QProgressDialog(self.trUtf8("打开文件"), \
        self.trUtf8("取消"), 0, f.size(), self)
    progress.setWindowModality(Qt.WindowModal)
    while not f.atEnd():
        data=f.read(1024)
        text=data.decode(locale.getpreferredencoding())
        buf.write(text)
        progress.setValue(f.pos())
        if progress.wasCanceled():
            return
    self.textEdit.setPlainText(buf.getvalue())
    progress.setValue(f.size()) #100%的时候对话框自动关掉
    progress.setParent(None) #清理资源
    

    异步与第二种方案类似,但是采用signal/slot,而且不能是模态对话框。

  4. 把长的操作分为小的操作,适合每次处理都是异步的或者很复杂的情况

    def __init__(self):
        self.itorator=self.fetchFile()
        self.timer=QTimer()
        self.timer.timeout.connect(self.processFile)
        self.timer.start(0)
    
    def fetchFile(self):
        for filename in os.listdir(pathToYourDir):
            yield filename
    
    def processFile():
        try:
            filename=self.itorator.next()
            doSomething()
            updateGui()
        except StopIterator:
            self.timer.stop()
    

第四种方案不常用,但是当你的处理过程本身是异步的时候就会用到。第一、第二方案都不 错,优点是灵活。第三种方案也常见,优点是模态对话框阻止用户的其它操作,可以随时取 消。缺点是一个对话框挡在那里,相当地影响用户体验。

ps:这些代码不能直接运行。。。差不多懂就好,呵呵 再ps: 纯网络操作可以看一下我的 eventlet-pyqt

标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/78/。


zzhiyuan(Oct. 28, 2013, 9:14 p.m.)

等我做完成了,会来膜拜的,这东西找半天了。


何不来发表一下您对本文的看法(使用Markdown语法,分段空两行):