Поскольку программа PyView разрабатывалась поэтапно, вам придется изучить объединение двух файлов и классов, чтобы понять, как она в действительности работает. В одном файле реализован класс, предоставляющий основные функции показа слайдов, а в другом реализован класс, расширяющий исходный и добавляющий новые функции поверх базового поведения. Начнем с класса расширения: пример 11.6 добавляет ряд функций в импортируемый базовый класс показа слайдов — редактирование примечаний, ползунок, определяющий задержку, метку для отображения имени файла и так далее. Это тот файл, который фактически запускает PyView.
Пример 11.6. PP4E\Gui\SlideShow\slideShowPlus.py
############################################################################# PyView 1.2: программа показа слайдов с прилагаемыми к ним примечаниями.
Подкласс класса SlideShow, который добавляет отображение содержимого файлов с примечаниями в прикрепляемом объекте PyEdit, ползунок для установки интервала задержки между сменами изображений и метку с именем текущего отображаемого файла изображения;
Версия 1.2 работает под управлением Python 3.x и дополнительно использует улучшенный алгоритм повторного прикрепления компонента PyEdit, чтобы обеспечить его растягиваемость, перехватывает операцию закрытия примечания в подклассе, чтобы избежать появления исключения при закрытии PyEdit, использующегося в режиме всплывающего окна или полнофункционального компонента, и вызывает метод update() перед вставкой текста во вновь прикрепленный редактор примечаний, чтобы обеспечить правильное позиционирование в первой строке (смотрите описание этой проблемы в книге).
#############################################################################
import os
from tkinter import *
from PP4E.Gui.TextEditor.textEditor import *
from slideShow import SlideShow
#from slideShow_threads import SlideShow
Size = (300, 550) # 1.2: начальные размеры, (высота, ширина)
class SlideShowPlus(SlideShow):
def __init__(self, parent, picdir, editclass, msecs=2000, size=Size) self.msecs = msecs
self.editclass = editclass
SlideShow.__init__(self, parent, picdir, msecs, size)
def makeWidgets(self):
self.name = Label(self, text=’None’, bg=’red’, relief=RIDGE)
SlideShow.makeWidgets(self)
Button(self, text=’Note’, command=self.onNote).pack(fill=X) Button(self, text=’Help’, command=self.onHelp).pack(fill=X) s = Scale(label=’Speed: msec delay’, command=self.onScale, from_=0, to=3000, resolution=50, showvalue=YES, length=400, tickinterval=250, orient=’horizontal’)
s.pack(side=BOTTOM, fill=X)
s.set(self.msecs)
# 1.2: знать о закрытии редактора необходимо, если он используется
# в режиме всплывающего окна или полнофункционального компонента self.editorGone = False
class WrapEditor(self.editclass):# расширяет PyEdit для перехвата Quit def onQuit(editor): # editor — экземпляр PyEdit
self.editorGone = True # self — вмещающий экземпляр self.editorUp = False # класса слайд—шоу self.editclass.onQuit(editor) # предотвратить рекурсию
# прикрепить фрейм редактора к окну или к фрейму слайд-шоу
if issubclass(WrapEditor, TextEditorMain): # создать объект редактора self.editor = WrapEditor(self.master) # указать корень для меню else: # встраиваемый компонент
self.editor = WrapEditor(self) # или компонент всплывающего окна self.editor.pack_forget() # скрыть редактор при запуске
self.editorUp = self.image = None
def onStart(self):
SlideShow.onStart(self)
self.config(cursor=’watch’)
def onStop(self):
SlideShow.onStop(self)
self.config(cursor=’hand2’)
def onOpen(self):
SlideShow.onOpen(self)
if self.image:
self.name.config(text=os.path.split(self.image[0])[1]) self.config(cursor=’crosshair’)
self.switchNote()
def quit(self):
self.saveNote()
SlideShow.quit(self)
def drawNext(self):
SlideShow.drawNext(self)
if self.image:
self.name.config(text=os.path.split(self.image[0])[1]) self.loadNote()
def onScale(self, value): self.msecs = int(value)
def onNote(self):
if self.editorGone: # 1.2: был уничтожен
return # не воссоздавать: видимо, он был нежелателен
if self.editorUp:
#self.saveNote() # если редактор уже открыт
self.editor.pack_forget() # сохранить текст?, скрыть редактор
self.editorUp = False
else:
# 1.2: повторно прикрепить с параметрами, управляющими
# растягиванием, иначе виджет редактора не будет
# растягиваться
# 1.2: вызвать update после прикрепления и перед вставкой текста,
# иначе текстовый курсор будет изначально помещен во 2 строку
self.editor.pack(side=TOP, expand=YES, fill=BOTH)
self.editorUp = True # или показать/прикрепить редактор self.update() # смотрите Pyedit: та же проблема с loadFirst
self.loadNote() # и загрузить текст примечания
def switchNote(self):
if self.editorUp:
self.saveNote() # сохранить примечание к текущему изображению
self.loadNote() # загрузить примечание для нового изображения
def saveNote(self):
if self.editorUp:
currfile = self.editor.getFileName() # или self.editor.onSave() currtext = self.editor.getAllText() # текст может отсутствовать if currfile and currtext:
try:
open(currfile, ‘w’).write(currtext) except:
pass # неудача является нормальным явлением при # выполнении за пределами текущего каталога
def loadNote(self):
if self.image and self.editorUp:
root, ext = os.path.splitext(self.image[0])
notefile = root + ‘.note’
self.editor.setFileName(notefile)
try:
self.editor.setAllText(open(notefile).read())
except:
self.editor.clearAllText() # примечание может отсутствовать
def onHelp(self):
showinfo(‘About PyView’,
‘PyView version 1.2\nMay, 2010\n(1.1 July, 1999)\n’
‘An image slide show\nProgramming Python 4E’) if __name__ == ‘__main__’: import sys picdir = ‘../gifs’ if len(sys.argv) >= 2: picdir = sys.argv[1]
editstyle = TextEditorComponentMinimal if len(sys.argv) == 3:
try:
editstyle = [TextEditorMain,
TextEditorMainPopup, TextEditorComponent, TextEditorComponentMinimal][int(sys.argv[2])] except: pass
root = Tk()
root.title(‘PyView 1.2 — plus text notes’)
Label(root, text=”Slide show subclass”).pack()
SlideShowPlus(parent=root, picdir=picdir, editclass=editstyle) root.mainloop()
Базовая функциональность, расширяемая классом SlideShowPlus, приводится в примере 11.7. Этот пример представляет первоначальную реализацию показа слайдов — он открывает файлы изображений, отображает их и организует показ слайдов в цикле. Его можно запустить как самостоятельный сценарий, но при этом вы не получите дополнительных функций, таких как примечания и ползунки, добавляемые подклассом SlideShowPlus.
Пример 11.7. PP4E\Gui\SlideShow\slideShow.py
######################################################################
SlideShow: простая реализация показа слайдов на Python/tkinter;
базовый набор функций, реализованных здесь, можно расширять в подклассах;
######################################################################
from tkinter import *
from glob import glob
from tkinter.messagebox import askyesno
from tkinter.filedialog import askopenfilename
import random
Size = (450, 450) # начальная высота и ширина холста
imageTypes = [(‘Gif files’, ‘.gif’), # для диалога открытия файла (‘Ppm files’, ‘.ppm’), # плюс jpg с исправлениями Tk, (‘Pgm files’, ‘.pgm’), # плюс растровые с помощью BitmapImage (‘All files’, ‘*’)]
class SlideShow(Frame):
def __init__(self, parent=None, picdir=’.’, msecs=3000, size=Size,**args) Frame.__init__(self, parent, **args) self.size = size self.makeWidgets() self.pack(expand=YES, fill=BOTH) self.opens = picdir files = []
for label, ext in imageTypes[:-1]:
files = files + glob(‘%s/*%s’ % (picdir, ext)) self.images = [(x, PhotoImage(file=x)) for x in files] self.msecs = msecs self.beep = True self.drawn = None
def makeWidgets(self): height, width = self.size self.canvas = Canvas(self, bg=’white’, height=height, width=width) self.canvas.pack(side=LEFT, fill=BOTH, expand=YES) self.onoff = Button(self, text=’Start’, command=self.onStart) self.onoff.pack(fill=X) Button(self, text=’Open’, command=self.onOpen).pack(fill=X) Button(self, text=’Beep’, command=self.onBeep).pack(fill=X) Button(self, text=’Quit’, command=self.onQuit).pack(fill=X)
def onStart(self): self.loop = True self.onoff.config(text=’Stop’, command=self.onStop) self.canvas.config(height=self.size[0], width=self.size[1]) self.onTimer()
def onStop(self): self.loop = False self.onoff.config(text=’Start’, command=self.onStart)
def onOpen(self): self.onStop() name = askopenfilename(initialdir=self.opens, filetypes=imageTypes) if name:
if self.drawn: self.canvas.delete(self.drawn) img = PhotoImage(file=name) self.canvas.config(height=img.height(), width=img.width()) self.drawn = self.canvas.create_image(2, 2, image=img, anchor=NW) self.image = name, img
def onQuit(self): self.onStop() self.update() if askyesno(‘PyView’, ‘Really quit now?’): self.quit()
def onBeep(self):
self.beep = not self.beep # toggle, or use ~ 1
def onTimer(self): if self.loop:
self.drawNext()
self.after(self.msecs, self.onTimer)
def drawNext(self):
if self.drawn: self.canvas.delete(self.drawn)
name, img = random.choice(self.images)
self.drawn = self.canvas.create_image(2, 2, image=img, anchor=NW) self.image = name, img if self.beep: self.bell() self.canvas.update()
if __name__ == ‘__main__’: import sys
if len(sys.argv) == 2:
picdir = sys.argv[1]
else:
picdir = ‘../gifs’
root = Tk()
root.title(‘PyView 1.2’)
root.iconname(‘PyView’)
Label(root, text=”Python Slide Show Viewer”).pack() SlideShow(root, picdir=picdir, bd=3, relief=SUNKEN) root.mainloop()
Чтобы вы могли получить более полное представление о том, что реализует этот базовый класс, на рис. 11.16 показано, как выглядит графический интерфейс, создаваемый этим примером, если запустить его в виде самостоятельного сценария. Здесь изображены два экземпляра, создаваемые сценарием slideShow_frames, который можно найти в дереве примеров и основная реализация которого приводится ниже:
root = Tk()
Label(root, text=”Two embedded slide shows: Frames”).pack()
SlideShow(parent=root, picdir=picdir, bd=3, relief=SUNKEN).pack(side=LEFT) SlideShow(parent=root, picdir=picdir, bd=3, relief=SUNKEN).pack(side=RIGHT) root.mainloop()
Простой сценарий slideShow_frames прикрепляет два экземпляра SlideShow к одному окну. Это возможно благодаря тому, что информация о состоянии сохраняется не в глобальных переменных, а в переменных экземпляра класса. Сценарий slideShow_toplevels (также можно найти в дереве примеров) прикрепляет два экземпляра SlideShow к двум всплывающим окнам верхнего уровня. В обоих случаях показ слайдов происходит независимо, но управляется событиями after, генерируемыми одним и тем же циклом событий в одном процессе.
Рис. 11.16. Два прикрепленных объекта SlideShow
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011