2016-08-17 10 views
1

私は実験的なセットアップのためにPyQt GUIを作成しています。これは計算上重い操作を必要とするため、マルチプロセッシングモジュールに基づいたアーキテクチャを目指しており、this answerからインスパイアされています。任意の署名で新しいスタイルのPyQtシグナルを出す

QMainWindowは、メインプロセスに指示を返送するメインプロセスと共有「コールバック」キューから命令を取得するために、個々の「タスク」キューと

  1. 子プロセスを作成します
  2. QThreadへQMainWindowのスロットに接続された信号に翻訳する

この例では、任意の記号self.emit(QtCore.SIGNAL(signature), args)を持つ古いスタイルの信号を使用しています。 私の質問は:新しいスタイルの信号でこの機能を再現することは可能ですか?

私はthis questionthis oneを認識しています。しかし、子プロセスの1つから受け取ったシグネチャに応じて、異なる名前のスロットに接続したいので、一般的なオブジェクトでシグナルを発信するのは、私のニーズに合っていません。ここ

は、作業コード(単純化のためにメインウィンドウ内の唯一の子プロセスと一つのスロットが存在するが、完成したコード内のいくつかがあります注意してください)である:

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

なぜ接続が署名に依存することがありますか?リンクした質問で示唆されているように、定義済みの信号を1つだけ使用し、識別子を最初のパラメータとして送信します。 – ekhumoro

+0

その場合、シグナルを使用する利点が失われます。たとえば、プログラムの実行中に特定の信号を特定のスロットに接続したり切断したりすることはできません。しかし、私はあなたのポイントを見逃しているかもしれません? –

+0

新しいスタイルの信号でダイナミックに発光するカスタム信号を複製する方法はありません。カスタム信号は、クラス属性としてあらかじめ定義されている必要があります。しかし、これが実行時に信号の接続や切断に影響する理由は何もありません。また、これがあなたの実際の質問とどのように関連しているかもわかりません。コード例でシグナルが切断されることはありません。 – ekhumoro

答えて

1

new-style signal and slot syntax信号であることを必要としますQObjectから継承するクラスのクラス属性として事前定義されています。クラスがインスタンス化されると、インスタンスのバウンドシグナルオブジェクトが自動的に作成されます。 bound-signalオブジェクトには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([オブジェクト]、[オブジェクト、オブジェクト])'というペーストをコピーする必要があるためです。つかいます。フォローアップの質問には、ポートを正当化する新しいスタイルの信号に大きな利点があるのでしょうか? –

+0

@ ThibaudRuelle。私は主な利点は前方互換性だと思う - 古い構文はPyQt5では全くサポートされていない。しかし、より一般的には、新しいスタイルの構文ははるかにpythonicであり、エラーを起こしにくいです。古いスタイルの構文(特に初心者やC++になじみのない人)のシグネチャを間違えることは非常に簡単です。さらに悪いことに、それは黙って失敗する。新しいスタイルのシグナルのようなエラーを引き起こすのではなく、私はまた、「可読性が重要です」と言っておく価値があると考えています。ほとんどの人は、古いスタイルの構文が非常に醜く、冗長であると感じています。私はそれをまったく見逃さない。 – ekhumoro

関連する問題