首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何从不同的进程向GUI中的插槽发出信号?

如何从不同的进程向GUI中的插槽发出信号?
EN

Stack Overflow用户
提问于 2014-11-05 06:37:10
回答 5查看 10.8K关注 0票数 19

上下文:在Python语言中,主线程产生第二个进程(使用多处理模块),然后启动图形用户界面(使用PyQt4)。此时,主线程会阻塞,直到GUI关闭。第二个进程总是在处理,理想情况下应该以异步方式向GUI中的特定插槽发出信号。

问:在Python和PyQt4中有哪些方法/工具可以实现这一点?如何实现?优选地以软中断方式而不是轮询方式。

抽象地说,我能想到的解决方案是在主线程中实例化的“工具/处理程序”,它从GUI实例获取可用的插槽,并与从第二个进程获取的信号连接,假设我为该工具提供了一些预期或硬编码的信息。这可以实例化到第三个进程/线程。

EN

回答 5

Stack Overflow用户

发布于 2015-05-05 17:17:19

这是一个示例Qt应用程序,演示从子进程向母进程中的插槽发送信号。我不确定这是不是正确的方法,但它是有效的。

我将进程区分为母进程和子进程,因为在Qt上下文中已经使用了父这个词。

母进程有两个线程。母进程的主线程通过multiprocessing.Queue向子进程发送数据。子进程将处理后的数据和信号签名通过multiprocessing.Pipe发送给母进程的第二个线程。母进程的第二个线程实际发出信号。

Python2.x,PyQt4:

代码语言:javascript
复制
from multiprocessing import Process, Queue, Pipe
from threading import Thread
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class Emitter(QObject, Thread):

    def __init__(self, transport, parent=None):
        QObject.__init__(self,parent)
        Thread.__init__(self)
        self.transport = transport

    def _emit(self, signature, args=None):
        if args:
            self.emit(SIGNAL(signature), args)
        else:
            self.emit(SIGNAL(signature))

    def run(self):
        while True:
            try:
                signature = self.transport.recv()
            except EOFError:
                break
            else:
                self._emit(*signature)

class Form(QDialog):

    def __init__(self, queue, emitter, parent=None):
        super(Form,self).__init__(parent)
        self.data_to_child = queue
        self.emitter = emitter
        self.emitter.daemon = True
        self.emitter.start()
        self.browser = QTextBrowser()
        self.lineedit = QLineEdit('Type text and press <Enter>')
        self.lineedit.selectAll()
        layout = QVBoxLayout()
        layout.addWidget(self.browser)
        layout.addWidget(self.lineedit)
        self.setLayout(layout)
        self.lineedit.setFocus()
        self.setWindowTitle('Upper')
        self.connect(self.lineedit,SIGNAL('returnPressed()'),self.to_child)
        self.connect(self.emitter,SIGNAL('data(PyQt_PyObject)'), self.updateUI)

    def to_child(self):
        self.data_to_child.put(unicode(self.lineedit.text()))
        self.lineedit.clear()

    def updateUI(self, text):
        text = text[0]
        self.browser.append(text)

class ChildProc(Process):

    def __init__(self, transport, queue, daemon=True):
        Process.__init__(self)
        self.daemon = daemon
        self.transport = transport
        self.data_from_mother = queue

    def emit_to_mother(self, signature, args=None):
        signature = (signature, )
        if args:
            signature += (args, )
        self.transport.send(signature)

    def run(self):
        while True:
            text = self.data_from_mother.get()
            self.emit_to_mother('data(PyQt_PyObject)', (text.upper(),))

if __name__ == '__main__':

    app = QApplication(sys.argv)
    mother_pipe, child_pipe = Pipe()
    queue = Queue()
    emitter = Emitter(mother_pipe)
    form = Form(queue, emitter)
    ChildProc(child_pipe, queue).start()
    form.show()
    app.exec_()

为了方便起见,还有Python3.x,PySide:

代码语言:javascript
复制
from multiprocessing import Process, Queue, Pipe
from threading import Thread

from PySide import QtGui, QtCore

