Как сделать окно с кнопкой

Обновлено: 29.04.2024

В этом уроке мы узнаем, как разрабатывать графические пользовательские интерфейсы, с помощью разбора некоторых примеров графического интерфейса Python с использованием библиотеки Tkinter.

Библиотека Tkinter установлена в Python в качестве стандартного модуля, поэтому нам не нужно устанавливать что-либо для его использования. Tkinter — очень мощная библиотека. Если вы уже установили Python, можете использовать IDLE, который является интегрированной IDE, поставляемой в Python, эта IDE написана с использованием Tkinter. Звучит круто!

Мы будем использовать Python 3.7 поэтому, если вы все еще используете Python 2.x, настоятельно рекомендуем перейти на Python 3.x, если вы не в курсе нюансов изменения языка, с целью, чтобы вы могли настроить код для запуска без ошибок.

Давайте предположим, что у вас уже есть базовые знания по Python, которые помогут понять что мы будем делать.
Мы начнем с создания окна, в котором мы узнаем, как добавлять виджеты, такие, как кнопки, комбинированные поля и т. д. После этого поэкспериментируем со своими свойствами, поэтому предлагаю начать.

Создание своего первого графического интерфейса

Для начала, следует импортировать Tkinter и создать окно, в котором мы зададим его название:

Обучение Python GUI (уроки по Tkinter)

Результат будет выглядеть следующим образом:
Прекрасно! Наше приложение работает.
Последняя строка вызывает функцию mainloop . Эта функция вызывает бесконечный цикл окна, поэтому окно будет ждать любого взаимодействия с пользователем, пока не будет закрыто.

В случае, если вы забудете вызвать функцию mainloop , для пользователя ничего не отобразится.

Создание виджета Label

Чтобы добавить текст в наш предыдущий пример, мы создадим lbl , с помощью класса Label , например:

Затем мы установим позицию в окне с помощью функции grid и укажем ее следующим образом:

Полный код, будет выглядеть следующим образом:

Обучение Python GUI (уроки по Tkinter)

И вот как будет выглядеть результат:
Если функция grid не будет вызвана, текст не будет отображаться.

Настройка размера и шрифта текста

Вы можете задать шрифт текста и размер. Также можно изменить стиль шрифта. Для этого передайте параметр font таким образом:

Обучение Python GUI (уроки по Tkinter)

Обратите внимание, что параметр font может быть передан любому виджету, для того, чтобы поменять его шрифт, он применяется не только к Label .

Отлично, но стандартное окно слишком мало. Как насчет настройки размера окна?

Настройка размеров окна приложения

Мы можем установить размер окна по умолчанию, используя функцию geometry следующим образом:

В приведенной выше строке устанавливается окно шириной до 400 пикселей и высотой до 250 пикселей.

Попробуем добавить больше виджетов GUI, например, кнопки и посмотреть, как обрабатывается нажатие кнопок.

Добавление виджета Button

Начнем с добавления кнопки в окно. Кнопка создается и добавляется в окно так же, как и метка:

Наш код будет выглядеть вот так:

Обучение Python GUI (уроки по Tkinter)

Результат будет следующим:
Обратите внимание, что мы помещаем кнопку во второй столбец окна, что равно 1. Если вы забудете и поместите кнопку в том же столбце, который равен 0, он покажет только кнопку.

Изменение цвета текста и фона у Button

Вы можете поменять цвет текста кнопки или любого другого виджета, используя свойство fg .
Кроме того, вы можете поменять цвет фона любого виджета, используя свойство bg .

Обучение Python GUI (уроки по Tkinter)

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

Кнопка Click

Для начала, мы запишем функцию, которую нужно выполнить при нажатии кнопки:

Затем мы подключим ее с помощью кнопки, указав следующую ​​функцию:

Обратите внимание: мы пишем clicked , а не clicked() с круглыми скобками. Теперь полный код будет выглядеть так:

Обучение Python GUI (уроки по Tkinter)

При нажатии на кнопку, результат, как и ожидалось, будет выглядеть следующим образом:
Круто!

Получение ввода с использованием класса Entry (текстовое поле Tkinter)

В предыдущих примерах GUI Python мы ознакомились со способами добавления простых виджетов, а теперь попробуем получить пользовательский ввод, используя класс Tkinter Entry (текстовое поле Tkinter).
Вы можете создать текстовое поле с помощью класса Tkinter Entry следующим образом:

Затем вы можете добавить его в окно, используя функцию grid .
Наше окно будет выглядеть так:

Во-первых, вы можете получить текст ввода, используя функцию get . Мы можем записать код для выбранной функции таким образом:

Если вы нажмете на кнопку — появится текст «Привет » вместе с введенным текстом в виджете записи. Вот полный код:

