2017-01-18 15 views
1

This is what I need to embedded into my GUI Как захватить вывод долговременной программы и представить ее в графическом интерфейсе в Python?

Я постараюсь быть максимально понятным.

У меня есть очень простой тестовый сценарий, что контроль блока питания, сценарий измерить некоторые ток от Agilent питания + испытываемое устройство, а затем, сценарий печати эти показания как просто, как:

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) 

Когда сценарий отменяет команду «print» (print (E3632A), вся информация отображается в окне «py.exe» DOS (C: \ Windows \ py.exe). Вот мой вопрос

Как я может встроить это в простой графический интерфейс? Я хочу, чтобы мой графический интерфейс отображал данные, которые показывает py.exe. Это просто ... Я прочитал все сообщения через Интернет, и ни у кого нет реального решения к этому.

+0

Вы должны изучить модуль 'tkinter' – spicypumpkin

+0

PyCrust (wxWidgets) - отличная интерактивная оболочка, которую вы можете включить в свои приложения. –

+0

Я сделал и не являюсь реальным решением для добавления этого окна в графический интерфейс, все «решения» связаны с использованием «подпроцесса», а затем считывают вывод подпроцесса, и это не то, что мне нужно. –

ответ

2

В предположении, что процесс, который вы вызываете, длительный и не производит весь его вывод за один раз, это означает, что вы не можете использовать subprocess.Popen.communicate(), так как он предназначен для чтения всего вывода до конца файл.

Вам нужно будет использовать другие стандартные методы для считывания с трубы.

Как вы хотите интегрировать его с графическим интерфейсом, и процесс длительный, вам нужно будет координировать чтение его вывода с помощью основного цикла GUI. Это несколько усложняет ситуацию.

TkInter

Давайте сначала предположим, что вы хотите использовать TkInter, как в одном из ваших примеров. Это ставит перед нами несколько проблем:

  • Там нет интеграции TkInter с выбрать модуль.
  • В настоящее время даже нет канонической интеграции TkInter с asyncio (см. Также https://bugs.python.org/issue27546).
  • Обычно рекомендуется использовать обычную основную петлю с использованием root.update(), что позволяет нам решать проблемы с потоками, что должно было быть основано на событиях.
  • У TkInter's event_generate() отсутствует способность Tk отправлять пользовательские данные вместе с событием, поэтому мы не можем использовать события TkInter для передачи полученного результата из одного потока в другой.

Таким образом, мы будем решать с резьб (даже если я предпочел бы не), где основной поток управляет Tk GUI и вспомогательный поток считывает выход из процесса, и не хватает родной в TkInter для передачи данных, мы используем поточно-безопасную Очередь.

#!/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") 

Благодаря вышеупомянутым предостережений, код Tkinter заканчивается немного на большей стороне.

PyQt

Использование PyQt вместо этого, мы можем значительно улучшить ситуацию, так как эта структура уже поставляется с родным способом интеграции с подпроцесс, в форме его QProcess класса.

Это означает, что мы можем сделать прочь с потоками и использовать механизм в Qt родной сигнала и Slot вместо этого.

#!/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 

Мы в конечном итоге с небольшим шаблонным вытекающим из классов Qt, но с общим подходом пылесоса.

Общие соображения

Также убедитесь, что процесс вы вызываете не буферные несколько выходных линий, так как в противном случае он все равно будет выглядеть, как будто консоль застряла.

В частности, если вызываемая программа представляет собой программу python, вы можете либо убедиться, что она использует print(..., flush=True), либо вызвать ее с помощью python -u callee.py для обеспечения небуферизованного вывода.

+0

Hi @blubberdiblub благодарит вас за вашу помощь. Я установил PyQt GPL v5.6 для Python 3.5. Я попытался запустить сценарий, который вы отправили в PythonWin, однако он выдает сообщение об ошибке «Это приложение не запускалось, потому что оно не могло найти или загрузить плагин платформы Qt« windows »в« ». Любая идея почему? Также, это первый раз, когда я использовать PyQt, до сих пор я создал GUI (.ui) с помощью Qt Designer, очень прост в использовании ... и переводил в py. Кроме того, мне удалось связать одну из кнопок с моим test_script. тот же вопрос поднимается, как перенаправить данные с консоли в мой графический интерфейс –

+0

Я не знаком с установкой PyQt под Windows, извините. Вы получаете ту же ошибку, когда вы запускаете ее из командной строки, следя за тем, чтобы CD в каталог сценариев? В любом случае, возможно, у них есть некоторые подсказки: http://stackoverflow.com/questions/17695027/ - https://github.com/pyqt/python-qt5/issues/2 - http://stackoverflow.com/questions/20495620/ – blubberdiblub

+0

Я установил Python 3.5.3 x86-64 с https://www.python.org/downloads/windows/, выполнив пользовательскую установку fo r Все пользователи в C: \ Python35 \ и добавили его в переменную среды PATH. Затем я пошел с рекомендацией https://www.riverbankcomputing.com/software/pyqt/download5 и установил PyQt, используя 'pip3 install PyQt5' в командной строке. Я не знаю, было ли какое-либо из этих изменений, но оба моих примера работают здесь. – blubberdiblub