如何修复自定义messagebox以在父python pyqt5的新线程中工作
我已经在pyqt5中设计了我自己的消息框,它工作得很好。我删除了除基本部分之外的所有内容。这是我的代码如何修复自定义messagebox以在父python pyqt5的新线程中工作,python,python-3.x,pyqt,pyqt5,signals-slots,Python,Python 3.x,Pyqt,Pyqt5,Signals Slots,我已经在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 messa
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()
但唯一的问题是当我调用messagebox方法时,比如
messbox.showinfo()
从一个线程中,它说无法创建连接,因为子线程位于新线程中。我在这个网站上浏览了关于工作线程的各种示例。但我无法正确理解工作线程。在这里,我注释掉了“线程部分”,并通过单击按钮调用。有人能让我明白吗?谢谢你 基本概念是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。这有点复杂,您能给出适合我的代码的示例吗?让我解释一下我想要什么!我想将其用作messagebox模块。假设我在主窗口中有一个下载进程,我使用了thread,出于某种原因,我想弹出messagebox。因为它在thread内,所以它会抛出错误或停止工作@Musicamento。如前所述,所有可能阻塞的进程都必须在单独的线程中发生。您不应该在主窗口中有一个下载进程(除非它已经是线程安全的),也不应该从其他线程创建/访问QWidget。您必须将下载放在线程中,并使用信号与主线程通信(然后最终显示消息框)。而且,正如前面所说的,您的示例不必要地复杂,根据您的案例调整它将意味着完全重写它。研究我的代码并理解它的作用。