Обучение Python GUI (уроки по Tkinter)

Запустите вышеуказанный код и проверьте результат:
Прекрасно!

Каждый раз, когда мы запускаем код, нам нужно нажать на виджет ввода, чтобы настроить фокус на ввод текста, но как насчет автоматической настройки фокуса?

Установка фокуса виджета ввода

Здесь все очень просто, ведь все, что нам нужно сделать, — это вызвать функцию focus :

Когда вы запустите свой код, вы заметите, что виджет ввода в фокусе, который дает возможность сразу написать текст.

Отключить виджет ввода

Чтобы отключить виджет ввода, отключите свойство состояния:

Обучение Python GUI (уроки по Tkinter)

Теперь вы не сможете ввести какой-либо текст.

Добавление виджета Combobox

Чтобы добавить виджет поля с выпадающем списком, используйте класс Combobox из ttk следующим образом:

Затем добавьте свои значения в поле со списком.

Обучение Python GUI (уроки по Tkinter)

Как видите с примера, мы добавляем элементы combobox , используя значения tuple .
Чтобы установить выбранный элемент, вы можете передать индекс нужного элемента текущей функции.
Чтобы получить элемент select , вы можете использовать функцию get вот таким образом:

Добавление виджета Checkbutton (чекбокса)

С целью создания виджета checkbutton , используйте класс Checkbutton :

Кроме того, вы можете задать значение по умолчанию, передав его в параметр var в Checkbutton :

Обучение Python GUI (уроки по Tkinter)

Посмотрите на результат:

Установка состояния Checkbutton

Здесь мы создаем переменную типа BooleanVar , которая не является стандартной переменной Python, это переменная Tkinter, затем передаем ее классу Checkbutton , чтобы установить состояние чекбокса как True в приведенном выше примере.

Вы можете установить для BooleanVar значение false, что бы чекбокс не был отмечен.
Так же, используйте IntVar вместо BooleanVar и установите значения 0 и 1.

Эти примеры дают тот же результат, что и BooleanVar .

Добавление виджетов Radio Button

Чтобы добавить radio кнопки, используйте класс RadioButton :

Обратите внимание, что вы должны установить value для каждой radio кнопки с уникальным значением, иначе они не будут работать.

Обучение Python GUI (уроки по Tkinter)

Результатом вышеприведенного кода будет следующий:
Кроме того, вы можете задать command любой из этих кнопок для определенной функции. Если пользователь нажимает на такую кнопку, она запустит код функции.
Вот пример:

Получение значения Radio Button (Избранная Radio Button)

Чтобы получить текущую выбранную radio кнопку или ее значение, вы можете передать параметр переменной и получить его значение.

Обучение Python GUI (уроки по Tkinter)

Каждый раз, когда вы выбираете radio button, значение переменной будет изменено на значение кнопки.

Добавление виджета ScrolledText (текстовая область Tkinter)

Чтобы добавить виджет ScrolledText , используйте класс ScrolledText :

Здесь нужно указать ширину и высоту ScrolledText , иначе он заполнит все окно.

Обучение Python GUI (уроки по Tkinter)

Результат:

Настройка содержимого Scrolledtext

Используйте метод insert , чтобы настроить содержимое Scrolledtext :

Удаление/Очистка содержимого Scrolledtext

Чтобы очистить содержимое данного виджета, используйте метод delete :

Чтобы показать всплывающее окно с помощью Tkinter, используйте messagebox следующим образом:

Обучение Python GUI (уроки по Tkinter)

Когда вы нажмете на кнопку, появится информационное окно.

Показ диалоговых окон с выбором варианта

Если вы кликнете OK, yes или retry, значение станет True, а если выберете no или cancel, значение будет False.
Единственной функцией, которая возвращает одно из трех значений, является функция askyesnocancel ; она возвращает True/False/None.

Добавление SpinBox (Виджет спинбокс)

Для создания виджета спинбокса, используйте класс Spinbox :

Таким образом, мы создаем виджет Spinbox , и передаем параметры from и to , чтобы указать диапазон номеров.
Кроме того, вы можете указать ширину виджета с помощью параметра width :

Проверим пример полностью:

Обучение Python GUI (уроки по Tkinter)

Вы можете указать числа для Spinbox , вместо использования всего диапазона следующим образом:

Виджет покажет только эти 3 числа: 3, 8 и 11.

Задать значение по умолчанию для Spinbox

В случае, если вам нужно задать значение по умолчанию для Spinbox, вы можете передать значение параметру textvariable следующим образом:

Теперь, если вы запустите программу, она покажет 36 как значение по умолчанию для Spinbox.

Добавление виджета Progressbar

Чтобы создать данный виджет, используйте класс progressbar :

Установите значение progressbar таким образом:

