Работа с публичным репозиторием; события в TkInter
Объявления
Проекту Strace нужны толковые студенты в Google Summer of Code 2021
Толковые школьники могут принять участие в апробации курса по PyGame (пишите в ТГ-личку)
DVCS — не только для хранения и версионирования, но и для организации взаимодействия
- Публикация исходников и истории (в т. ч. коммитов и веток)
Обмен патчами (в случае format-patch/am это, считай, обмен коммитами)
Замечание: GIT не предоставляет канала обмена информацией. Это может быть email или мессенджер, движок сайта, почтовый голубь…
Удалённый репозиторий
Простая схема с одним публичным репозиторием:
Цикл «pull → commit * N → push»
Кстати, pull = fetch + merge
git remote -v / .git/config
⇒ можно организовать конвейер, когда fetch ≠ push
Проблема первоначального создания удалённого репозитория
- Простой вариант (на самом деле сложный): нажать кнопочку в веб-интерфейсе
Сложный вариант (на самом деле простой): доступ по ssh
Коротко про ssh и ключи
Пример (не забыть про bare)
У GitHub для этого есть CLI-утилита
- (GH и парольный доступ)
Работа с несколькими удалёнными репозиториями:
- + Upstream
- + Коллеги
- (ЧСХ — все read-only, кроме вашего публичного)
Еще про tkinter
Tk: общие переменные
Например, optionmenu и label
События и их обработчики:
Стандартные действия (command=)
- Регистрация обработчиков
- Правила диспетчеризации
Вброс события с помощью event_generate()
Фокус и диспетчеризация событий клавиатуры
Пример:
- Дополнительный код
1 def create_widgets(self): 2 '''Create all the widgets''' 3 self.B = tk.Button(self, text="Quit", command=self.master.quit) 4 self.B.bind('<Any-KeyPress>', lambda event: print(event)) 5 self.master.bind('<Any-KeyPress>', lambda event: print("Root", event)) 6 self.B.grid() 7 self.E = tk.Button(self, text="Event", command=lambda: self.event_generate('<KeyPress-Return>')) 8 self.E.grid()
Если кнопка «Quit» не в фокусе, нажатие на Event приводит к получению события корневым окном
Если кнопка «Quit» в фокусе, событие получает и она, и корневое окно
по-видимому, очередь событий в tkinter одна и диспетчеризация не зависит от того, через какой интерфейс событие вброшено: в примере вы воспользовались Application, а не не «Quit» и не корневым окном (в которое Application вложено)
Ввод текста entry:
Поле ввода как текстовый редактор (курсор, вставка, удаление, видимость текста в случае, когда он шире окна, и т. п.)
не нравится мудрить, вот вам StringVar
Особенность: для того, чтобы функция/метод воспринимался TCL как собственный обработчик, его необходимо обернуть в TCL-код с помощью.register()
Указывается событие и что передавать
Пример (дополнительный код):
1 class App(Application):
2 def create_widgets(self):
3 alpha = self.register(self.alpha)
4 self.S = tk.StringVar()
5 self.E = tk.Entry(self, textvariable=self.S,
6 validate='all', validatecommand=(alpha, '%V', '%S'))
7 self.E.grid(columnspan=2)
8 self.L = tk.Label(self, text="Lower letters only")
9 self.L.grid(row=1, column=0)
10 self.Q = tk.Button(self, text="Quit", command=self.master.quit)
11 self.Q.grid(row=1, column=1)
12
13 def alpha(self, why, txt):
14 print(f"{why}: '{txt}'")
15 return why != 'key' or txt.isalpha() and txt.islower()
Если событие связано с редактированием Entry, действие разрешено только если удаляется/вставляется прописная буква.
Д/З
Прочитать (про события и Entry) TODO
(необязательная) Задача-головоломка. Напишите класс Application(tk.Frame) таким образом, чтобы приведённый ниже код создавал приложение с интерфейсной моделью как на картинке (цвета воспроизводить не надо)
- Код:
1 class App(Application): 2 def createWidgets(self): 3 self.message = "Congratulations!\nYou've found a sercet level!" 4 self.F1(tk.LabelFrame, "1:0", text="Frame 1") 5 self.F1.B1(tk.Button, "0:0/NW", text="1") 6 self.F1.B2(tk.Button, "0:1/NE", text="2") 7 self.F1.B3(tk.Button, "1:0+1/SEW", text="3") 8 self.F2(tk.LabelFrame, "1:1", text="Frame 2") 9 self.F2.B1(tk.Button, "0:0/N", text="4") 10 self.F2.B2(tk.Button, "0+1:1/SEN", text="5") 11 self.F2.B3(tk.Button, "1:0/S", text="6") 12 self.Q(tk.Button, "2.0:1.2/SE", text="Quit", command=self.quit) 13 self.F1.B3.bind("<Any-Key>", lambda event: showinfo(self.message.split()[0], self.message)) 14 15 app = App(title="Sample application") 16 app.mainloop()
- Интерфейс
- Особенности:
Конструктором виджета является первое обращение к нему по имени, все последующие обращения по этому имени просто выдают сам объект-виджет
- Первый параметр — тип виджета
- Второй параметр — геометрия (см. далее)
- Остальные параметры — именные параметры для создания виджета данного типа
Виджет A.B встроен в виджет A.
Геометрия B указывается относительно A
В геометрии также указывается вес (эластичность, weight) рядов и колонок. Это значение наследуется от последнего заданного в данном ряду/колонке виджета (по умолчанию 1)
(в примере первые три кнопки встроены в F1, а следующие три — в F2, соответственно задана и геометрия)
Обращение к уже заданному виджету работает как обычно (возвращается сам объект-виджет)
- Синтаксис геометрии:
ряд.вес+высота:колонка.вес+ширина/гравитация
ряд — виджет.grid(… row=ряд …)
.вес (необязательное поле) — виджет.master.rowconfigure(ряд, weight=вес)
- по умолчанию = 1
+высота (необязательное поле) — grid(… rowspan=высота+1 …)
- по умолчанию = 0 (⇒ rowspan = 1, виджет занимает один ряд)
для колонка.вес+ширина — соответственно
/гравитация (необязательное поле) — виджет.grid(… sticky=гравитация …)
по умолчанию "NEWS"
Как я решал головоломку. Я сделал ешё чуть более головоломной!
Переписал __getattr__() (его всё равно надо переписывать) у Applicattion таким образом, чтобы обращение к отсутствующему полю возвращало конструктор
В конструкторе делал производный класс от типа виджета, в котором тоже переписал __getattr__(), но так, чтобы на несуществующее поле он возвращал либо поле master-объекта Application, если оно есть, либо конструктор объекта оттуда
- Разница только в том, какой виджет является master-ом
Да, setattr/getattr(объект, "A.B.C") отлично работает ☺
В результате Tk видит иерархию виджетов, а в действительности все они — атрибуты объекта Application
Это сильно помогает программно найти виджет по имени
- Возможно, я перемудрил ☺
- Код:
(если головоломку делать неохота; задача простая, но немного исследовательская) Реализовать класс InputLabel(tk.Label), для простейшего редактирования строк в виджете Label (такая дешёвая пластиковая имитация Entry). Должно поддерживаться
- Требования и свойства
- Разрешённый фокус и рамка фокуса по умолчанию
- Ввод печатных символов
- Текстовый курсор
- Перемещение курсора стрелками и Home/End
- Перемещение курсора и получение фокуса кликом мыши
- Удаление символа перед курсором
- Как реализовать курсор?
В Label, как и в любой другой виджет, можно встраивать ещё виджеты
Я встраивал Frame с правильным стилем и толщиной рамки
Для позиционирования курсора внутри Label использовать .place() (а не .grid())
Я не придумал, как вычислить ширину подстроки, поэтому используйте моноширинный шрифт и просто захардкодьте высоту и ширину одного знакоместа
у меня зашло …font="fixed", вам, возможно, придётся поупражняться с названием шрифта
- Требования и свойства
- Решать можно любую из задач (или обе, если интересно).
В зарегистрированном вами репозитории создать подкаталог 04_PublicRepositoryEvents (совпадает с финальной частью URL данной лекции), всё, что вы написали, положить туда
Решение головоломки (первой задачи) должно иметь название Simplified.py
Решение задачи на события (второй) должно иметь название LabelEdit.py