GuiStreams: перенаправление потоков данных в виджеты

guistreams perenapravlenie potokov dannyh v vidzhety Приемы программирования графических интерфейсов

Следующий прием программирования графических интерфейсов. В ответ на проблему, поставленную в конце предыдущего раздела, сценарий в примере 10.12 организует отображение входных и выходных потоков во всплывающие окна приложения, применяя практически тот же способ, который мы использовали со строками в темах, связанных с перенаправлением потоков ввода-вывода в главе 3. Хотя этот модуль служит лишь базовым прототипом и сам нуждается в усовершенствовании (например, для ввода каждой входной строки отображается новый диалог — не самое эргономичное решение), тем не менее он демонстрирует идею в целом.

Объекты этого модуля, GuiOutput и GuiInput, определяют методы, позволяющие им маскироваться под файлы везде, где ожидаются настоящие файлы. Как мы узнали в главе 3, это осуществляется с помощью средств доступа к стандартным потокам ввода-вывода, таких как встроенные функции print и input, и явных вызовов read и write. Типичные случаи использования обслуживаются в этом модуле двумя высокоуровневыми интерфейсами:

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

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

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

Пример 10.12. PP4E\Gui\Tools\guiStreams.py

############################################################################## начальная реализация классов, похожих на файлы, которые можно использовать для перенаправления потоков ввода и вывода в графические интерфейсы; входные данные поступают из стандартного диалога (единый интерфейс вывод+ввод или постоянное поле Entry для ввода были бы удобнее); кроме того, некорректно берутся строки в запросах входных данных, когда количество байтов > 1еп(строки); в Guilnput можно было бы добавить методы __iter__/__next__, для поддержки итераций по строкам, как в файлах, но это способствовало бы порождению большого количества всплывающих окон;

##############################################################################

from tkinter import *

from tkinter.simp1edia1og import askstring

from tkinter.scro11edtext import Scro11edText # или PP4E.Gui.Tour.scro11edtext

c1ass GuiOutput:

font = (‘courier’, 9, ‘normal’) # в класседля всех, self для одного

def __init__(se1f, parent=None):

self.text = None

if parent: self.popupnow(parent) # сейчас или при первой записи

def popupnow(self, parent=None): # сейчас в родителе, Toplevel потом

if self.text: return

self.text = ScrolledText(parent or Toplevel())

self.text.config(font=self.font)

self.text.pack()

def write(self, text):

self.popupnow()

self.text.insert(END, str(text))

self.text.see(END)

self.text.update() # обновлять после каждой строки

def writelines(self, lines): # строки уже включают ‘\n’

for line in lines: self.write(line) # или map(self.write, lines)

class GuiInput:

def __init__(self):

self.buff = ‘’

def inputLine(self):

line = askstring(‘GuiInput’, ‘Enter input line + <crlf> (cancel=eof)’) if line == None:


return


else


return line + ‘\n’


# диалог для ввода каждой строки

# кнопка cancel означает eof

# иначе добавить символ ‘\n’


 


def read(self, bytes=None) if not self.buff:

self.buff = self.inputLine()

 

 

 

if bytes:

#

читать по счетчику

байтов,

text = self.buff[:bytes]

self.buff = self.buff[bytes:]

#

чтобы не захватить

лишние строки

else:

 

 

 

text = ‘’

line = self.buff while line:

#

читать до eof

 

text = text + line line = self.inputLine()

#

до cancel=eof=’’

 

return text

 

def readline(self):

text = self.buff or self.inputLine() # имитировать методы чтения файла self.buff = ‘’ return text

def readlines(self):

lines = [] # читать все строки

while True:

next = self.readline()

if not next: break

lines.append(next) return lines

def redirectedGuiFunc(func, *pargs, **kargs):


import sys

saveStreams = sys.stdin, sys.stdout sys.stdin = GuiInput() sys.stdout = GuiOutput() sys.stderr = sys.stdout result = func(*pargs, **kargs) sys.stdin, sys.stdout = saveStreams return result

def redirectedGuiShellCmd(command): import os

input = os.popen(command, ‘r’) output = GuiOutput()

def reader(input, output):

