2016-08-17 10 views
1

Я создаю GUI PyQt для экспериментальной установки. Это будет включать в себя вычислительно тяжелые операции, поэтому я нацелен на архитектуру, основанную на модуле многопроцессорности и вдохновленную от this answer.Вывести новый сигнал PyQt с произвольной подписи

Функция QMainWindow создает

  1. дочерних процессов с помощью отдельной «задачи» очередь, чтобы получить инструкции от основного процесса и общего «обратного вызова» очереди, чтобы послать инструкции к основному процессу
  2. QThread к опрашивать «обратный вызов» очередь и переводить сообщения в сигналы, которые подключены к пазам QMainWindow

пример использует старые сигналы стиля с произвольной сигнатурой self.emit(QtCore.SIGNAL(signature), args). Мой вопрос: Можно ли воспроизвести эту функцию с помощью сигналов нового стиля?.

Я знаю this question и this one. Однако всегда излучение сигнала valueChanged с общим объектом не соответствует моим потребностям, так как я хотел бы подключиться к слотам с разными именами в зависимости от сигнатуры, полученной от одного из дочерних процессов.

Вот рабочий код (обратите внимание, что только один дочерний процесс и один слот в MainWindow для простоты, но будет несколько в готовом коде):

from multiprocessing import Process, Queue 
import sys 
from PyQt4 import QtGui, QtCore 


class CallbackQueueToSignal(QtCore.QThread): 

    def __init__(self, queue, parent=None): 
     super(CallbackQueueToSignal, self).__init__(parent) 
     self.queue = queue 

    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: 
      signature = self.queue.get() 
      self._emit(*signature) 


class WorkerProcess(Process): 

    def __init__(self, callback_queue, task_queue, daemon=True): 
     super(WorkerProcess, self).__init__() 
     self.daemon = daemon 
     self.callback_queue = callback_queue 
     self.task_queue = task_queue 

    def _process_call(self, func_name, args=None): 
     func = getattr(self, func_name) 
     if args: 
      func(args) 
     else: 
      func() 

    def emit_to_mother(self, signature, args=None): 
     signature = (signature,) 
     if args: 
      signature += (args,) 
     self.callback_queue.put(signature) 

    def run(self): 
     while True: 
      call = self.task_queue.get() 
      # print("received: {}".format(call)) 
      self._process_call(*call) 

    def text_upper(self, text): 
     self.emit_to_mother('data(PyQt_PyObject)', (text.upper(),)) 


class MainWin(QtGui.QMainWindow): 

    def __init__(self, parent=None): 
     super(MainWin, self).__init__(parent) 

     self.data_to_child = Queue() 
     self.callback_queue = Queue() 

     self.callback_queue_watcher = CallbackQueueToSignal(self.callback_queue) 
     self.callback_queue_watcher.start() 

     self.child = WorkerProcess(self.callback_queue, self.data_to_child) 
     self.child.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.layout_widget = QtGui.QWidget() 
     self.layout_widget.setLayout(layout) 
     self.setCentralWidget(self.layout_widget) 
     self.lineedit.setFocus() 
     self.setWindowTitle('Upper') 
     self.connect(self.lineedit, QtCore.SIGNAL('returnPressed()'), self.to_child) 
     self.connect(self.callback_queue_watcher, QtCore.SIGNAL('data(PyQt_PyObject)'), self.updateUI) 

    def to_child(self): 
     self.data_to_child.put(("text_upper",) + (self.lineedit.text(),)) 
     self.lineedit.clear() 

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

    def closeEvent(self, event): 
     result = QtGui.QMessageBox.question(
      self, 
      "Confirm Exit...", 
      "Are you sure you want to exit ?", 
      QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) 
     event.ignore() 

     if result == QtGui.QMessageBox.Yes: 
      # self.pipeWatcher.exit() 
      self.child.terminate() 
      event.accept() 

