Исходный программный код PyDraw

ishodnyj programmnyj kod pydraw Примеры законченных программ с графическим интерфейсом

Как и PyEdit, программа PyDraw размещается в одном файле. За главным модулем, представленным в примере 11.8, приводятся два расширения, изменяющие реализацию перемещения.

Пример 11.8. PP4E\Gui\MovingPics\movingpics.py

############################################################################## PyDraw 1.1: простая программа рисования на холсте и перемещения объектов с воспроизведением анимационного эффекта.

В реализации перемещения объектов используются циклы time.sleep, поэтому в каждый момент времени может перемещаться только один объект; перемещение выполняется плавно и быстро, однако далее приведены подклассы, реализующие другие режимы перемещения на основе метода widget.after и потоков выполнения. Версия 1.1 была дополнена возможностью выполнения под управлением Python 3.X (версия 2.X не поддерживается) ############################################################################## helpstr = “””—PyDraw версия 1.1-Операции, выполняемые мышью:

подпись: левая= Начальная точка рисования

Левая+Перемещение = Рисовать новый объект

Двойной щелчок левой = Удалить все объекты

Правая = Переместить текущий объект

Средняя = Выбрать ближайший объект

Средняя+Перемещение = Перетащить текущий объект

подпись: с=выбрать цвет
s=выбрать задержку при перемещении г=рисовать прямоугольники
a=рисовать дуги
1=поднять объект
f=выполнить заливку объекта
p=добавить фотографию
Keyboard commands:

w=Выбрать ширину рамки u= Выбрать шаг перемещения о=Рисовать овалы 1=Рисовать линии d=Удалить объект 2=Опустить объект Ь=Выполнить заливку фона z=Сохранить в формате Postscript x=Выбрать режим рисования

?=Справка другие=стереть текст

import time, sys

from tkinter import *

from tkinter.filedialog import * from tkinter.messagebox import * PicDir = ‘../gifs’ if sys.platform[:3] == ‘win’:

HelpFont = (‘courier’, 9, ‘normal’) else:

HelpFont = (‘courier’, 12, ‘normal’)

pickDelays = [0.01, 0.025, 0.05, 0.10, 0.25, 0.0, 0.001, 0.005]

pickUnits = [1, 2, 4, 6, 8, 10, 12]

pickWidths = [1, 2, 5, 10, 20]

pickFills = [None,’white’,’blue’,’red’,’black’,’yellow’,’green’,’purple’]

pickPens = [‘elastic’, ‘scribble’, ‘trails’]

class MovingPics:

def __init__(self, parent=None):

canvas = Canvas(parent, width=500, height=500, bg= ‘white’)

canvas.pack(expand=YES, fill=BOTH)

canvas.bind(‘<ButtonPress-1>’, self.onStart)

canvas.bind(‘<B1-Motion>’, self.onGrow)

canvas.bind(‘<Double-1>’, self.onClear)

canvas.bind(‘<ButtonPress-3>’, self.onMove)

canvas.bind(‘<Button-2>’, self.onSelect)

canvas.bind(‘<B2-Motion>’, self.onDrag) parent.bind(‘<KeyPress>’, self.onOptions)

self.createMethod = Canvas.create_oval

self.canvas = canvas

self.moving = []

self.images = []

self.object = None

self.where = None

self.scribbleMode = 0

parent.title(‘PyDraw Moving Pictures 1.1’) parent.protocol(‘WM_DELETE_WINDOW’, self.onQuit) self.realquit = parent.quit

self.textInfo = self.canvas.create_text(

5, 5, anchor=NW, font=HelpFont, text=’Press ? for help’)

def onStart(self, event): self.where = event self.object = None

def onGrow(self, event): canvas = event.widget if self.object and pickPens[0] == ‘elastic’: canvas.delete(self.object)

self.object = self.createMethod(canvas,

self.where.x, self.where.y, # начало event.x, event.y, # конец

fill=pickFills[0], width=pickWidths[0])