while True:

line = input.readline() if not line: break output.write(line) reader(input, output)

if __name__ == ‘__main__’:

def makeUpper():

while True:

#   отображает потоки функции

#   во всплывающие окна

#   выводит диалог при необходимости

#   новое окно для каждого вызова

#   это блокирующий вызов

#   показать стандартный вывод

#   команды оболочки в новом

#   окне с виджетом Text;

#   вызов readline может

#   блокироваться

#   код самотестирования

#   использовать стандартные потоки

#   ввода-вывода



try:

line = input(‘Line? ‘) except:

break

print(line.upper())

print(‘end of file’)

def makeLower(input, output): # использовать файлы

while True:

line = input.readline() if not line: break output.write(line.lower()) print(‘end of file’)

root = Tk()

Button(root, text=’test streams’,

command=lambda: redirectedGuiFunc(makeUpper)).pack(fill=X) Button(root, text=’test files ‘,

command=lambda: makeLower(GuiInput(), GuiOutput()) ).pack(fill=X) Button(root, text=’test popen ‘,

command=lambda: redirectedGuiShellCmd(‘dir *’)).pack(fill=X) root.mainloop()

Класс GuiOutput прикрепляет объект ScrolledText (из стандартной библиотеки Python) либо к указанному родительскому контейнеру, либо выводит новое окно верхнего уровня, которое должно служить контейнером, при первом обращении к методу записи. Класс GuiInput выводит новый стандартный диалог ввода каждый раз, когда метод read запрашивает новую входную строку. Ни одна из этих схем не будет оптимальной для всех ситуаций (ввод лучше было бы отобразить на более долгоживущий виджет), но они доказывают правильность общей идеи.

На рис. 10.8 изображена картина, создаваемая реализацией самотестирования этого сценария, после перехвата вывода команды оболочки dir в Windows (слева) и двух интерактивных проверок цикла (окно с приглашениями «Lineи прописными буквами представляет работу теста makeUpper перенаправления потоков). Диалог ввода выведен для демонстрации нового теста интерфейса файлов makeLower.

Возможно, эта картина не настолько захватывающая, чтобы на нее можно было смотреть часами, но она отражает автоматическое отображение файловых операций ввода-вывода в виджеты графического интерфейса — как будет показано чуть ниже, это решает большую часть последней проблемы, обозначенной в предыдущем разделе.

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


Directory

 

test streams

 

Рис. 10.8. Сценарий guiStreams направляет потоки во всплывающие окна

 

подпись: 04/03/2010
04/03/2010
04/03/2010
04/03/2010
04/03/2010
подпись: 6,773 guimaker.py
b,708 guimaker.pyc
подпись: test files
подпись: test popen


 

рожденной команды оболочки, могут быть заблокированы — они не возвращают управление циклу событий графического интерфейса, пока не завершится функция или запущенная команда оболочки. И хотя класс GuiOutput предусматривает вызов метода update из библиотеки tkinter после записи каждой строки, тем не менее в целом этот модуль не имеет возможности контролировать продолжительность выполнения функций или команд, которые он запускает.

В функции redirectedGuiShellCmd, например, вызов метода input.readline будет вызывать задержку, пока от порожденной программы не будет принята полная выходная строка, и графический интерфейс в течение этого времени не будет откликаться на действия пользователя. Поскольку объект вывода output вызывает метод update, изображение на экране будет обновляться в процессе выполнения программы (метод update немедленно вызывает цикл событий Tk), но не чаще, чем будут приниматься строки от порожденной программы. Кроме того, из-за наличия цикла в этой функции графический интерфейс полностью подчиняет себя запущенной им команде, пока она не завершится.

Вызовы функций в redirectedGuiFunc также подвержены подобным блокировкам. Кроме того, на протяжении всего времени работы вызываемой функции графический интерфейс обновляется не чаще, чем функция будет выводить данные. Иными словами, эта упрощенная блокирующая модель может стать источником проблем в крупных графических интерфейсах. Мы еще вернемся к этой теме далее, когда встретимся с потоками выполнения. А пока данная реализация вполне соответствует нашим текущим целям.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий