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!

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

7 Comments

Thank you very much for elaborate answer! i have two questions:
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?
Just added another example of how to move the process functionality down to B
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...
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.