Вы можете установить это значение на основе любого процесса или при выполнении задачи.

Изменение цвета Progressbar

Изменение цвета Progressbar немного сложно. Сначала нужно создать стиль и задать цвет фона, а затем настроить созданный стиль на Progressbar. Посмотрите следующий пример:

Обучение Python GUI (уроки по Tkinter)

И в результате вы получите следующее:

Добавление поля загрузки файла

Для добавления поля с файлом, используйте класс filedialog :

После того, как вы выберете файл, нажмите “Открыть”; переменная файла будет содержать этот путь к файлу. Кроме того, вы можете запросить несколько файлов:

Указание типа файлов (расширение фильтра файлов)

Возможность указания типа файлов доступна при использовании параметра filetypes , однако при этом важно указать расширение в tuples.

Вы можете запросить каталог, используя метод askdirectory :

Вы можете указать начальную директорию для диалогового окна файла, указав initialdir следующим образом:

Добавление панели меню

Для добавления панели меню, используйте класс menu :

Сначала мы создаем меню, затем добавляем наш первый пункт подменю. Вы можете добавлять пункты меню в любое меню с помощью функции add_cascade() таким образом:

Наш код будет выглядеть так:

Обучение Python GUI (уроки по Tkinter)

Таким образом, вы можете добавить столько пунктов меню, сколько захотите.

Обучение Python GUI (уроки по Tkinter)

Теперь мы добавляем еще один пункт меню “Изменить” с разделителем меню. Вы можете заметить пунктирную линию в начале, если вы нажмете на эту строку, она отобразит пункты меню в небольшом отдельном окне.

Можно отключить эту функцию, с помощью tearoff подобным образом:

Просто отредактируйте new_item , как в приведенном выше примере и он больше не будет отображать пунктирную линию.
Вы так же можете ввести любой код, который работает, при нажатии пользователем на любой элемент меню, задавая свойство команды.

Добавление виджета Notebook (Управление вкладкой)

Для удобного управления вкладками реализуйте следующее:

  • Для начала, создается элемент управления вкладкой, с помощью класса Notebook .
  • Создайте вкладку, используя класс Frame .
  • Добавьте эту вкладку в элемент управления вкладками.
  • Запакуйте элемент управления вкладкой, чтобы он стал видимым в окне.

Обучение Python GUI (уроки по Tkinter)

Таким образом, вы можете добавлять столько вкладок, сколько нужно.

Добавление виджетов на вкладку

После создания вкладок вы можете поместить виджеты внутри этих вкладок, назначив родительское свойство нужной вкладке.

Обучение Python GUI (уроки по Tkinter)

Добавление интервала для виджетов (Заполнение)

Вы можете добавить отступы для элементов управления, чтобы они выглядели хорошо организованными с использованием свойств padx и pady .

Передайте padx и pady любому виджету и задайте значение.

Это очень просто!

В этом уроке мы увидели много примеров GUI Python с использованием библиотеки Tkinter. Так же рассмотрели основные аспекты разработки графического интерфейса Python. Не стоит на этом останавливаться. Нет учебника или книги, которая может охватывать все детали. Надеюсь, эти примеры были полезными для вас.


Привет!

Мои знания в создании какого-либо графического интерфейса до недавних пор были нулевыми. Поэтому было принято решение немного пошерстить просторы Интернета и сотворить какое-нибудь окошко с какими-нибудь кнопками, при нажатии которых будет что-то происходить. Возможно, в этом тексте кто-то найдет ответы на возникшие когда-то вопросы; в то же время я надеюсь на серьезную и грамотную критику.

Что будет происходить?

Так как цель у меня — разобраться с JavaFX, нужно придумать какую-то функцию у нашего окна. Идея такая: создадим набор людей (предположим, что они работают в какой-то компании), для каждого из которых будет известно имя, возраст, зарплата и семейное положение. Наше окно, во-первых, будет выводить информацию о каждом из них, во-вторых, изменять зарплату какого-либо сотрудника, в-третьих, иметь возможность добавлять нового сотрудника, и, наконец, в-четверных, будет способно фильтровать сотрудников в зависимости от значений их атрибутов. В результате должно получиться что-то по типу этого:


Начинаем

Перво-наперво создаем главный класс Company, откуда будет происходить запуск приложения:


В этом классе будем расширять возможности класса Application. Из него нам нужен главным образом метод start(), в котором будет происходить основное действие (будем пропускать ошибки, возникающие в этом методе; другими словами, если что-то пойдет не так, наше окно не закроется, но в консоли появится StackTrace ошибки):
Отлично. Для того, чтобы работать с представителями сотрудников компании, создадим класс User: объекты этого класса и будут выступать в роли сотрудников.

Создаем

