Eugene Sajine
Eugene Sajine

Reputation: 8200

Using 1 instance of QProcess in several classes (PyQt)

I have a a Main window wrapper class (Say A) and another class used in the wrapper (say B). B has a method that in turn HAD a subrocess.check_call(command) call. I'm changing it to use QProcess in order to be able to communicate with this process and display the Qprocess stdout and stderr in main window QTextEdit as well send back data to Qprocess stdin from main window QLineEdit.

for that I have:

class A(....):
   def __init__(self):
      ....
      QtCore.QObject.connect(self.ui.actionNew, QtCore.SIGNAL("triggered()", self.selectedNew)
      self.qprocess = QtCore.QProcess()
      self.connect(self.qprocess, QtCore.SIGNAL("readyReadStandardOutput()", self.readStdOut)
      self.connect(self.qprocess, QtCore.SIGNAL("readyReadStandardError()", self.readStdErr)

    def readStdOut(self):
        self.ui.text_edit.append(QtCore.QString(self.qprocess.readAllStandardOutput()))

    def readStdErr(self):
        self.ui.text_edit.append(QtCore.QString(self.qprocess.readAllStandardError()))

    def selectedNew(self:)
        ...
        newB = B(self.qprocess)
        newB.doWork(params)

 class B():
     def __init__(self, qprocess):
         self.qp = qprocess

     def doWork(params):
         ...
         # creating a thread to not block the main thread
         class ConsoleThread(threading.Thread):

             def __init__(self, qprocess):
                self.tqp = qprocess
                threading.Thread.__init__(self)

             def run(self):
                 self.qtp.execute("script_that_waits_for_user_input_during_execution")

          # starting the thread and passing it the QProcess instance from B
          ConsoleThread(self.qp).start()
          print(self.qp.state()) # this returns 0, when I expected 2, obviously something wrong

in the end the output of the "script_that_waits_for_user_input_during_execution" is not showing up in QTextEdit, but is still printed in the console. It doesn't seem that I'm getting any signals back in A and I'm not reaching A.readStdOut() method. General idea is to have a GUI app wrapping different command line scripts. So i need a way to correctly get the output from the QProcess and be able to communicate back by writing to it from GUI. Of course it could probably be less complicated if i would move the functions from B to A (would eliminate unnecessary steps) but in the same time GUI wrapper should be separate from logic i think.

Thanks!

Upvotes: 0

Views: 1502

Answers (1)

jdi
jdi

Reputation: 92569

I think you might be misunderstanding the use of a QProcess, because this example is doing a number of odd things:

  1. An instance of class A gets a QProcess created as a member, and signals connected
  2. Every time selectedNew() is called, it creates a new instance of B, passing that same QProcess instance each time.
  3. doWork is then called, which creates a local class definition every single time, only to make an instance and throw away the class. The thread calls a staticmethod on the QProcess instance which creates a new QProcess that runs freely.
  4. In doWork, you immediately check the state of the QProcess right after starting the asych thread, as if anything happened to the QProcess instance the whole time.

Mainly, you have a thread which starts a new process each time, and then you check a value that doesn't change on the original because it was never started. And the signals never get called because they are attached to that original process. To simplify this whole situation, all you need is a new QProcess every time you want to run.

First off, lets clean up that class A into something that would actually work properly:

class A():
    def __init__(self):
      ...
      self.ui.actionNew.triggered.connect(self.selectedNew)

    def readStdOut(self):
        self.ui.text_edit.append(str(self.qprocess.readAllStandardOutput()))

    def readStdErr(self):
        self.ui.text_edit.append(str(self.qprocess.readAllStandardError()))

    def selectedNew(self):
        ...
        qprocess = QtCore.QProcess(self)
        qprocess.readyReadStandardOutput.connect(self.readStdOut)
        qprocess.readyReadStandardError.connect(self.readStdErr)

        qprocess.start("script_that_waits_for_user_input", params)

        print qprocess.state() == qprocess.Running

Instead of creating the QProcess in the init, you create it on demand when you want to run something. Theny ou connect the signals of that instance to your slots, and call start on the QProcess. This will actually start the process, and call your slots.

I don't know what you intend to use class B for, unless you want to wrap command line scripts into different classes, in which case you would completely move the QProcess creation to class B, and then connect your class A signals to the b_instance.qprocess member.

Update

In order to move this QProcess responsibility down to the B class, so that you can have many different class types that perform different types of work, it might look like this:

class A(QtGui.QMainWindow):
    ...

    def readStdOut(self):
        qprocess = self.sender()
        self.ui.text_edit.append(QtCore.QString(qprocess.readAllStandardOutput()))

    def readStdErr(self):
        qprocess = self.sender()
        self.ui.text_edit.append(QtCore.QString(qprocess.readAllStandardError()))


    def selectedNew(self):
        ...
        b = B()
        b.qprocess.readyReadStandardOutput.connect(self.readStdOut)
        b.qprocess.readyReadStandardError.connect(self.readStdErr)

        b.doWork(params)

        print b.qprocess.state() == qprocess.Running


class B(QtCore.QObject):
    def __init__(self):
        ...
        self.qprocess = QtCore.QProcess(self)

    def doWork(params):
        ...
        self.qprocess.start("script_that_waits_for_user_input", params)

class A creates a new instance of B, and connects the signals of B's process to its own slots. Then it calls the doWork with the params for the command. B actually starts the thread and owns it.

Upvotes: 1

Related Questions