class Emitter(QtCore.QObject, Thread):

    def __init__(self, transport, parent=None):
        QtCore.QObject.__init__(self, parent)
        Thread.__init__(self)
        self.transport = transport

    def _emit(self, signature, args=None):
        if args:
            self.emit(QtCore.SIGNAL(signature), args)
        else:
            self.emit(QtCore.SIGNAL(signature))

    def run(self):
        while True:
            try:
                signature = self.transport.recv()
            except EOFError:
                break
            else:
                self._emit(*signature)

class Form(QtGui.QDialog):

    def __init__(self, queue, emitter, parent=None):
        super().__init__(parent)
        self.data_to_child = queue
        self.emitter = emitter
        self.emitter.daemon = True
        self.emitter.start()
        self.browser = QtGui.QTextBrowser()
        self.lineedit = QtGui.QLineEdit('Type text and press <Enter>')
        self.lineedit.selectAll()
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.browser)
        layout.addWidget(self.lineedit)
        self.setLayout(layout)
        self.lineedit.setFocus()
        self.setWindowTitle('Upper')
        self.lineedit.returnPressed.connect(self.to_child)
        self.connect(self.emitter, QtCore.SIGNAL('data(PyObject)'), self.updateUI)

    def to_child(self):
        self.data_to_child.put(self.lineedit.text())
        self.lineedit.clear()

    def updateUI(self, text):
        self.browser.append(text[0])

class ChildProc(Process):

    def __init__(self, transport, queue, daemon=True):
        Process.__init__(self)
        self.daemon = daemon
        self.transport = transport
        self.data_from_mother = queue

    def emit_to_mother(self, signature, args=None):
        signature = (signature, )
        if args:
            signature += (args, )
        self.transport.send(signature)

    def run(self):
        while True:
            text = self.data_from_mother.get()
            self.emit_to_mother('data(PyQt_PyObject)', (text.upper(),))

if __name__ == '__main__':

    app = QApplication(sys.argv)
    mother_pipe, child_pipe = Pipe()
    queue = Queue()
    emitter = Emitter(mother_pipe)
    form = Form(queue, emitter)
    ChildProc(child_pipe, queue).start()
    form.show()
    app.exec_()
票数 21
EN

Stack Overflow用户

发布于 2018-12-06 16:15:08

大家好,

我希望这不会被认为是一场死亡转储,但我认为更新尼扎姆的答案会很好,方法是将更新他的示例添加到PyQt5中,添加一些注释,删除一些python2语法,最重要的是使用PyQt中提供的新样式的信号。希望有人觉得它有用。

代码语言:javascript
复制
"""
Demo to show how to use PyQt5 and qt signals in combination with threads and
processes.

Description:
Text is entered in the main dialog, this is send over a queue to a process that 
performs a "computation" (i.e. capitalization) on the data. Next the process sends 
the data over a pipe to the Emitter which will emit a signal that will trigger 
the UI to update.

Note:
At first glance it seems more logical to have the process emit the signal that 
the UI can be updated. I tried this but ran into the error 
"TypeError: can't pickle ChildProc objects" which I am unable to fix.
"""

import sys
from multiprocessing import Process, Queue, Pipe

from PyQt5.QtCore import pyqtSignal, QThread
from PyQt5.QtWidgets import QApplication, QLineEdit, QTextBrowser, QVBoxLayout, QDialog


class Emitter(QThread):
    """ Emitter waits for data from the capitalization process and emits a signal for the UI to update its text. """
    ui_data_available = pyqtSignal(str)  # Signal indicating new UI data is available.

    def __init__(self, from_process: Pipe):
        super().__init__()
        self.data_from_process = from_process

    def run(self):
        while True:
            try:
                text = self.data_from_process.recv()
            except EOFError:
                break
            else:
                self.ui_data_available.emit(text.decode('utf-8'))


class ChildProc(Process):
    """ Process to capitalize a received string and return this over the pipe. """

    def __init__(self, to_emitter: Pipe, from_mother: Queue, daemon=True):
        super().__init__()
        self.daemon = daemon
        self.to_emitter = to_emitter
        self.data_from_mother = from_mother

    def run(self):
        """ Wait for a ui_data_available on the queue and send a capitalized version of the received string to the pipe. """
        while True:
            text = self.data_from_mother.get()
            self.to_emitter.send(text.upper())