Пока при запуске приложение у нас не происходит ничего. Чтобы появилось хотя бы окошечко, его необходимо добавить. Это и сделаем.


Что мы только что сделали: мы добавили сцену (scene) размером , дали ей название «Company». На нашей сцене присутствует группа root, которая по своей сути является «контейнером», который будет содержать в себе все кнопки.

Добавим выпадающий список с нашими сотрудниками. Создадим HBox, в который положим наш выпадающий список ComboBox userBox и кнопку, которая будет выдавать информацию о сотруднике (сам HBox поместим в VBox):



HBox — это набор кнопок, текстов, полей для ввода, каждый объект которого расположен последовательно горизонтально. Аналогичный ему, VBox является тем же самым, но хранит свои объекты (children) по вертикали. Мы будем использовать следующую схему: создатим VBox, в который будем класть несколько HBox'ов, в каждый из которых положим последовательность кнопок и полей.

ComboBox — это сам просто выпадающий список.

Теперь мы хотим создать кнопку, которая будет выдавать информацию о выбранном сотруднике. Добавим саму кнопку (Button) и текстовое поле (TextField), в которое будет выводиться текст. Эти два объекта добавим в HBox:



Но пока эта кнопка ничего не делает. Чтобы она что-то делала, необходимо ей присвоить действие:



Дальнейшие действия

Абсолютно аналогичным образом добавляем еще два HBox'а: во втором будет происходить изменение зарплаты, в третьем — добавляние нового сотрудника. В силу того, что логика здесь абсолютно та же самая, пропустим объяснения этих моментов и сразу покажем результат:


Фильтры

Теперь добавим «фильтрацию» сотрудников по признакам. Опять же, списки для фильтров будем добавлять с использованием ComboBox по той же самой логике.



Класс Company (главный)
Класс User

Что это было

Сама по себе программа не представляет никакой полезности и была написана исключительно для того, чтобы познакомиться с JavaFX и ее инструментами. В некотрых местах, например, при изменении зарплаты, не учитываются варианты, когда не выбран пользователь, или когда введена строка: в эти случаях будет появляться ошибка. То же самое касается полей, куда вводится информация при добавлении новых сотрудников. И, наконец, ошибка будет возникать, если не выбрано какое-то значение в любом из выпадающих списков. При желании, каждый может добавить свой «обход» этих моментов.

При написании фильтров, кстати, я использовал небезызвестные StreamAPI и -выражения, которые сокращают код, добавляют в него легкость понимания и свой шарм.

Вёрстка таких окон сначала кажется простой задачей. Модальные окна можно сделать даже без помощи JS только лишь с помощью CSS, но на практике они оказываются неудобными, и из-за маленьких недочетов модальные окна раздражают посетителей сайта.

В итоге было задумано сделать собственное простое решение.


Вообще говоря, есть несколько готовых скриптов, JavaScript библиотек, реализующих функционал модальных окон, например:

  • Arctic Modal,
  • jquery-modal,
  • iziModal,
  • Micromodal.js,
  • tingle.js,
  • Bootstrap Modal (из библиотеки Bootstrap) и др.

(в статье не рассматриваем решения на базе Frontend-фреймворков)

Несколькими из них я пользовался сам, но почти у всех находил какие-то недостатки. Некоторые из них требуют подключения библиотеки jQuery, которая есть не на всех проектах. Для разработки своего решения, нужно сначала определиться с требованиями.

Что мы ждём от модальных окон? Отвечая на этот вопрос, я основывался на докладе «Знакомьтесь, модальное окно» Анны Селезнёвой, а так-же на относительно старой статье NikoX «arcticModal — jQuery-плагин для модальных окон».

Итак, чтобы нам хотелось видеть?

Дисклеймер: Прежде чем мы рассмотрим подробности, сразу дам ссылку на готовый код получившейся библиотеки (HystModal) на GitHub, а также ссылку на демо+документацию.

Начнём с разметки.

1. Разметка HTML и CSS

1.1. Каркас модальных окон

Как открыть окно быстро? Самое простое решение: разместить всю разметку модального окна сразу в HTML странице. Затем скрывать/показывать это окно при помощи переключения классов CSS.

Набросаем такую разметку HTML (я назвал этот скрипт «hystmodal»):

Сделаем так, чтобы .hystmodal растягивался на всё окно браузера и закрывал собой содержимое страницы. Чтобы этого добиться, установим фиксированное позиционирование в CSS и приравняем свойства top, bottom, left и right к нулю.

В этом коде сделаны ещё две вещи:

  1. Так как мы хотим центрировать окно внутри страницы, превращаем .hystmodal в flex-контейнер с выравниваем его потомков по центру по вертикали и горизонтали.
  2. Окно может быть больше высоты экрана браузера, поэтому мы устанавливаем overflow-y: auto , чтобы при переполнении возникала полоса прокрутки. Также, для сенсорных экранов (в основном для Safari) нам стоит установить свойство -webkit-overflow-scrolling: touch , чтобы сенсорная прокрутка работала именно на этом блоке а не на странице.

