【问题标题】:How to fix custom messagebox to work in a new thread from parent python pyqt5如何修复自定义消息框以在父 python pyqt5 的新线程中工作
【发布时间】:2021-01-16 23:35:03
【问题描述】:

我在 pyqt5 中设计了自己的消息框,它运行良好。我删除了除基本部分之外的所有内容。这是我的代码

import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import  QApplication,QWidget,QPushButton,QDialog,QFrame,QLabel,QTextEdit
from threading import Thread
class messagebox():
    def __init__(self,parent):
        self.parent=parent
        self.parent_width=self.parent.geometry().width()
        self.parent_height=self.parent.geometry().height()
    #This method will flash the messagebox dialog when we click on parent    
    def flash(self,event):
                    QTimer.singleShot(50,lambda:self.toolbar.setStyleSheet("background-color: blue;\n"
        "border-top-left-radius: 20px;\n"
        "border-top-right-radius: 20px;\n"
        "") )
                    QTimer.singleShot(120, lambda:self.toolbar.setStyleSheet("background-color: red;\n"
        "border-top-left-radius: 20px;\n"
        "border-top-right-radius: 20px;\n"
        "") )           
       
    def showinfo(self,title="ShowInfo",msg="Hello,There!"):
        self.value=None
        self.pop = QDialog()
        self.pop.setGeometry(500,200,454,165)
        self.msg=msg
        self.pop.mousePressEvent=self.click
        self.pop.mouseMoveEvent=self.move
        self.frame = QFrame(self.pop)
        self.frame.setStyleSheet("background-color: #1b1b1b;\n"
            "border-bottom-left-radius: 20px;\n"
            "border-bottom-right-radius: 20px;\n"
            "")
        self.frame.setGeometry(0,30,454,134)
        self.toolbar = QFrame(self.pop)
        self.toolbar.setStyleSheet("background-color:red;\n"
            "border-top-left-radius: 20px;\n"
            "border-top-right-radius: 20px;\n"
            "")
        self.toolbar.setGeometry(0,0,454,30)
        #This makes the window to frameless
        self.pop.setWindowFlags(Qt.FramelessWindowHint|Qt.WindowStaysOnTopHint)                   
        self.pop.setAttribute(Qt.WA_TranslucentBackground, True)
        #self.cover will Cover a frame to its parent entirely
        self.cover=QFrame(self.parent)   
        self.cover.resize(self.parent_width,self.parent_height)
        self.cover.setStyleSheet("background-color:transparent;")
        #you can see the frame by setting background to a color
        self.cover.show()
        self.cover.mousePressEvent=self.flash
        b1 = QPushButton("Ok",self.frame)
        b1.setStyleSheet('''QPushButton {background-color: red;
        border-style: outset;
        border-width: 2px;
        border-radius: 10px;
        border-color: beige;
        font: bold 14px;
        min-width: 60px;
        min-height: 25px;
        }''''''QPushButton:pressed{background-color: green;
        border-style: inset;}''')
        b1.move(350,100)
        b1.clicked.connect(self.on_close)
        self.pop.setWindowTitle("Dialog")
        self.pop.setWindowModality(Qt.WindowModal)
        self.pop.exec_()
        return self.value
    def on_close(self):
        self.value=True
        self.cover.deleteLater()
        self.pop.close()
    # move and click  methods are used to drag the dialog   
    def move(self, event):
            if event.buttons() ==Qt.LeftButton:
                    deltax = event.x()- self.xp
                    deltay = event.y() - self.yp
                    x = self.pop.x() + deltax
                    y = self.pop.y() + deltay
                    width,height=int(self.pop.geometry().width()),int(self.pop.geometry().height())
                    self.pop.setGeometry(int(x),int(y),width,height)
    def click(self, event):
           if event.buttons() == Qt.LeftButton:
               self.xp=event.x()
               self.yp=event.y()      
def window():
       app = QApplication(sys.argv)
       w = QWidget()
       w.setGeometry(100,100,400,400)
       b = QPushButton(w)
       b.setText("Hello World!")
       b.move(50,50)
       messbox=messagebox(w)
       def run(): 
               h=messbox.showinfo('hello','how r u?')
               print(h) 
       #Thread(target=run).start() 
       b.clicked.connect(run)
       w.setWindowTitle("PyQt Dialog demo")
       w.show()
       sys.exit(app.exec_())    
if __name__ == '__main__':
   window()

但唯一的问题是当我从一个线程调用消息框方法(如messbox.showinfo())时,它说无法创建连接,因为孩子在新线程中。我已经浏览了这个网站上关于工作线程的各种示例。但是我无法正确理解工作线程。在这里我注释掉了“线程部分”并通过按钮单击调用。谁能让我明白吗?谢谢你!!!

