GuiStreams: перенаправление потоков данных в виджеты
Следующий прием программирования графических интерфейсов. В ответ на проблему, поставленную в конце предыдущего раздела, сценарий в примере 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:
# диалог для ввода каждой строки
# кнопка 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.
Возможно, эта картина не настолько захватывающая, чтобы на нее можно было смотреть часами, но она отражает автоматическое отображение файловых операций ввода-вывода в виджеты графического интерфейса — как будет показано чуть ниже, это решает большую часть последней проблемы, обозначенной в предыдущем разделе.
Но прежде чем двинуться дальше, необходимо отметить, что реализованный в этом модуле вызов функции, для которой выполняется перенаправление потоков ввода-вывода, а также его цикл чтения вывода по-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 10.8. Сценарий guiStreams направляет потоки во всплывающие окна
|
|
рожденной команды оболочки, могут быть заблокированы — они не возвращают управление циклу событий графического интерфейса, пока не завершится функция или запущенная команда оболочки. И хотя класс GuiOutput предусматривает вызов метода update из библиотеки tkinter после записи каждой строки, тем не менее в целом этот модуль не имеет возможности контролировать продолжительность выполнения функций или команд, которые он запускает.
В функции redirectedGuiShellCmd, например, вызов метода input.read— line будет вызывать задержку, пока от порожденной программы не будет принята полная выходная строка, и графический интерфейс в течение этого времени не будет откликаться на действия пользователя. Поскольку объект вывода output вызывает метод update, изображение на экране будет обновляться в процессе выполнения программы (метод update немедленно вызывает цикл событий Tk), но не чаще, чем будут приниматься строки от порожденной программы. Кроме того, из-за наличия цикла в этой функции графический интерфейс полностью подчиняет себя запущенной им команде, пока она не завершится.
Вызовы функций в redirectedGuiFunc также подвержены подобным блокировкам. Кроме того, на протяжении всего времени работы вызываемой функции графический интерфейс обновляется не чаще, чем функция будет выводить данные. Иными словами, эта упрощенная блокирующая модель может стать источником проблем в крупных графических интерфейсах. Мы еще вернемся к этой теме далее, когда встретимся с потоками выполнения. А пока данная реализация вполне соответствует нашим текущим целям.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011