События
Обычно, чтобы приложение с графическим интерфейсом что-то делало, должны происходить те или иные события, чаще всего представляющие собой воздействие человека на элементы GUI.
Можно выделить три основных типа событий: производимые мышью, нажатиями клавиш на клавиатуре, а также события, возникающие в результате изменения виджетов. Нередко обрабатываются сочетания. Например, клик мышью с зажатой клавишей на клавиатуре.
При вызове метода bind событие передается в качестве первого аргумента.
Название события заключается в кавычки, а также в угловые скобки < и >. События описывается с помощью зарезервированных ключевых слов.
Часто используемые события, производимые мышью:
- – клик левой кнопкой мыши
- – клик средней кнопкой мыши
- – клик правой кнопкой мыши
- – двойной клик левой кнопкой мыши
- – движение мыши
- и т. д.
from tkinter import * def b1(event): root.title("Левая кнопка мыши") def b3(event): root.title("Правая кнопка мыши") def move(event): x = event.x y = event.y s = "Движение мышью <>x<>".format(x, y) root.title(s) root = Tk() root.minsize(width=500, height=400) root.bind('', b1) root.bind('', b3) root.bind('', move) root.mainloop()
В этой программе меняется надпись в заголовке главного окна в зависимости от того, двигается мышь, щелкают левой или правой кнопкой.
Событие ( Event ) – это один из объектов tkinter . У событий есть атрибуты, как и у многих других объектов. В примере в функции move извлекаются значения атрибутов x и y объекта event , в которых хранятся координаты местоположения курсора мыши в пределах виджета, по отношению к которому было сгенерировано событие. В данном случае виджетом является главное окно, а событием – , т. е. перемещение мыши.
В программе ниже выводится информация об экземпляре Event и некоторым его свойствам. Все атрибуты можно посмотреть с помощью команды dir(event) . У разных событий они одни и те же, меняются только значения. Для тех или иных событий часть атрибутов не имеет смысла, такие свойства имеют значения по умолчанию.
В примере хотя обрабатывается событие нажатия клавиши клавиатуры, в поля x , y , x_root , y_root сохраняются координаты положения на экране курсора мыши.
from tkinter import * def event_info(event): print(type(event)) print(event) print(event.time) print(event.x_root) print(event.y_root) root = Tk() root.bind('a', event_info) root.mainloop()
Пример выполнения программы:
Для событий с клавиатуры буквенные клавиши можно записывать без угловых скобок (например, ‘a’ ).
Для неалфавитных клавиш существуют специальные зарезервированные слова. Например, — нажатие клавиши Enter , — пробел. (Заметим, что есть событие , которое не имеет отношения к нажатию клавиши Enter , а происходит, когда курсор заходит в пределы виджета.)
from tkinter import * def enter_leave(event): if event.type == '7': event.widget['text'] = 'In' elif event.type == '8': event.widget['text'] = 'Out' root = Tk() lab1 = Label(width=20, height=3, bg='white') lab1.pack() lab1.bind('', enter_leave) lab1.bind('', enter_leave) lab2 = Label(width=20, height=3, bg='black', fg='white') lab2.pack() lab2.bind('', enter_leave) lab2.bind('', enter_leave) root.mainloop()
В ней две метки используют одну и ту же функцию, и каждая метка использует эту функцию для обработки двух разных событий: ввода курсора в пределы виджета и вывода во границы.
Функция, в зависимости от того, по отношению к какому виджету было зафиксировано событие, изменяет свойства только этого виджета. Как изменяет, зависит от произошедшего события.
Свойство event.widget содержит ссылку на виджет, сгенерировавший событие. Свойство event.type описывает, что это было за событие. У каждого события есть имя и номер. С помощью выражения print(repr(event.type)) можно посмотреть его полное описание. При этом на одних платформах str(event.type) возвращает имя события (например, ‘Enter’), на других – строковое представление номера события (например, ‘7’).
Вернемся к событиям клавиатуры. Сочетания клавиш пишутся через тире. В случае использования так называемого модификатора, он указывается первым, детали на третьем месте. Например, — одновременное нажатие клавиш Shift и стрелки вверх, – движение мышью с зажатой левой кнопкой и клавишей Ctrl .
from tkinter import * def exit_win(event): root.destroy() def to_label(event): t = ent.get() lbl.configure(text=t) def select_all(event): def select_all2(widget): widget.selection_range(0, END) widget.icursor(END) # курсор в конец root.after(10, select_all2, event.widget) root = Tk() ent = Entry(width=40) ent.focus_set() ent.pack() lbl = Label(height=3, fg='orange', bg='darkgreen', font="Verdana 24") lbl.pack(fill=X) ent.bind('', to_label) ent.bind('', select_all) root.bind('', exit_win) root.mainloop()
Здесь сочетание клавиш Ctrl+a выделяет текст в поле. Без root.after() выделение не работает. Метод after выполняет функцию, указанную во втором аргументе, через промежуток времени, указанный в первом аргументе. В третьем аргументе передается значение атрибута widget объекта event . В данном случае им будет поле ent . Именно оно будет передано как аргумент в функцию select_all2 и присвоено параметру widget .
Практическая работа
Напишите программу по описанию. Размеры многострочного текстового поля определяются значениями, введенными в однострочные текстовые поля. Изменение размера происходит при нажатии мышью на кнопку, а также при нажатии клавиши Enter .
Цвет фона экземпляра Text светлосерый ( lightgrey ), когда поле не в фокусе, и белый, когда имеет фокус.
Событие получения фокуса обозначается как , потери – как .
Для справки: фокус перемещается по виджетам при нажатии Tab , Ctrl+Tab , Shift+Tab , а также при клике по ним мышью (к кнопкам последнее не относится).
Курс с примерами решений практических работ: pdf-версия
Tkinter. Программирование GUI на Python
Метод bind
В tkinter с помощью метода bind между собой связываются виджет, событие и действие. Например, виджет – кнопка, событие – клик по ней левой кнопкой мыши, действие – отправка сообщения. Другой пример: виджет – текстовое поле, событие – нажатие Enter , действие – получение текста из поля методом get для последующей обработки программой. Действие оформляют как функцию или метод, которые вызываются при наступлении события.
Один и тот же виджет можно связать с несколькими событиями. В примере ниже используется одна и та же функция-обработчик, однако могут быть и разные:
from tkinter import * root = Tk() def change(event): b['fg'] = "red" b['activeforeground'] = "red" b = Button(text='RED', width=10, height=3) b.bind('', change) b.bind('', change) b.pack() root.mainloop()
Здесь цвет текста на кнопке меняется как при клике по ней (событие ), так и при нажатии клавиши Enter (событие ). Однако Enter сработает, только если кнопка предварительно получила фокус. В данном случае для этого надо один раз нажать клавишу Tab . Иначе нажатие Enter будет относиться к окну, но не к кнопке.
У функций-обработчиков, которые вызываются через bind , а не через свойство command , должен быть обязательный параметр event , через который передается событие. Имя event – соглашение, идентификатор может иметь другое имя, но обязательно должен стоять на первом месте в функции, или может быть вторым в методе:
from tkinter import * root = Tk() class RedButton: def __init__(self): self.b = Button(text='RED', width=10, height=3) self.b.bind('', self.change) self.b.pack() def change(self, event): self.b['fg'] = "red" self.b['activeforeground'] = "red" RedButton() root.mainloop()
Что делать, если в функцию надо передать дополнительные аргументы? Например, клик левой кнопкой мыши по метке устанавливает для нее один шрифт, а клик правой кнопкой мыши – другой. Можно написать две разные функции:
from tkinter import * root = Tk() def font1(event): l['font'] = "Verdana" def font2(event): l['font'] = "Times" l = Label(text="Hello World") l.bind('', font1) # ЛКМ l.bind('', font2) # ПКМ l.pack() root.mainloop()
Но это не совсем правильно, так как код тела функций фактически идентичен, а имя шрифта можно передавать как аргумент. Лучше определить одну функцию:
… def changeFont(event, font): l['font'] = font …
Однако возникает проблема, как передать дополнительный аргумент функции в метод bind ? Ведь в этот метод мы передаем объект-функцию, но не вызываем ее. Нельзя написать l.bind(», changeFont(event, «Verdana»)) . Потому что как только вы поставили после имени функции скобки, значит вызвали ее, то есть заставили тело функции выполниться. Если в функции нет оператора return , то она возвращает None . Поэтому получается, что даже если правильно передать аргументы, то в метод bind попадет None , но не объект-функция.
На помощь приходят так называемые анонимные объекты-функции Python, которые создаются инструкцией lambda . Применительно к нашей программе выглядеть это будет так:
… l.bind('', lambda e, f="Verdana": changeFont(e, f)) l.bind('', lambda e, f="Times": changeFont(e, f)) …
Лямбда-функции можно использовать не только с методом bind , но и опцией command , имеющейся у ряда виджет. Если функция передается через command , ей не нужен параметр event . Здесь обрабатывается только одно основное событие для виджета – клик левой кнопкой мыши.
У меток нет command , однако это свойство есть у кнопок:
from tkinter import * def change_font(font): label['font'] = font root = Tk() label = Label(text="Hello World") label.pack() Button(command= lambda f="Verdana": change_font(f))\ .pack() Button(command= lambda f="Times": change_font(f))\ .pack() root.mainloop()
Практическая работа
Напишите программу по следующему описанию. Нажатие Enter в однострочном текстовом поле приводит к перемещению текста из него в список (экземпляр Listbox ). При двойном клике ( ) по элементу-строке списка, она должна копироваться в текстовое поле.
Курс с примерами решений практических работ: pdf-версия
Tkinter. Программирование GUI на Python