Теперь установим стили для самого окна.

Кажется возникли сложности.

Проблема №1. Если высота окна больше высоты окна браузера, то контент окна будет обрезан сверху.


Это возникает из-за свойства justify-content: center . Оно позволяет нам удобно выровнять потомков по основной оси (по вертикали), но если потомок становится больше родителя то часть его становится недоступной даже при прокручиваемом контейнере. Подробнее можно посмотреть на stackoverflow. Решение – установить justify-content: flex-start , а потомку установить margin:auto . Это выровняет его по центру.

Проблема №2. В ie-11 если высота окна больше высоты окна браузера, то фон окна обрезается.

Решение: мы можем установить flex-shrink:0 потомку – тогда обрезки не происходит.

Проблема №3. В браузерах кроме Chrome нет отступа от нижней границы окна (т.е. padding-bottom не сработал).

Сложно сказать баг это браузеров или наоборот соответствует спецификации, но решения два:

  • установить псевдоэлемент ::after после потомка и дать ему высоту вместо padding
  • обернуть элемент в дополнительный блок и дать отступы уже ему.

Воспользуемся вторым методом. Добавим обертку .hystmodal__wrap . Так мы заодно обойдём и проблему №1, а вместо padding у родителя установим margin-top и margin-top у самого .hystmodal__window .

Наш итоговый html:

В код также добавлены некоторые aria и role атрибуты для обеспечения доступности.

Обновленный код CSS для обертки и окна.

1.2 Скрываем окно

Сейчас наше окно всегда видно. Когда говорят о скрытии элементов, первое что приходит на ум это переключать свойство display со значения none до нашего значения flex.

Но этот подход нас не устроит, ведь свойство display не анимируется. Все переходы дочерних элементов, указанные в свойстве transition, работать не будут.

Нам поможет другое свойство visibility:hidden . Оно скроет окно визуально, хотя и зарезервирует под него место. А так как все будущие окна на странице имеют фиксированное
позиционирование – они будут полностью скрыты и не повлияют на остальную страницу. Кроме того, на элементы с visibility:hidden нельзя установить фокус с клавиатуры, а от скрин-ридеров мы уже скрыли окна с помощью атрибута aria-hidden="true" .

Добавим также классы для открытого окна:

1.3 Оформление подложки

В лучшем случае, нам нужен отдельный html-элемент в качестве оверлея. Можно использовать и имеющийся элемент модального окна .hystmodal в качестве оверлея, но тогда анимация на этом элементе (например переход opacity) будет затрагивать и внутренние элементы. В итоге, не получится анимировать разные свойства для окна и оверлея отдельно.

Просто разместим элемент .hystmodal__shadow прямо перед закрывающим . В будущем, сделаем так, чтобы этот элемент создавался автоматически из js при инициализации библиотеки.

1.4 Отключение прокрутки страницы

Когда модальное окна открывается, мы хотим, чтобы страница под ним не прокручивалась.
Самый простой способ этого добиться — повесить overflow:hidden для body или html, когда окно открывается. Однако с этим есть проблема:

Проблема №4. В браузере Safari на iOS страница будет прокручиваться, даже если на тег html или body повешен overflow:hidden .
Решается двумя способами, либо блокированием событий прокрутки (touchmove, touchend или touchsart) из js вида:

Однако при этом блокируется и прокрутка самого модального окна, а также все прокручиваемые блоки внутри окна, если они будут. Требуется дополнительные проверки селекторов из js, что приводит к усложнению кода, поэтому рассмотрим другой вариант.

ps: можно конечно применить библиотеку scroll-lock, в которую заложено это решение, но в статье было решено воспользоваться другим вариантом.

Другое решение – основано частично на CSS. Пусть когда окно открывается, на элемент будет добавляться класс .hystmodal__opened :

Благодаря position:fixed , окно не будет прокручиваться даже в safari, однако здесь тоже не всё гладко:

Проблема №5. При открытии/закрытии окна страница прокручивается в начало.
Действительно, это происходит из-за изменения свойства position, текущая прокрутка окна сбрасывается.

Для решения, нам нужно написать следующий JS (упрощенно):

При открытии:

При закрытии:

Отлично, приступим к JavaScript коду.

2. Код JavaScript

2.2 Каркас библиотеки

Нам нужна совместимость со старыми браузерами включая IE11 поэтому нам нужно выбрать из 2 вариантов кодинга:

  • Разрабатывать на старом стандарте ES5, и использовать только те фичи, которые поддерживают все браузеры.
  • Применить современный синтаксис ES6, но подключить транспайлер Babel, который автоматически преобразует код для всех браузеров и встроит необходимые полифилы.
    Было принято решение использовать второй вариант, с прицелом на будущее.
    Приступим.

