0

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!

Eugene Sajine
  • 8,104
  • 3
  • 23
  • 28

1 Answers1

1

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.

jdi
  • 90,542
  • 19
  • 167
  • 203
  • Thank you very much for elaborate answer! i have two questions: – Eugene Sajine Aug 24 '12 at 14:11
  • 1. The class B is a program that is wrapping some infrastructure related set up steps. It was a script that took user input and was performing several different tasks by calling subprocesses i.e. executing different scripts and programs. Now the user input is substituted with the gui for my script, but underlying ones are still expecting some user interaction. I don't want to move all B functionality into the A class. 2. Could you please advise how can i connect A signals to B_instance.qprocess to be able to read/write to it? – Eugene Sajine Aug 24 '12 at 14:23
  • Just added another example of how to move the process functionality down to `B` – jdi Aug 24 '12 at 17:24
  • Again thanks for awesome info - it is almost working! one thing: After I start the process in Class B, i said self.qprocess.waitForFinished() in order to let the underlying program to go through all the bonanza, so my script can continue. I get the finished signal, when the underlying script asks user for confirmation to proceed! I would like to be able to start the underlying script and when it asks for confirmation i would like to be able to answer, so it can proceed or cancel. (underlying script is not mine - can't edit). It seems it is just timimg out though... – Eugene Sajine Aug 24 '12 at 19:52
  • First off, if you call `waitForFinished`, it is going to block your whole application in the main thread. How do you plan on communicating with the process in the first place? Is your main UI element reading the stdout, and then you have some other line edit input that that will write to the process? You can't wait on the process. You need to use the signaling and the reading of the output. Then you can call `writeData()` on the process to send back user input. – jdi Aug 24 '12 at 20:13
  • Also, I just updated the second example to clarify how you would need to adjust your SLOTs. They can check `self.sender()` to get the specific QProcess that emitted the signal. And your class B inherits from `QObject` – jdi Aug 24 '12 at 20:19
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/15779/discussion-between-eugene-sajine-and-jdi) – Eugene Sajine Aug 24 '12 at 20:22