if pickPens[0] == ‘scribble’: self.where = event # нач. координаты для следующей итерации

def onClear(self, event):

if self.moving: return # если идет перемещение event.widget.delete(‘all’) # использовать тег all self.images = []

self.textInfo = self.canvas.create_text(

5, 5, anchor=NW, font=HelpFont, text=’Press ? for help’)

def plotMoves(self, event):

diffX = event.x self.where.x # план анимированного перемещения diffY = event.y self.where.y # по горизонтали, затем по вертикали reptX = abs(diffX) // pickUnits[0] # приращение на шаге, число шагов reptY = abs(diffY) // pickUnits[0] # от предыдущего до текущего щелчка incrX = pickUnits[0] * ((diffX > 0) or -1) # 3.x требуется деление // incrY = pickUnits[0] * ((diffY > 0) or -1) # с усечением

return incrX, reptX, incrY, reptY

def onMove(self, event):

traceEvent(‘onMove’, event, 0) # переместить объект в точку щелчка

object = self.object # игнорировать некоторые

if object and object not in self.moving: # операции при движении

msecs = int(pickDelays[0] * 1000)

parms = ‘Delay=%d msec, Units=%d’ % (msecs, pickUnits[0]) self.setTextInfo(parms)

self.moving.append(object)

canvas = event.widget

incrX, reptX, incrY, reptY = self.plotMoves(event)

for i in range(reptX):

canvas.move(object, incrX, 0)

canvas.update()

time.sleep(pickDelays[0])

for i in range(reptY):

canvas.move(object, 0, incrY)

canvas.update() # update выполнит другие операции

time.sleep(pickDelays[0]) # приостановить до следующего шага

self.moving.remove(object)

if self.object == object: self.where = event

def onSelect(self, event): self.where = event self.object = self.canvas.find_closest(event.x, event.y)[0] # кортеж

def onDrag(self, event):

diffX = event.x self.where.x # OK, если объект перемещается

diffY = event.y self.where.y # переместить в новом направлении

self.canvas.move(self.object, diffX, diffY) self.where = event

def onOptions(self, event): keymap = {

‘w’: lambda self: self.changeOption(pickWidths, ‘Pen Width’),

‘c’: lambda self: self.changeOption(pickFills, ‘Color’),

‘u’: lambda self: self.changeOption(pickUnits, ‘Move Unit’),

‘s’: lambda self: self.changeOption(pickDelays, ‘Move Delay’),

‘x’: lambda self: self.changeOption(pickPens, ‘Pen Mode’),

‘o’: lambda self: self.changeDraw(Canvas.create_oval, ‘Oval’),

‘r’: lambda self: self.changeDraw(Canvas.create_rectangle, ‘Rect’),

‘l’: lambda self: self.changeDraw(Canvas.create_line, ‘Line’),

‘a’: lambda self: self.changeDraw(Canvas.create_arc, ‘Arc’),

‘d’: MovingPics.deleteObject,

‘1’: MovingPics.raiseObject,

‘2’: MovingPics.lowerObject, # если только 1 схема вызова

f’: MovingPics.fillObject, # использовать несвязанные методы

b’: MovingPics.fillBackground, # иначе передавать self в lambda

‘p’: MovingPics.addPhotoItem,

‘z’: MovingPics.savePostscript,

‘?’: MovingPics.help}

try:

keymap[event.char](self)

except KeyError:

self.setTextInfo(‘Press ? for help’)

def changeDraw(self, method, name): self.createMethod = method # несвязанный метод объекта Canvas self.setTextInfo(‘Draw Object=’ + name)

def changeOption(self, list, name):

list.append(list[0])

del list[0]

self.setTextInfo(‘%s=%s’ % (name, list[0]))

def deleteObject(self):

