1

This is what I need to embedded into my GUI

I'll try to be much clear as possible.

I have a very simple test script that control a Power Supply, the script measure some current from the Agilent Power Supply + Unit Under Test, then, the script print these readings as simple as:

PS.write(b"MEAS:CURR? \n")
time.sleep(2)
response = PS.read(1000)
time.sleep(3)
print(response)
(float(response)*1)
E3632A=(float(response)*1)
print (E3632A)

When the script excecute the "print command" (print (E3632A), all the information is displayed into the "py.exe" DOS Window (C:\Windows\py.exe). Here is my question

How I can embedded this into a simple GUI? I want my GUI display the Data that py.exe is showing. that simple... I have read all post over the internet and none has a real solution to this.

arghtype
  • 4,376
  • 11
  • 45
  • 60
F Casian
  • 93
  • 1
  • 11
  • You should look into `tkinter` module – spicypumpkin Jan 18 '17 at 20:37
  • PyCrust (wxWidgets) is an excellent interactive shell that you can include in your applications. – Jean-François Fabre Jan 18 '17 at 20:40
  • I did and is not a real solution to add this window into a GUI, all the "solutions" are related to use a "subprocess" then read the subprocess output and this is not what I need. – F Casian Jan 18 '17 at 20:40
  • Thank you Jean-François Fabre but I ended with the same question ... how to embedded into my GUI. – F Casian Jan 18 '17 at 20:45
  • 1
    You might find the Tkinter-based `errorwindow.py` module in [this answer](http://stackoverflow.com/a/18091356/355230) useful (it works for `stderr` as well as `stderr`). On the other hand, the [`easygui`](http://easygui.sourceforge.net/) module may be all that you need. – martineau Jan 18 '17 at 21:23
  • @FCasian Maybe you should point out _why_ `subprocess` is not a good solution for your case. `subprocess` can very well be used to capture the output of an executable. Or maybe you just want to get rid of the executable? But we don't know that. – blubberdiblub Jan 18 '17 at 21:53
  • Hi @blubberdiblub thanks for your comment, I did the subprocess and the problem is that I can only read the output once the process is completed. Please see as following: from tkinter import * import subprocess as sub p = sub.Popen(['python.exe', 'C:\\N\\test_py'],stdout=sub.PIPE, stderr=sub.PIPE) output, errors = p.communicate() root = Tk() text = Text(root) text.pack() text.insert(END, output) root.mainloop() – F Casian Jan 18 '17 at 21:57
  • @FCasian ah, I see, so your problem is how to use `subprocess` correctly. The `communicate()` is the culprit, as that can't read the output line by line. You need to use a different technique in that case. – blubberdiblub Jan 18 '17 at 22:06
  • Hi @blubberdiblub that is correct, that is the reason that I try to find the way to embedded the console into the GUI, I mean, why do we want to find the way using subprocess and other techniques if I already have available the "console", right? is just matter to include/embedded into a GUI, that's it. – F Casian Jan 18 '17 at 23:14
  • @FCasian you should clearly point out in your question that your actual problem is to read the output from the program piece by piece (or line by line) as it comes in (as opposed to all at once) and that the program producing the output doesn't produce it all at once. Having it hidden in the comments here won't help attract contributors ;) – blubberdiblub Jan 19 '17 at 12:45
  • Hi @blubberdiblub Thank you for your feedback, I really doubt some one has a solution for this "simple" request. (Simple that I haven't been able to find the solution lol) – F Casian Jan 19 '17 at 18:14
  • No, really, it's important to improve the question, as that will considerably raise the likelihood of getting it answered. Also read http://stackoverflow.com/help/how-to-ask if you like to know more. Users are unlikely to answer a question they don't like, as the answer stands and falls with the question here on SO. Let me try my hand at an answer, maybe it encourages you to improve the question afterwards ;) – blubberdiblub Jan 19 '17 at 20:50

1 Answers1

2

Under the assumption that the process you're calling is long-running and doesn't produce all its output in one go, it means you cannot use subprocess.Popen.communicate(), as that is designed to read all output up to an end of file.

You will have to use other standard techniques to read from the pipe.

As you want to integrate it with a GUI and the process is long-running, you will need to coordinate reading its output with the GUI's main loop. This complicates things somewhat.

TkInter

Let's first assume you want to use TkInter, as in one of your examples. That confronts us with a couple of Problems:

  • There's no integration of TkInter with the select module.
  • There's even no canonical integration of TkInter with asyncio as of now (also see https://bugs.python.org/issue27546).
  • Hacking together a custom main loop using root.update() is usually recommended against, leaving us solving with threading what should have been an event based approach.
  • TkInter's event_generate() is missing Tk's ability to send user data along with the event, so we can't use TkInter events to pass the received output from one thread to another.

Thus, we will tackle it with threading (even if I'd prefer not to), where the main thread controls the Tk GUI and a helper thread reads the output from the process, and lacking a native way in TkInter to pass data around, we utilize a thread-safe Queue.

#!/usr/bin/env python3

from subprocess import Popen, PIPE, STDOUT, TimeoutExpired
from threading import Thread, Event
from queue import Queue, Empty
from tkinter import Tk, Text, END


class ProcessOutputReader(Thread):

    def __init__(self, queue, cmd, params=(),
                 group=None, name=None, daemon=True):
        super().__init__(group=group, name=name, daemon=daemon)
        self._stop_request = Event()
        self.queue = queue
        self.process = Popen((cmd,) + tuple(params),
                             stdout=PIPE,
                             stderr=STDOUT,
                             universal_newlines=True)

    def run(self):
        for line in self.process.stdout:
            if self._stop_request.is_set():
                # if stopping was requested, terminate the process and bail out
                self.process.terminate()
                break

            self.queue.put(line)  # enqueue the line for further processing

        try:
            # give process a chance to exit gracefully
            self.process.wait(timeout=3)
        except TimeoutExpired:
            # otherwise try to terminate it forcefully
            self.process.kill()

    def stop(self):
        # request the thread to exit gracefully during its next loop iteration
        self._stop_request.set()

        # empty the queue, so the thread will be woken up
        # if it is blocking on a full queue
        while True:
            try:
                self.queue.get(block=False)
            except Empty:
                break

            self.queue.task_done()  # acknowledge line has been processed


class MyConsole(Text):

    def __init__(self, parent, queue, update_interval=50, process_lines=500):
        super().__init__(parent)
        self.queue = queue
        self.update_interval = update_interval
        self.process_lines = process_lines

        self.after(self.update_interval, self.fetch_lines)

    def fetch_lines(self):
        something_inserted = False

        for _ in range(self.process_lines):
            try:
                line = self.queue.get(block=False)
            except Empty:
                break

            self.insert(END, line)
            self.queue.task_done()  # acknowledge line has been processed

            # ensure scrolling the view is at most done once per interval
            something_inserted = True

        if something_inserted:
            self.see(END)

        self.after(self.update_interval, self.fetch_lines)


# create the root widget
root = Tk()

# create a queue for sending the lines from the process output reader thread
# to the TkInter main thread
line_queue = Queue(maxsize=1000)

# create a process output reader
reader = ProcessOutputReader(line_queue, 'python3', params=['-u', 'test.py'])

# create a console
console = MyConsole(root, line_queue)

reader.start()   # start the process
console.pack()   # make the console visible
root.mainloop()  # run the TkInter main loop

reader.stop()
reader.join(timeout=5)  # give thread a chance to exit gracefully

if reader.is_alive():
    raise RuntimeError("process output reader failed to stop")

Due to the aforementioned caveats, the TkInter code ends up a bit on the larger side.

PyQt

Using PyQt instead, we can considerably improve our situation, as that framework already comes with a native way to integrate with a subprocess, in the shape of its QProcess class.

That means we can do away with threads and use Qt's native Signal and Slot mechanism instead.

#!/usr/bin/env python3

import sys

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QProcess, QTextCodec
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import QApplication, QPlainTextEdit


class ProcessOutputReader(QProcess):
    produce_output = pyqtSignal(str)

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

        # merge stderr channel into stdout channel
        self.setProcessChannelMode(QProcess.MergedChannels)

        # prepare decoding process' output to Unicode
        codec = QTextCodec.codecForLocale()
        self._decoder_stdout = codec.makeDecoder()
        # only necessary when stderr channel isn't merged into stdout:
        # self._decoder_stderr = codec.makeDecoder()

        self.readyReadStandardOutput.connect(self._ready_read_standard_output)
        # only necessary when stderr channel isn't merged into stdout:
        # self.readyReadStandardError.connect(self._ready_read_standard_error)

    @pyqtSlot()
    def _ready_read_standard_output(self):
        raw_bytes = self.readAllStandardOutput()
        text = self._decoder_stdout.toUnicode(raw_bytes)
        self.produce_output.emit(text)

    # only necessary when stderr channel isn't merged into stdout:
    # @pyqtSlot()
    # def _ready_read_standard_error(self):
    #     raw_bytes = self.readAllStandardError()
    #     text = self._decoder_stderr.toUnicode(raw_bytes)
    #     self.produce_output.emit(text)


class MyConsole(QPlainTextEdit):

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

        self.setReadOnly(True)
        self.setMaximumBlockCount(10000)  # limit console to 10000 lines

        self._cursor_output = self.textCursor()

    @pyqtSlot(str)
    def append_output(self, text):
        self._cursor_output.insertText(text)
        self.scroll_to_last_line()

    def scroll_to_last_line(self):
        cursor = self.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else
                            QTextCursor.StartOfLine)
        self.setTextCursor(cursor)


# create the application instance
app = QApplication(sys.argv)

# create a process output reader
reader = ProcessOutputReader()

# create a console and connect the process output reader to it
console = MyConsole()
reader.produce_output.connect(console.append_output)

reader.start('python3', ['-u', 'test.py'])  # start the process
console.show()                              # make the console visible
app.exec_()                                 # run the PyQt main loop

We end up with a little boilerplate deriving from the Qt classes, but with an overall cleaner approach.

General considerations

Also make sure that the process you're calling is not buffering multiple output lines, as otherwise it will still look as if the console got stuck.

In particular if the callee is a python program, you can either ensure that it's using print(..., flush=True) or call it with python -u callee.py to enforce unbuffered output.

blubberdiblub
  • 4,085
  • 1
  • 28
  • 30
  • Hi @blubberdiblub thank you so much for your help. I have installed PyQt GPL v5.6 For Python 3.5. I tried to run the script you posted in PythonWin however it popup an error "This application failed to start because it could not find or load the Qt platform plugin "windows" in "". Any idea why? Also, is the first time I use PyQt, so far, I have created a GUI (.ui) using Qt Designer, is very simple to use... and translate into py. Also, I was able to link one of the buttons into my test_script. however, the same question raise up, how to redirect the data from the console into my GUI – F Casian Jan 20 '17 at 02:27
  • I'm not familiar with installing PyQt under Windows, sorry. Do you get the same error when you run it from the command line, making sure you CD into the script directory? Anyway, maybe those have some hints: http://stackoverflow.com/questions/17695027/ - https://github.com/pyqt/python-qt5/issues/2 - http://stackoverflow.com/questions/20495620/ – blubberdiblub Jan 20 '17 at 06:48
  • I installed Python 3.5.3 x86-64 from https://www.python.org/downloads/windows/, doing a Custom Install for All Users into C:\Python35\, and having it added to the PATH Environment Variable. Then I went with the recommendation of https://www.riverbankcomputing.com/software/pyqt/download5 and installed PyQt using `pip3 install PyQt5` on the command line. I don't know if any of this made any difference, but both my example programs work out of the box here. – blubberdiblub Jan 20 '17 at 07:48
  • Hi @blubberdiblub, thank you so much, I re-install the SW into system and is working, I followed your instructions and I can open now your script. Let me dig in to, now, the only thing I need to do is to Link my test script in to your window and embedded into PyQt5. – F Casian Jan 20 '17 at 23:35
  • Hi @blubberdiblub, I make it work, now is working... Finally the console output is now into a Tk. I'll try to import now into my GUI – F Casian Jan 24 '17 at 01:59
  • Hi @blubberdiblub, Just to let you know ..... My Test Software Script is now WORKING as I want.... thank you so much for your help, so far I solve the problem I had and my project keep moving it... Thank you again for all your HELP ....!!!!!! – F Casian Jan 24 '17 at 21:27
  • @FCasian you're welcome :) Maybe you could edit your question a bit, as mentioned in the comments up there, for the benefit of other users who may have the same or a similar problem? – blubberdiblub Jan 24 '17 at 22:01
  • Hi @blubberdiblub, I just edit... Please let me know what you think.... Thanks again ...!!! – F Casian Jan 24 '17 at 22:03