【问题讨论】:

    标签: python python-3.x pyqt pyqt5 signals-slots


    【解决方案1】:

    基本概念是 UI 元素只能从主 Qt 线程创建访问。您不能创建新的小部件,也不能直接从另一个线程访问(读取,但最重要的是写入)它们的属性。

    为了做到这一点,你必须与主线程通信,Qt 的信号和槽正好可以用于此,因为 Qt 能够在不干扰其主循环的情况下管理线程之间的通信:任何通信 外部线程排队,然后在主线程允许时立即处理。请注意,要使用信号和插槽,您不应该使用 python 线程,因为通常最好从 QThread 子类化。为了与外部线程进行回通信,可以使用 python 队列。

    请注意,只有在某些处理需要一段时间才能完成(包括等待外部事件,例如来自网络请求的响应)时,才应使用外部线程。

    在您的情况下,您没有做这些事情,因此不需要另一个线程(此外,您的代码对于它应该做什么来说非常复杂,我不会将它用作以下示例的基础) .

    这是一个展示如何实现 QThread(“worker”)的简单示例。

    from PyQt5 import QtCore, QtWidgets
    from queue import Queue
    
    class Worker(QtCore.QThread):
        testSignal = QtCore.pyqtSignal()
        def __init__(self):
            super().__init__()
            self.queue = Queue()
    
        def run(self):
            self.timer = QtCore.QElapsedTimer()
            timeout = -1
            while True:
                try:
                    res = self.queue.get(timeout=.1)
                except:
                    # if a timeout has been already set, check if it's expired
                    if self.timer.hasExpired(timeout):
                        self.testSignal.emit()
                        self.timer.invalidate()
                        timeout = -1
                    continue
                if res is None:
                    self.timer.invalidate()
                    timeout = -1
                else:
                    timeout = res
                    self.timer.start()
    
        def setTimeout(self, timeout):
            self.queue.put(timeout)
    
        def stopTimeout(self):
            self.queue.put(None)
    
    
    class Window(QtWidgets.QDialog):
        def __init__(self):
            super().__init__()
            layout = QtWidgets.QVBoxLayout(self)
            self.spinBox = QtWidgets.QSpinBox(minimum=1000, maximum=5000, singleStep=500)
            layout.addWidget(self.spinBox)
            self.startButton = QtWidgets.QPushButton('Start')
            layout.addWidget(self.startButton)
            self.stopButton = QtWidgets.QPushButton('Stop')
            layout.addWidget(self.stopButton)
            self.stopButton.setEnabled(False)
            self.label = QtWidgets.QLabel()
            layout.addWidget(self.label)
            self.label.setAlignment(QtCore.Qt.AlignCenter)
    
            self.startButton.clicked.connect(self.startThreadTimeout)
            self.stopButton.clicked.connect(self.stopThreadTimeout)
            self.resetTimer = QtCore.QTimer(interval=1000, timeout=self.reset)
    
            self.worker = Worker()
            self.worker.start()
            self.worker.testSignal.connect(self.flash)
    
        def startThreadTimeout(self):
            self.reset()
            self.resetTimer.stop()
            self.worker.setTimeout(self.spinBox.value())
            self.startButton.setEnabled(False)
            self.stopButton.setEnabled(True)
            self.label.setText(
                'thread started, finishing in {}ms'.format(self.spinBox.value()))
    
        def stopThreadTimeout(self):
            # note that this does *not* stop the thread, but only its internal timeout check
            self.worker.stopTimeout()
            self.stopButton.setEnabled(False)
            self.startButton.setEnabled(True)
            self.reset()
    
        def flash(self):
            self.label.setStyleSheet('background: green')
            self.label.setText('finished')
            self.stopButton.setEnabled(False)
            self.startButton.setEnabled(True)
            self.resetTimer.start()
    
        def reset(self):
            self.label.setStyleSheet('')
            self.label.setText('')
    
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        window = Window()
        window.show()
        sys.exit(app.exec_())
    

    请注意,上面的代码并不是很有用,因为它只是用于学习目的:显然你可以在主线程中使用一个简单的普通 QTimer.singleShot。

    【讨论】:

    • 这有点复杂,你能举出适合我的代码的例子吗?
    • 让我解释一下我想要什么!我想将其用作消息框模块。假设我在主窗口中有一个下载过程,在那里我使用了线程,出于某种原因我想弹出消息框。因为它在线程内部,所以它会引发错误或停止工作@musicamante
    • 没有。如前所述,所有可能阻塞的进程必须发生在单独的线程中。您不应该在主窗口中有下载过程(除非它已经是线程安全的),并且您不应该从其他线程创建/访问 QWidget。您必须将下载放在线程中,并使用信号与主线程通信(然后最终显示消息框)。而且,正如已经说过的,您的示例过于复杂,将其适应您的案例将意味着完全重写它。研究我的代码并了解它的作用。
    猜你喜欢
    • 2021-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多