if __name__ == '__main__': 

    app = QtGui.QApplication(sys.argv) 

    form = MainWin() 
    form.show() 

    app.aboutToQuit.connect(app.deleteLater) 
    sys.exit(app.exec_()) 
+0

Почему соединение должны зависеть от подписи? Просто используйте один предварительно определенный сигнал, как предлагается в вопросах, с которыми вы связаны, и отправьте идентификатор в качестве первого параметра. – ekhumoro

+0

В этом случае вы потеряете преимущество использования сигналов. Например, вы не могли подключать и отключать определенные слоты от определенного сигнала во время выполнения программы. Но, может быть, мне не хватает вашей точки? –

+0

Невозможно реплицировать динамически излучающие пользовательские сигналы с помощью сигналов нового стиля. Пользовательские сигналы должны быть предварительно определены как атрибуты класса. Но я не вижу причин, почему это повлияет на подключение и отключение сигналов во время выполнения. И я также не вижу, как это имеет отношение к вашему фактическому вопросу - ваш пример кода не отключает никаких сигналов. – ekhumoro

ответ

1

new-style signal and slot syntax требует, чтобы сигналы предварительно определенные как атрибуты класса для класса, который наследует от QObject. Когда экземпляр класса создается, для экземпляра создается автоматически связанный объект. Объект связанного сигнала имеет методы connect/disconnect/emit и синтаксис __getitem__, который позволяет выбирать разные перегрузки.

Поскольку связанные сигналы объектов, уже не имеет смысла допускать динамическое излучение произвольных сигналов, которое было возможно с помощью синтаксиса старого стиля. Это просто потому, что произвольный сигнал (т. Е. Тот, который не является , предопределенным) не может иметь соответствующий объект связанного сигнала для подключения слотов.

Пример кода в этом вопросе по-прежнему может быть перенесен на синтаксис нового стиля, хотя:

class CallbackQueueToSignal(QtCore.QThread): 
    dataSignal = QtCore.pyqtSignal([], [object], [object, object]) 
    ... 

    def _emit(self, signal, *args): 
     getattr(self, signal)[(object,) * len(args)].emit(*args) 

    def run(self): 
     while True: 
      args = self.queue.get() 
      self._emit(*args) 


class WorkerProcess(Process): 
    ... 

    def emit_to_mother(self, *args): 
     self.callback_queue.put(args) 

    def text_upper(self, text): 
     self.emit_to_mother('dataSignal', (text.upper(),)) 


class MainWin(QtGui.QMainWindow): 
    def __init__(self, parent=None): 
     ... 

     self.lineedit.returnPressed.connect(self.to_child) 
     self.callback_queue_watcher.dataSignal[object].connect(self.updateUI) 
+0

Извините за длительное время отклика. Теперь я понимаю, что вы имеете в виду. Это действительно умный способ подключения к синтаксису нового стиля, спасибо! Я бы никогда не подумал о перегрузке такого сигнала. –

+0

Однако это менее общее, что код со старыми сигналами, поскольку теперь вам нужно скопировать пасту 'signal_name = QtCore.pyqtSignal ([], [object], [object, object])' для каждого имени другого сигнала, которое вы хотите использовать. Таким образом, следующий вопрос будет: есть ли существенные преимущества сигналов нового стиля, которые бы оправдали порт? –

+0

@ThibaudRuelle. Я полагаю, что основным преимуществом является совместимость вперед - старый синтаксис вообще не поддерживается в PyQt5. Но в более общем смысле синтаксис нового стиля намного более пифонов и менее подвержен ошибкам. Очень легко получить подпись неправильно с синтаксисом старого стиля (особенно для новичков и незнакомых с C++). Хуже того, он терпит неудачу. вместо того, чтобы поднимать ошибку, как это делают сигналы нового стиля. Я также думаю, что стоит сказать, что «читаемость имеет значение»: большинство людей считают синтаксис старого стиля очень уродливым и многословным. Я совсем этого не пропустил. – ekhumoro

 Смежные вопросы

  • Нет связанных вопросов^_^