class Form(QDialog):
    def __init__(self, child_process_queue: Queue, emitter: Emitter):
        super().__init__()
        self.process_queue = child_process_queue
        self.emitter = emitter
        self.emitter.daemon = True
        self.emitter.start()

        # ------------------------------------------------------------------------------------------------------------
        # Create the UI
        # -------------------------------------------------------------------------------------------------------------
        self.browser = QTextBrowser()
        self.lineedit = QLineEdit('Type text and press <Enter>')
        self.lineedit.selectAll()
        layout = QVBoxLayout()
        layout.addWidget(self.browser)
        layout.addWidget(self.lineedit)
        self.setLayout(layout)
        self.lineedit.setFocus()
        self.setWindowTitle('Upper')

        # -------------------------------------------------------------------------------------------------------------
        # Connect signals
        # -------------------------------------------------------------------------------------------------------------
        # When enter is pressed on the lineedit call self.to_child
        self.lineedit.returnPressed.connect(self.to_child)

        # When the emitter has data available for the UI call the updateUI function
        self.emitter.ui_data_available.connect(self.updateUI)

    def to_child(self):
        """ Send the text of the lineedit to the process and clear the lineedit box. """
        self.process_queue.put(self.lineedit.text().encode('utf-8'))
        self.lineedit.clear()

    def updateUI(self, text):
        """ Add text to the lineedit box. """
        self.browser.append(text)


if __name__ == '__main__':
    # Some setup for qt
    app = QApplication(sys.argv)

    # Create the communication lines.
    mother_pipe, child_pipe = Pipe()
    queue = Queue()

    # Instantiate (i.e. create instances of) our classes.
    emitter = Emitter(mother_pipe)
    child_process = ChildProc(child_pipe, queue)
    form = Form(queue, emitter)

    # Start our process.
    child_process.start()

    # Show the qt GUI and wait for it to exit.
    form.show()
    app.exec_()
票数 12
EN

Stack Overflow用户

发布于 2015-04-29 21:44:37

首先应该看看信号/插槽在一个Python进程中是如何工作的:

如果只有一个正在运行的QThread,它们就直接调用插槽。

如果信号是在不同的线程上发出的,它必须找到该信号的目标线程,并将消息/ post事件放入该线程的线程队列中。然后,此线程将在适当的时候处理消息/事件并调用信号。

因此,内部总是涉及到某种类型的轮询,重要的是轮询是非阻塞的。

multiprocessing创建的进程可以通过管道进行通信,这为每一端提供了两个connections

Connectionpoll函数是非阻塞的,因此我会定期使用QTimer轮询它,然后相应地发出信号。

另一种解决方案可能是让来自线程模块(或QThread)的Thread专门等待来自具有队列的get函数的Queue的新消息。有关详细信息,请参阅multiprocessing的管道和队列部分。

下面是一个在另一个Process中启动Qt的示例,以及一个监听ConnectionThread,并在收到特定消息时关闭GUI,然后该GUI终止该进程。

代码语言:javascript
复制
from multiprocessing import Process, Pipe
from threading import Thread
import time
from PySide import QtGui

class MyProcess(Process):

    def __init__(self, child_conn):
        super().__init__()
        self.child_conn = child_conn

    def run(self):
        # start a qt application
        app = QtGui.QApplication([])
        window = QtGui.QWidget()
        layout = QtGui.QVBoxLayout(window)
        button = QtGui.QPushButton('Test')
        button.clicked.connect(self.print_something)
        layout.addWidget(button)
        window.show()

        # start thread which listens on the child_connection
        t = Thread(target=self.listen, args = (app,))
        t.start()

        app.exec_() # this will block this process until somebody calls app.quit

    def listen(self, app):
        while True:
            message = self.child_conn.recv()
            if message == 'stop now':
                app.quit()
                return

    def print_something(self):
        print("button pressed")

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    s = MyProcess(child_conn)
    s.start()
    time.sleep(5)
    parent_conn.send('stop now')
    s.join()
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/26746379

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档