if self.object != self.textInfo: # ok если объект перемещается self.canvas.delete(self.object) # стереть, но движение продолжится self.object = None

def raiseObject(self):

if self.object: # ok если объект перемещается

self.canvas.tkraise(self.object) # поднять в процессе перемещения

def lowerObject(self):

if self.object:

self.canvas.lower(self.object)

def fillObject(self):

if self.object:

type = self.canvas.type(self.object)

if type == ‘image’: pass

elif type == ‘text’:

self.canvas.itemconfig(self.object, fill=pickFills[0]) else:

self.canvas.itemconfig(self.object, fill=pickFills[0], width=pickWidths[0])

def fillBackground(self):

self.canvas.config(bg=pickFills[0])

def addPhotoItem(self):

if not self.where: return

filetypes=[(‘Gif files’, ‘.gif’), (‘All files’, ‘*’)] file = askopenfilename(initialdir=PicDir, filetypes=filetypes) if file:

image = PhotoImage(file=file) # загрузить изображение

self.images.append(image) # сохранить ссылку

self.object = self.canvas.create_image( # на холст,

self.where.x, self.where.y, # в точку image=image, anchor=NW) # посл. щелчка

def savePostscript(self):

file = asksaveasfilename() if file:

self.canvas.postscript(file=file) # сохранить холст в файл

def help(self):

self.setTextInfo(helpstr)

#showinfo(‘PyDraw’, helpstr)

def setTextInfo(self, text):

self.canvas.dchars(self.textInfo, 0, END) self.canvas.insert(self.textInfo, 0, text) self.canvas.tkraise(self.textInfo)

def onQuit(self):

if self.moving:

self.setTextInfo(“Can’t quit while move in progress”) else:

self.realquit() # стандартная операция закрытия окна: сообщит # об ошибке, если выполняется перемещение

def traceEvent(label, event, fullTrace=True): print(label) if fullTrace:

for atrr in dir(event):

if attr[:2] != ‘__’: print(attr, ‘=>’, getattr(event, attr))

if __name__ == ‘__main__’:

from sys import argv # когда выполняется как сценарий,

if len(argv) == 2: PicDir = argv[1] # ‘..’ не действует при запуске из

# другого каталога

root = Tk() # создать и запустить объект

MovingPics(root) # MovingPics

root.mainloop()

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

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

Пример 11.9. PP4E\Gui\MovingPics\movingpics_after.py

PyDrawafter: простая программа рисования на холсте и перемещения объектов с воспроизведением анимационного эффекта.

Для реализации перемещения объектов используются циклы на основе метода widget. after, благодаря чему оказалось возможным организовать одновременное перемещение нескольких объектов без применения потоков выполнения; движение осуществляется параллельно, но медленнее, чем в версии с использованием time.sleep; смотрите также пример canvasDraw в обзоре: он конструирует и передает сразу весь список incX/incY: здесь могло бы быть allmoves = ([(incrX, 0)] * reptX) + ([(0, incrY)] * reptY)

from movingpics import *

class MovingPicsAfter(MovingPics):

def doMoves(self, delay, objectId, incrX, reptX, incrY, reptY): if reptX:

self.canvas.move(objectId, incrX, 0) reptX -= 1 else:

self.canvas.move(objectId, 0, incrY) reptY -= 1

if not (reptX or reptY):

self.moving.remove(objectId)

else:

self.canvas.after(delay,

self.doMoves, delay, objectId, incrX, reptX, incrY, reptY)

def onMove(self, event):

traceEvent(‘onMove’, event, 0)

object = self.object # переместить текущий объект в точку щелчка if object:

msecs = int(pickDelays[0] * 1000)

parms = ‘Delay=%d msec, Units=%d’ % (msecs, pickUnits[0]) self.setTextInfo(parms) self.moving.append(object)

incrX, reptX, incrY, reptY = self.plotMoves(event) self.doMoves(msecs, object, incrX, reptX, incrY, reptY) self.where = event

if __name__ == ‘__main__’: from sys import argv # когда выполняется как сценарий

if len(argv) == 2:

import movingpics # глобальная перем. не из этого модуля

movingpics.PicDir = argv[1] # а from* не связывает имена

root = Tk()

MovingPicsAfter(root) root.mainloop()

Чтобы оценить работу этого примера, распахните окно сценария на весь экран и создайте несколько объектов на его холсте, нажимая клавишу p после предварительного щелчка, чтобы вставить картинки, нарисуйте несколько фигур и так далее. Теперь, когда уже выполняется одно или несколько перемещений, можно запустить перемещение еще одного объекта, щелкнув на нем средней кнопкой и затем правой кнопкой в том месте, куда требуется его переместить. Перемещение начинается немедленно, даже если на холсте присутствуют другие движущиеся объекты. Запланированные события after всех объектов помещаются в одну и ту же очередь цикла событий и передаются библиотекой tkinter после срабатывания таймера настолько быстро, насколько возможно.

Если запустить этот модуль подкласса непосредственно, то можно заметить, что перемещение не такое плавное и быстрое, как первоначально (в зависимости от быстродействия вашего компьютера и наличия дополнительных программных уровней под Python), зато одновременно может выполняться несколько перемещений.

В примере 11.10 демонстрируется, как обеспечить параллельное перемещение нескольких объектов с помощью потоков. Этот прием действует, но, как отмечалось в главах 9 и 10, обновление графического интерфейса в дочерних потоках выполнения является, вообще говоря, опасным делом. На моей машине перемещение в этом сценарии с потоками происходит не так плавно, как в первоначальной версии, что отражает накладные расходы, связанные с переключением интерпретатора (и ЦП) между несколькими потоками, но, опять же, во многом это зависит от быстродействия компьютера.

Пример 11.10. PP4E\Gui\MovingPics\movingpics_threads.py

PyDrawthreads: использует потоки для перемещения объектов; прекрасно работает в Windows, если не вызывать метод canvas.update() в потоках (иначе сценарий будет завершаться с фатальными ошибками, некоторые объекты будут начинать движение сразу после того как будут нарисованы, и так далее); имеется как минимум несколько методов холста, которые могут вызываться из потоков выполнения; движение осуществляется менее плавно, чем с применением time.sleep, и данная реализация более опасна в целом: внутри потоков лучше ограничиться изменением глобальных переменных и никак не касаться графического интерфейса;

import _thread as thread, time, sys, random

from tkinter import Tk, mainloop

from movingpics import MovingPics, pickUnits, pickDelays

class MovingPicsThreaded(MovingPics):

def __init__(self, parent=None):

MovingPics.__init__(self, parent)

self.mutex = thread.allocate_lock()

import sys

#sys.setcheckinterval(0) # переключение контекста после каждой

# операции виртуальной машины: не поможет

def onMove(self, event): object = self.object if object and object not in self.moving: msecs = int(pickDelays[0] * 1000) parms = ‘Delay=%d msec, Units=%d’ % (msecs, pickUnits[0]) self.setTextInfo(parms)

#self.mutex.acquire() self.moving.append(object) #self.mutex.release() thread.start_new_thread(self.doMove, (object, event))

def doMove(self, object, event): canvas = event.widget incrX, reptX, incrY, reptY = self.plotMoves(event) for i in range(reptX):

canvas.move(object, incrX, 0)

#  canvas.update()

time.sleep(pickDelays[0]) # может измениться

for i in range(reptY):

canvas.move(object, 0, incrY)

#  canvas.update() # update выполняет другие операции

time.sleep(pickDelays[0]) # приостановиться до следующего шага #self.mutex.acquire() self.moving.remove(object)

if self.object == object: self.where = event #self.mutex.release()

if __name__ == ‘__main__’: root = Tk()

MovingPicsThreaded(root) mainloop()

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

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