Основа нашей библиотеки единственный класс HystModal . Ниже я приведу скелет кода с комментариями, а потом добавим остальной функционал.

Итак, мы описали класс HystModal . Чтобы всё работало, нужно всего лишь подключить наш скрипт и создать экземпляр класса:

Проблема №6: если в браузере есть фиксированный скроллбар (который влияет на ширину страницы), то при открытии/закрытии окна происходит сдвиг контента, когда полоса прокрутки то появляется то пропадает.

Действительно – скроллбар пропадает и контент страницы перераспределяется. Чтобы решить эту проблему, можно добавить отступ справа к тегу html, равный ширине скроллбара когда он пропадает.

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

Дополним метод _bodyScrollControl()

Почему код метода close() упрощён? Дело в том, что просто убирая классы CSS у элементов, мы не можем анимировать закрытие окна.

Проблема №7. При закрытии окна, свойство visibility:hidden применяется сразу и не даёт возможности анимировать закрытие окна.

Причина этого известна: свойство visibility:hidden не анимируется. Конечно, можно обойтись без анимации, но, если она нужна, сделаем следующее.

  • Создадим дополнительный CSS-класс .hystmodal—moved почти такой-же как и .hystmodal--active
  • Затем при закрытии сначала добавим этот класс к окну и повесим обработчик события «transitionend» на модальном окне. Затем удалим класс `.hystmodal—active , таким образом вызывая css-переход. Как только переход завершится, сработает обработчик события «transitionend», в котором сделаем всё остальное и удалим сам обработчик события.

Ниже: новая версия методов закрытия окна:

Вы заметили, что мы создали ещё один метод _closeAfterTransition() и перенесли основную логику закрытия туда. Это нужно, чтобы удалить обработчик события transitionend после закрытия окна, ведь в метод removeEventListener необходимо передать именно ту функцию, которую мы привязывали.

Кроме того, если анимация не будет нужна, можно просто вызвать this._closeAfterTransition() не вешая его на событие.

Как мы помним, внутри addEventListener, this будет указывать на селектор где происходит событие, а не на наш экземпляр класса, поэтому в конструкторе нужно добавить ещё одну строчку для жёсткой привязки метода к this.

2.2 Закрытие окна по клику на оверлей

Нам нужно обработать ещё одно событие – закрытие окна по клику на элемент подложки .hystmodal__wrap . Мы можем повесить обработчик клика на документ для делегирования события как при открытии и проверить что событие произошло на .hystmodal__wrap примерно так:

Это будет работать, но есть один малозаметный недостаток.

Проблема №8. Если кнопку мыши нажать внутри окна, а отпустить за его пределами (над подложкой), окно закрывается.

Представьте, что в окне форма. Вы выделяете текст в поле ввода и случайно двигаете мышь чуть дальше, курсор заходит на подложку и вдруг окно закрывается, всё вдруг пропадает из виду. Не хотелось бы, чтобы так было.

Окно закрывается потому что по спецификации, если нажатие и отпускание мыши были на разных элементах, то событие click сработает на самом ближайшем общем для них элементе, а у нас это как раз .hystmodal__wrap .

Мы могли бы решить это изменением html, добавляя ещё один div сразу после .hystmodal__window и размещая его визуально под окном. Но нам бы не хотелось добавлять лишний пустой div ещё сильнее усложняя разметку.

Мы можем разбить наш addEventListener на два отдельных обработчика: для событий mousedown и mouseup и будем проверять чтобы оба события происходили именно на .hystmodal__wrap . Добавим новые обработчики событий в наш метод eventsFeeler()

2.3 Управление фокусом

У нас заготовлено два метода для управления фокусом: focusContol() для переноса фокуса внутрь окна и обратно при его закрытии, а также focusCatcher(event) для блокирования ухода фокуса из окна.

Решения для фокуса были реализованы аналогично js-библиотеке «Micromodal» (Indrashish Ghosh). А именно:

1. В служебный массив сохраним все css селекторы на которых может быть установлен фокус (свойство помещаем в init()):

2. В методе focusContol() находим первый такой селектор в окне и устанавливаем на него фокус, если окно открывается. Если же окно закрывается – то переводим фокус на this.starter :

3. В методе focusCatcher() находим в окне и превращаем в массив коллекцию всех элементов на которых может быть фокус. И проверяем, если фокус должен был выйти на пределы окна, то вместо этого устанавливаем фокус снова на первый или последний элемент (ведь фокус можно переключать как по Tab так и по Shift+Tab в обратную сторону).

Результирующий код метода focusCatcher:

По сути мы реализовали все необходимое для успешного создания модальных окон, осталось ещё несколько дел:

Проблема №9. В IE11 не работают методы Element.closest() и Object.assign() .

Для поддержки Element.closest, воспользуемся полифилами для closest и matches от MDN.

Можно их вставить просто так, но так как у нас проект всё равно собирается webpack, то удобно воспользоваться пакетом element-closest-polyfill который просто вставляет этот код.

Для поддержки Object.assign , можно воспользоваться уже babel-плагином @babel/plugin-transform-object-assign

3. Заключение и ссылки

Повторяя начало статьи, всё изложенное выше, я оформил в маленькую библиотеку hystModal под MIT-лицензией. Вышло 3 кБ кода при загрузке с gzip. Ещё написал для неё подробную документацию на русском и английском языке.

Что вошло ещё в библиотеку hystModal, чего не было в статье:

  • Настройки (вкл/выкл управление фокусом, варианты закрытия, ожидание анимации закрытия)
  • Коллбеки (функции вызывающиеся перед открытием окна и после его закрытия (в них передаётся объект модального окна))
  • Добавлен запрет на какие-либо действия пока анимация закрытия окна не завершится, а также ожидание анимации закрытия текущего окна перед открытием нового (если окно открывается из другого окна).
  • Оформление кнопки-крестика закрытия в CSS
  • Минификация CSS и JS плагинами Webpack.

Если вам будет интересна эта библиотека, буду рад звёздочке в GitHub, или напишите в Issues о найденных багах. (Особенно большие проблемы, наверное, в грамматике английской версии документации, так как мои знания языка пока на начальном уровне. Связаться со мной также можно в Instagram

Статья рассчитана на пользователей только начинающих работать с jQuery UI и желающих на практике познакомиться с этой библиотекой.
Данный оконный интерфейс предполагает такие основные свойства как — наличие окон, возможность их перетаскивания, возможность изменения размера окон, их свертывания/развертывания и т.д. Вот что должно получиться в итоге.
Итак, имеем желание создать пример интерактивного оконного веб-интерфейса и возможности использовать для этой цели jQuery UI – тогда, добро пожаловать под кат.

Краткое вступление

Кто еще не знает, что такое jQuery UI, может подробнее почитать об этом на русском языке, пройдя по следующей ссылке. О появлении русского перевода документации рассматриваемой библиотеки уже упоминалось на Хабре.

1 Этап. – Подготовительный.
Этап 2. – HTML и JavaScript код для создания диалогового окна.

Пришло время создать код для первого диалогового окна. Согласно документации, окна в jQuery UI — это обычные “дивы” (div), к которым применен соответствующий класс и метод инициализации. Внутрь такого блока помещается содержимое окна.
В тэг body добавим следующий код:

Для инициализации окна, требуется исполнить JavaScript код. Его можно вынести в отдельный файл и подключить в теге , а можно там же указать следующую конструкцию:


Теперь, если открыть в браузере файл index.html, то мы получим следующее:

Обратите внимание, это окно уже обладает такими свойствами drag-n-drop, изменение размера, и возможностью закрытия.
Теперь создадим простую форму для возможности вызова диалоговых окон из другого диалогового окна. Вставим код ниже в содержимое нашего первого окна, т.е. внутрь дива вместо строки «Привет, мир!»:

Этот шаблон пригодится нам на следующих этапах.

Этап 3. – Кастомизация диалога посредством стилей и диалоговых опций.

Заменим инициализирующий окно код таким:

Пропишем новые новые стили в тег head:


Теперь окно с формой внутри смотрится чуть симпатичнее:

В свойствах диалога, записями width: 'auto’ и height: 'auto' мы указали окну подгонять свой размер под размер содержимого.

Этап 4 – Добавление кнопки для открытия диалогового окна.

Кнопки (компонент Buttons) в jQuery UI, позволяют “навешивать” на свои события (например на нажатие) различные функции, чем сейчас и надо заняться.
Для начала создадим кнопку, добавив HTML код:

в тэг body.
Затем проинициализируем её, добавив строчку:

в функцию $(document).ready().
Раз уж начали экспериментировать с кнопками, то переопределим наши чекбоксы в форме в некое подобие кнопок, для более презентабельного вида.



Сейчас, когда всё выглядит более-менее нормально, сделаем так, чтобы по нажатию на кнопку создания окна выполнялось соответствующее действие, а при следующем нажатие окно убиралось. Перепишем JavaScript код:

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

Во второй части статьи будут рассмотрены более сложные этапы по добавлению оставшегося функционала интерфейса.

Код с небольшими изменениями, основной мотив статьи, порядок следования этапов создания оконного интерфейса при помощи jQuery UI взяты отсюда – англоязычный источник.
Демо.
Часть 2.

Одно из главных преимуществ создания приложения с Tkinter в том, что с его помощью очень просто настроить интерфейс всего в несколько строк. И если программа становится сложнее, то и сложнее логически разделять ее на части, а организованная структура помогает сохранять код в чистом виде.

Простой пример

Возьмем в качестве примера следующую программу:

Она создает окно с кнопкой, которая выводит Привет, Tkinter! каждый раз при нажатии. Кнопка расположена с внутренним отступом 120px по горизонтальной оси и 30px – по вертикальной. Последняя строка запускает основной цикл, который обрабатывает все пользовательские события и обновляет интерфейс до закрытия основного окна.

Простое окно с кнопкой

Программу можно запустить, чтобы убедиться, что она работает. Все переменные определяются в глобальном пространстве имен, и чем больше виджетов добавляется, тем сложнее разобраться в том, какие части они используют.

Wildcard-импорты ( from … import * ) считаются плохой практикой, поскольку они загрязняют глобальное пространство имен. Здесь они используются для иллюстрации анти-паттерна, который часто встречается в примерах онлайн.

Эти проблемы настройки решаются с помощью базовых техник объектно-ориентированного программирования, что считается хорошей практикой для любых типов программ на Python.

Правильный пример

Чтобы улучшить модуль простой программы, стоит определить несколько классов, которые станут обертками вокруг глобальных переменных:

Теперь каждая переменная хранится в конкретной области видимости, включая функцию command , которая находится в отдельном методе.

Как работает это приложение?

Во-первых нужно заменить wildcard-импорт на импорт в формате import … as для лучшего контроля над глобальным пространством имен.

Затем класс App определяется как подкласс Tk , который теперь ссылается на пространство имен tk . Для правильной инициализации базового класса, вызывается метод __init__() класса Tk с помощью встроенной функции super() . За это отвечают следующие строки:

Теперь есть ссылка на экземпляр App с переменной self . Так что виджет кнопки будет добавлен как атрибут класса.

Это может казаться излишним для такой простой программы, но подобный рефакторинг помогает работать с каждой отдельной частью. Создание кнопки отделено от обратного вызова, которые исполняется при нажатии. А генерация приложения перемещена в if __name__ == "main" , что является стандартной практикой для исполняемых скриптов в Python.

Такой же принцип будет использовать в примерах и дальше, поэтому его можно взять как шаблон-стартовая точка для крупных приложений.

Дополнение о структуре приложения

Класс Tk вынесен в отдельный класс в примере, но распространенной практикой считается выделять так же другие классы виджетов. Это делается для воссоздания тех же инструкций, которые были до рефакторинга.

Однако может быть более удобно разделять классы Frame или Toplevel особенно для больших программ, где, например, есть несколько окон. Это все потому что у приложения Tkinter должен быть один экземпляр Tk , а система создает их автоматически при создании экземпляра виджета до создания экземпляра самого Tk .

Помните, что это не влияет на структуру класса App , поскольку у всех классов виджетов есть метод mainloop , который запускает основной цикл Tk .

Работа с кнопками

Виджеты кнопок представляют собой кликабельные элементы графического интерфейса приложений. Они обычно используют текст или изображение, указывающие на то, какое действие будет выполнено при нажатии. Tkinter позволяет легко настраивать их функциональность с помощью стандартных настроек класса виджета Button .

Как создать кнопку

Следующий блок содержит кнопку с изображением, которая выключается при нажатии, а также список кнопок с разными типами анимации после нажатия:

Цель программы — показать разные варианты настройки, которые могут быть использованы при создании виджета кнопки.

После выполнения кода выше, возвращается следующее:

Разные варианты настройки кнопок

Простейший способ создания экземпляра Button — использование параметра text для настройки метки кнопки и command , который ссылается на вызываемую функцию при нажатии кнопки.

В этом примере также добавляется PhotoImage с помощью параметра image , который имеет приоритет над строкой text . Этот параметр используется для объединения изображения и текста на одной кнопке, определяя местоположение, где будет находиться картинка. Он принимает следующие константы: CENTER, BOTTOM, LEFT, RIGHT и TOP.

Второй ряд кнопок создается с помощью сгенерированного списка и списка значений RELIEF . Метка каждой кнопки соответствует константе, так что можно заметить разницу во внешнем виде.

Для сохранения ссылки на экземпляр PhotoImage использовался атрибут, хотя его и нет вне метода __init__ . Причина в том, что изображения удаляются при сборке мусора. Это и происходит, если объявить их в качестве локальных переменных.

Для избежания этого нужно помнить о сохранении ссылки на каждый объект PhotoImage до тех пор, пока окно, где он показывается, не закрыто.

Читайте также: