#статьи
-
0
Знакомимся с библиотекой Tkinter — пишем на Python кросс-платформенный калькулятор, который рассчитывает вес человека.
Иллюстрация: Merry Mary для Skillbox Media
Изучает Python, его библиотеки и занимается анализом данных. Любит путешествовать в горах.
Десктопные приложения пишут на разных языках программирования: C++, C#, C, Python и других. Начинающим разработчикам проще всего использовать Python и его библиотеки для работы над графическими интерфейсами.
Одна из таких библиотек — Tkinter. Она входит в стандартный пакет Python и позволяет создавать приложения для Windows, mac OS и Linux. Давайте разберёмся, как устроена эта библиотека, и напишем десктопный калькулятор, помогающий рассчитать вес человека.
GUI (Graphical User Interface) — это графический интерфейс пользователя, оболочка программы, с которой мы взаимодействуем с помощью клавиатуры и мыши. На современных операционных системах почти все программы работают с графическим интерфейсом, и мы каждый день сталкиваемся с GUI: читаем статьи в браузере, набираем текст в редакторе или играем в игры.
Противоположность графическому интерфейсу — командная строка, позволяющая управлять приложением с помощью текстовых команд. Такой интерфейс реализован в терминале macOS и командной строке Windows.
Для работы с GUI в Python есть четыре библиотеки:
- Tkinter;
- Kivy;
- Python QT;
- wxPython.
Мы выбрали Tkinter, потому что она не требует дополнительной установки и позволяет быстро создавать приложения с простым графическим интерфейсом.
Tkinter — это удобный интерфейс для работы со средствами Tk. Приложения, созданные на основе этой библиотеки, кросс-платформенные, то есть могут запускаться на разных операционных системах.
Схематично работу с Tkinter можно представить в виде четырёх шагов:
Что здесь происходит:
- Мы подключаем библиотеку Tkinter с помощью директивы import.
- Создаём главное окно приложения, в котором будут размещаться все графические элементы.
- Добавляем виджеты — визуальные элементы, выполняющие определённые действия.
- Создаём главный цикл событий — он включает в себя все события, происходящие при взаимодействии пользователя с интерфейсом.
Ключевые объекты в работе с Tkinter — виджеты. Это аналоги тегов из HTML, которые позволяют создавать интерактивные и неинтерактивные элементы, например надписи или кнопки. Всего их 18, но чаще всего используют следующие:
- Button — кнопки;
- Canvas — «холст», на котором рисуют графические фигуры;
- Entry — виджет для создания полей ввода;
- Label — контейнер для размещения текста или изображения;
- Menu — виджет для создания пунктов меню.
Понять работу с виджетами легче всего на практике. Но прежде чем к ней приступить, обсудим идею нашего первого десктопного приложения.
Мы напишем калькулятор индекса массы тела. ИМТ — это важный медицинский показатель, который позволяет оценить, есть ли у человека избыточный вес или ожирение. Он рассчитывается по следующей формуле:
Результаты расчётов оценивают с помощью специальной таблицы. У врачей она имеет много градаций, мы же воспользуемся упрощённой версией:
Писать код на Python лучше всего в специальной IDE, например в PyCharm или Visual Studio Code. Они подсвечивают синтаксис и предлагают продолжение кода — это сильно упрощает работу программиста. Весь код из этой статьи мы писали в Visual Studio Code.
Библиотека Tkinter предустановлена в Python. Поэтому её нужно только импортировать:
import tkinter as tk
Теперь мы можем использовать любые модули из этой библиотеки.
Прежде чем писать код, необходимо ответить на несколько вопросов:
- Какие данные мы хотим получить от пользователя и в каком виде?
- Какое событие будет запускать расчёт ИМТ: нажатие кнопки, получение приложением всех необходимых данных или что-то другое?
- Как будем показывать результат?
В нашем случае необходимо получить от пользователя вес и рост в виде целых чисел. При этом вес должен быть введён в килограммах, а рост — в сантиметрах. ИМТ будет рассчитываться по нажатии кнопки, а результат — выводиться во всплывающем окне в виде значения ИМТ и категории, к которой он относится.
Схематично графический интерфейс нашего калькулятора будет выглядеть так:
Теперь попробуем реализовать интерфейс и работу калькулятора с помощью Python и Tkinter.
После импорта библиотеки в Python загрузим её методы:
from tkinter import * from tkinter import messagebox
Первая строка позволяет нам загрузить все методы Tkinter и использовать их в коде без ссылки на их наименование. Второй строкой мы явно импортируем метод messagebox, который будем использовать для вывода всплывающего окна с результатом. Это удобно, так как метод потребуется нам несколько раз.
Теперь создадим окно нашего приложения. Для этого воспользуемся модулем Tk. Приложение назовём «Калькулятор индекса массы тела (ИМТ)»:
window = Tk() #Создаём окно приложения. window.title("Калькулятор индекса массы тела (ИМТ)") #Добавляем название приложения.
После запуска кода ничего не произойдёт. Это не ошибка. На самом деле код выполнился и окно закрылось. Необходимо явно указать, что окно приложения не должно закрываться до тех пор, пока пользователь сам не сделает этого. Для этого к коду добавим функцию window.mainloop (), указывающую на запуск цикла событий:
window.mainloop()
Запустив код, увидим экран приложения:
Мы не указали размер окна, поэтому название приложения не помещается в него полностью. Исправим это с помощью метода geometry:
window.geometry('400x300')
Теперь название приложения видно полностью:
В окне приложения необходимо разместить несколько элементов с нашего эскиза: два поля ввода информации с подписями и одну кнопку. Важно, чтобы поля не накладывались друг на друга и не уходили за пределы окна. В Tkinter для этого есть несколько методов:
- pack — используется, когда мы работаем с контейнерами для элементов. Позволяет позиционировать кнопки, надписи или другие элементы внутри контейнеров.
- place — позволяет позиционировать элементы, указывая точные координаты.
- grid — размещает элементы по ячейкам условной сетки, разделяющей окно приложения.
Мы воспользуемся комбинацией методов pack и grid. Для начала создадим виджет Frame для размещения надписей, полей ввода и кнопок. Подробное описание работы виджета есть в документации. Мы же используем только два свойства: padx и pady.
Обозначим отступы по вертикали и горизонтали в 10 пикселей для элементов, которые будут расположены внутри Frame:
frame = Frame( window, #Обязательный параметр, который указывает окно для размещения Frame. padx = 10, #Задаём отступ по горизонтали. pady = 10 #Задаём отступ по вертикали. ) frame.pack(expand=True) #Не забываем позиционировать виджет в окне. Здесь используется метод pack. С помощью свойства expand=True указываем, что Frame заполняет весь контейнер, созданный для него.
В окне приложения нам необходимо добавить три вида виджетов: поле для ввода информации (Entry), текстовые надписи (Label) и кнопку (Button).
Начнём с надписей. Воспользуемся виджетом Label:
height_lb = Label( frame, text="Введите свой рост (в см) " ) height_lb.grid(row=3, column=1)
Мы передаём виджету Label два параметра:
- frame — используем заготовку виджета Frame, в которой уже настроены отступы по вертикали и горизонтали.
- text — текст, который должен быть выведен на экран.
Для позиционирования виджета используем метод grid. Укажем, что текст должен располагаться в ячейке с координатами «3-я строка, 1-й столбец». Если запустим код, то увидим там единственный элемент:
Сейчас элемент расположен в центре окна, но он займёт правильное положение, когда мы напишем другие элементы.
Добавим вторую надпись о весе аналогичным образом, но при позиционировании в grid укажем следующую, четвёртую строку:
weight_lb = Label( frame, text="Введите свой вес (в кг) ", ) weight_lb.grid(row=4, column=1)
Запускаем код и смотрим на результат:
Теперь добавим поля для ввода пользовательской информации, используя виджет Entry:
height_tf = Entry( frame, #Используем нашу заготовку с настроенными отступами. ) height_tf.grid(row=3, column=2)
Для позиционирования мы также воспользовались методом grid. Обратите внимание, что наш элемент должен быть расположен напротив надписи «Введите свой рост (в см)». Поэтому мы используем ячейку в той же строке, но уже во втором столбце. Запустим код и посмотрим на результат:
Всё получилось. Остаётся по аналогии добавить поле ввода веса:
weight_tf = Entry( frame, ) weight_tf.grid(row=4, column=2, pady=5)
Посмотрим на результат:
Теперь добавим кнопку, которая будет запускать расчёт ИМТ. Сделаем это с помощью виджета Button:
cal_btn = Button( frame, #Заготовка с настроенными отступами. text='Рассчитать ИМТ', #Надпись на кнопке. ) cal_btn.grid(row=5, column=2) #Размещаем кнопку в ячейке, расположенной ниже, чем наши надписи, но во втором столбце, то есть под ячейками для ввода информации.
Посмотрим на результат:
Теперь в приложении есть все графические элементы. Остаётся лишь написать код, который будет получать информацию из виджетов Entry и рассчитывать индекс массы тела.
Напишем простую функцию и разберём её построчно:
def calculate_bmi(): #Объявляем функцию. kg = int(weight_tf.get()) #С помощью метода .get получаем из поля ввода с именем weight_tf значение веса, которое ввёл пользователь и конвертируем в целое число с помощью int(). m = int(height_tf.get())/100 #С помощью метода .get получаем из поля ввода с именем height_tf значение роста и конвертируем в целое число с помощью int(). Обязательно делим его на 100, так как пользователь вводит рост в сантиметрах, а в формуле для расчёта ИМТ используются метры. bmi = kg/(m*m)#Рассчитываем значение индекса массы тела. bmi = round(bmi, 1) #Округляем результат до одного знака после запятой.
Функция готова. Но теперь нам необходимо оценить полученный результат расчёта и вывести сообщение для пользователя.
Дополним нашу функцию calculate_bmi. Воспользуемся условным оператором if, чтобы учесть полученные значения ИМТ, и методом Tkinter messagebox для отображения сообщения во всплывающем окне:
if bmi < 18.5: messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует недостаточному весу') elif (bmi > 18.5) and (bmi < 24.9): messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует нормальному весу') elif (bmi > 24.9) and (bmi < 29.9): messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует избыточному весу') else: messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует ожирению')
Остаётся последний шаг — наша функция должна запускаться при нажатии на кнопку «Рассчитать ИМТ». Для этого добавим свойство command в виджет Button:
cal_btn = Button( frame, text='Рассчитать ИМТ', command=calculate_bmi #Позволяет запустить событие с функцией при нажатии на кнопку. ) cal_btn.grid(row=5, column=2)
Запустим код и посмотрим на результат:
Всё работает. Функция получает данные из полей ввода и рассчитывает индекс массы тела, показывая результат на экране.
from tkinter import *
from tkinter import messagebox
def calculate_bmi():
kg = int(weight_tf.get())
m = int(height_tf.get())/100
bmi = kg/(m*m)
bmi = round(bmi, 1)
if bmi < 18.5:
messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует недостаточному весу')
elif (bmi > 18.5) and (bmi < 24.9):
messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует нормальному весу')
elif (bmi > 24.9) and (bmi < 29.9):
messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует избыточному весу')
else:
messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует ожирению')
window = Tk()
window.title('Калькулятор индекса массы тела (ИМТ)')
window.geometry('400x300')
frame = Frame(
window,
padx=10,
pady=10
)
frame.pack(expand=True)
height_lb = Label(
frame,
text="Введите свой рост (в см) "
)
height_lb.grid(row=3, column=1)
weight_lb = Label(
frame,
text="Введите свой вес (в кг) ",
)
weight_lb.grid(row=4, column=1)
height_tf = Entry(
frame,
)
height_tf.grid(row=3, column=2, pady=5)
weight_tf = Entry(
frame,
)
weight_tf.grid(row=4, column=2, pady=5)
cal_btn = Button(
frame,
text='Рассчитать ИМТ',
command=calculate_bmi
)
cal_btn.grid(row=5, column=2)
window.mainloop()
Узнать о возможностях Tkinter и особенностях работы с виджетами можно в официальной документации. А если хотите найти больше реальных примеров для практики, советуем две книги:
- Python GUI Programming with Tkinter. Develop responsive and powerful GUI applications with Tkinter, Алан Мур.
- Tkinter GUI Programming by Example, Дэвид Лав.
Курс с трудоустройством: «Профессия Python-разработчик»
Узнать о курсе
Пройдите тест, узнайте какой профессии подходите
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы
Введение в разработку приложений для ПК на Python
Python — это мощный и гибкий язык программирования, который идеально подходит для разработки приложений для ПК. Благодаря своей простоте и обширной библиотеке, Python позволяет создавать как простые утилиты, так и сложные приложения с графическим интерфейсом. В этой статье мы рассмотрим основные шаги, необходимые для разработки приложений для ПК на Python, начиная с установки окружения и заканчивая сборкой и распространением готового продукта.
Python имеет множество преимуществ, которые делают его отличным выбором для разработки приложений. Во-первых, это кроссплатформенность: приложения, написанные на Python, могут работать на Windows, macOS и Linux без значительных изменений в коде. Во-вторых, Python обладает богатой экосистемой библиотек и фреймворков, которые упрощают разработку и позволяют сосредоточиться на логике приложения, а не на низкоуровневых деталях.
Кроме того, Python известен своей читаемостью и простотой синтаксиса, что делает его отличным выбором для новичков. Даже если вы только начинаете свой путь в программировании, вы сможете быстро освоить основы Python и приступить к созданию своих первых приложений.

Установка и настройка окружения
Перед тем как начать разработку, необходимо установить Python и настроить рабочее окружение. Для этого выполните следующие шаги:
-
Скачайте и установите Python: Перейдите на официальный сайт Python (https://www.python.org/) и скачайте последнюю версию. Установите Python, следуя инструкциям установщика. Убедитесь, что вы установили Python с опцией добавления в PATH, чтобы иметь возможность запускать его из командной строки.
-
Установите виртуальное окружение: Виртуальные окружения позволяют изолировать зависимости вашего проекта. Это особенно полезно, если вы работаете над несколькими проектами, которые требуют разных версий библиотек. Для создания виртуального окружения выполните команду:
Активируйте виртуальное окружение:
– На Windows:
– На macOS и Linux: -
Установите необходимые библиотеки: Для разработки графических интерфейсов на Python часто используется библиотека Tkinter, которая входит в стандартную библиотеку Python. Дополнительно можно установить PyInstaller для сборки приложения:
Настройка виртуального окружения также позволяет вам легко управлять зависимостями вашего проекта. Вы можете создать файл requirements.txt
, в котором будут перечислены все необходимые библиотеки, и затем установить их с помощью команды:
Создание простого графического интерфейса с Tkinter
Tkinter — это стандартная библиотека Python для создания графических интерфейсов. Она проста в использовании и позволяет быстро создавать окна, кнопки, текстовые поля и другие элементы интерфейса. Tkinter является частью стандартной библиотеки Python, поэтому вам не нужно устанавливать дополнительные пакеты для его использования.
Пример простого окна
Создадим простое окно с кнопкой и текстовым полем. Этот пример поможет вам понять основные принципы работы с Tkinter и создать базовый интерфейс для вашего приложения:
Этот код создает окно с заголовком «Простое приложение», меткой и кнопкой. При нажатии на кнопку текст метки изменяется на «Привет, мир!». Основные элементы интерфейса, такие как окна, метки и кнопки, создаются с помощью соответствующих классов Tkinter. Метод pack()
используется для размещения элементов в окне.
Дополнительные элементы интерфейса
Помимо меток и кнопок, Tkinter предоставляет множество других элементов интерфейса, таких как текстовые поля, флажки, радиокнопки и списки. Рассмотрим пример с добавлением текстового поля и флажка:
В этом примере добавлено текстовое поле для ввода имени пользователя и флажок. При нажатии на кнопку приложение выводит приветствие с именем пользователя и состоянием флажка.
Работа с событиями и обработчиками
Важной частью любого приложения с графическим интерфейсом является обработка событий, таких как нажатия кнопок, ввод текста и т.д. В Tkinter события обрабатываются с помощью функций-обработчиков, которые связываются с элементами интерфейса.
Пример обработки событий
Добавим обработку ввода текста в наше приложение. Это позволит нам реагировать на действия пользователя и изменять интерфейс в зависимости от введенных данных:
Теперь приложение запрашивает у пользователя имя и выводит приветствие с этим именем при нажатии на кнопку. Обработка событий в Tkinter осуществляется с помощью связывания функций-обработчиков с элементами интерфейса. В данном случае функция on_button_click
вызывается при нажатии на кнопку.
Обработка дополнительных событий
Помимо нажатий кнопок, Tkinter позволяет обрабатывать множество других событий, таких как движение мыши, нажатие клавиш и изменение состояния элементов интерфейса. Рассмотрим пример обработки события нажатия клавиши:
В этом примере приложение реагирует на нажатие любой клавиши и выводит символ нажатой клавиши в метке. Метод bind
используется для связывания события с функцией-обработчиком.
Сборка и распространение приложения
После завершения разработки приложения его необходимо собрать в исполняемый файл, чтобы пользователи могли запускать его без установки Python и зависимостей. Для этого можно использовать PyInstaller.
Сборка приложения с PyInstaller
- Установите PyInstaller (если еще не установили):
bash
pip install pyinstaller - Соберите приложение:
bash
pyinstaller --onefile --windowed your_script.py
Параметры--onefile
и--windowed
создают один исполняемый файл без консольного окна. PyInstaller анализирует ваш скрипт и все его зависимости, а затем упаковывает их в один исполняемый файл, который можно запускать на целевой системе без необходимости установки Python.
Настройка сборки
PyInstaller предоставляет множество опций для настройки процесса сборки. Вы можете указать дополнительные файлы, которые должны быть включены в сборку, настроить иконку приложения и многое другое. Рассмотрим пример настройки иконки и включения дополнительных файлов:
-
Создайте файл спецификации:
В этом примере используется параметр `—icon` для указания иконки приложения и `—add-data` для включения папки с данными.
-
Редактирование файла спецификации: После первого запуска PyInstaller создаст файл спецификации (
.spec
), который можно отредактировать для более тонкой настройки сборки. Откройте файл.spec
и внесите необходимые изменения.
Распространение приложения
Для распространения приложения можно использовать различные методы:
-
Распределение через файлообменники: Просто загрузите исполняемый файл на файлообменник и поделитесь ссылкой с пользователями. Это простой и быстрый способ распространения, но он может быть неудобен для пользователей, если приложение требует установки дополнительных файлов или настроек.
-
Создание установщика: Используйте инструменты, такие как Inno Setup (для Windows) или Platypus (для macOS), чтобы создать удобный установщик для вашего приложения. Установщик позволяет пользователям легко установить и настроить ваше приложение, а также может включать дополнительные файлы и зависимости.
-
Публикация на GitHub: Создайте репозиторий на GitHub и загрузите туда исходный код и исполняемый файл. Это позволит пользователям легко найти и скачать ваше приложение. Вы также можете использовать GitHub Releases для публикации новых версий приложения и предоставления пользователям удобного интерфейса для загрузки.
-
Использование платформ для распространения ПО: Рассмотрите возможность публикации вашего приложения на платформах, таких как Microsoft Store, Mac App Store или Linux Package Managers. Это может потребовать дополнительных усилий для соответствия требованиям платформы, но обеспечит более широкую аудиторию и удобство для пользователей.
Заключение
Разработка приложений для ПК на Python — это увлекательный и полезный процесс. С помощью Python и библиотек, таких как Tkinter и PyInstaller, можно быстро создавать и распространять приложения с графическим интерфейсом. Надеюсь, это руководство помогло вам сделать первые шаги в этой области. Удачи в разработке! 😉
Читайте также
Считается, что Python не лучший выбор для десктопных приложений. Однако, когда в 2016 году я собирался переходить от разработки сайтов к программному обеспечению, Google подсказал мне, что на Python можно создавать сложные современные приложения. Например blender3d, который написан на Python.
Мы будем использовать PyQt (произносится «Пай-Кьют»). Это фреймворк Qt, портированный с C++. Qt известен тем, что необходим C++ разработчикам. С помощью этого фреймворка сделаны blender3d, Tableau, Telegram, Anaconda Navigator, Ipython, Jupyter Notebook, VirtualBox, VLC и другие. Мы будем использовать его вместо удручающего Tkinter.
Требования
- Вы должны знать основы Python
- Вы должны знать, как устанавливать пакеты и библиотеки с помощью pip.
- У вас должен быть установлен Python.
Установка
Вам нужно установить только PyQt. Откройте терминал и введите команду:
Мы будем использовать PyQt версии 5.15. Дождитесь окончания установки, это займёт пару минут.
Hello, World!
Создайте папку с проектом, мы назовём его helloApp. Откройте файл main.py, лучше сделать это vscode, и введите следующий код:
import sys
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('./UI/main.qml')
sys.exit(app.exec())
Этот код вызывает QGuiApplication и QQmlApplicationEngine которые используют Qml вместо QtWidget в качестве UI слоя в Qt приложении. Затем, мы присоединяем UI функцию выхода к главной функции выхода приложения. Теперь они оба закроются одновременно, когда пользователь нажмёт выход. Затем, загружаем qml файл для Qml UI. Вызов app.exec(), запускает приложение, он находится внутри sys.exit, потому что возвращает код выхода, который передается в sys.exit.
Добавьте этот код в main.qml:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 600
height: 500
title: "HelloApp"
Text {
anchors.centerIn: parent
text: "Hello, World"
font.pixelSize: 24
}
}
Этот код создает окно, делает его видимым, с указанными размерами и заголовком. Объект Text отображается в середине окна.
Теперь давайте запустим приложение:
Вы увидите такое окно:
Давайте немного обновим UI, добавим фоновое изображение и время:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 400
height: 600
title: "HelloApp"
Rectangle {
anchors.fill: parent
Image {
sourceSize.width: parent.width
sourceSize.height: parent.height
source: "./images/playas.jpg"
fillMode: Image.PreserveAspectCrop
}
Rectangle {
anchors.fill: parent
color: "transparent"
Text {
text: "16:38:33"
font.pixelSize: 24
color: "white"
}
}
}
}
Внутри типа ApplicationWindow находится содержимое окна, тип Rectangle заполняет пространство окна. Внутри него находится тип Image и другой прозрачный Rectangle который отобразится поверх изображения.
Если сейчас запустить приложение, то текст появится в левом верхнем углу. Но нам нужен левый нижний угол, поэтому используем отступы:
Text {
anchors {
bottom: parent.bottom
bottomMargin: 12
left: parent.left
leftMargin: 12
}
text: "16:38:33"
font.pixelSize: 24
...
}
После запуска вы увидите следующее:
Показываем текущее время
Модуль gmtime позволяет использовать структуру со временем, а strftime даёт возможность преобразовать её в строку. Импортируем их:
import sys
from time import strftime, gmtime
Теперь мы можем получить строку с текущим временем:
curr_time = strftime("%H:%M:%S", gmtime())
Строка "%H:%M:%S"
означает, что мы получим время в 24 часовом формате, с часами минутами и секундами (подробнее о strtime).
Давайте создадим property в qml файле, для хранения времени. Мы назовём его currTime.
property string currTime: "00:00:00"
Теперь заменим текст нашей переменной:
Text {
...
text: currTime // used to be; text: "16:38:33"
font.pixelSize: 48
color: "white"
}
Теперь, передадим переменную curr_time из pyhton в qml:
engine.load('./UI/main.qml')
engine.rootObjects()[0].setProperty('currTime', curr_time)
Это один из способов передачи информации из Python в UI.
Запустите приложение и вы увидите текущее время.
Обновление времени
Для того чтобы обновлять время, нам нужно использовать потоки. Для этого я предлагаю использовать сигналы.
Чтобы использовать сигналы нам нужен подкласс QObject. Назовём его Backend.
...
from PyQt5.QtCore import QObject, pyqtSignal
class Backend(QObject):
def __init__(self):
QObject.__init__(self)
...
У нас уже имеется свойства для строки со временем curr_time, теперь создадим свойство backend типа QtObject в файле main.qml.
property string currTime: "00:00:00"
property QtObject backend
Передадим данные из Python в qml:
engine.load('./UI/main.qml')
back_end = Backend()
engine.rootObjects()[0].setProperty('backend', back_end)
В qml файле один объект QtObject может получать несколько функций (называемых сигналами) из Python.
Создадим тип Connections и укажем backend в его target. Теперь внутри этого типа может быть столько функций, сколько нам необходимо получить в backend.
...
Rectangle {
anchors.fill: parent
Image {
...
}
...
}
Connections {
target: backend
}
...
Таким образом мы свяжем qml и сигналы из Python.
Мы используем потоки, для того чтобы обеспечить своевременное обновление UI. Создадим две функции, одну для управления потоками, а вторую для выполнения действий. Хорошая практика использовать в названии одной из функций _.
...
import threading
from time import sleep
...
class Backend(QObject):
def __init__(self):
QObject.__init__(self)
def bootUp(self):
t_thread = threading.Thread(target=self._bootUp)
t_thread.daemon = True
t_thread.start()
def _bootUp(self):
while True:
curr_time = strftime("%H:%M:%S", gmtime())
print(curr_time)
sleep(1)
...
Создадим pyqtsignal и назовём его updated, затем вызовем его из функции updater.
...
from PyQt5.QtCore import QObject, pyqtSignal
...
def __init__(self):
QObject.__init__(self)
updated = pyqtSignal(str, arguments=['updater'])
def updater(self, curr_time):
self.updated.emit(curr_time)
...
В этом коде updated имеет параметр arguments, который является списком, содержащим имя функции “updater”. Qml будет получать данные из этой функции. В функции updater мы вызываем метод emit и передаём ему данные о времени.
Обновим qml, получив сигнал, с помощью обработчика, название которого состоит из “on” и имени сигнала:
target: backend
function onUpdated(msg) {
currTime = msg;
}
Теперь нам осталось вызвать функцию updater. В нашем небольшом приложении, использовать отдельную функцию для вызова сигнала не обязательно. Но это рекомендуется делать в больших программах. Изменим задержку на одну десятую секунды.
curr_time = strftime("%H:%M:%S", gmtime())
self.updater(curr_time)
sleep(0.1)
Функция bootUp должна быть вызвана сразу же после загрузки UI:
engine.rootObjects()[0].setProperty('backend', back_end)
back_end.bootUp()
sys.exit(app.exec())
Всё готово
Теперь можно запустить программу. Время будет обновляться корректно. Для того, чтобы убрать рамку, вы можете добавить в qml файл следующую строку:
flags: Qt.FramelessWindowHint | Qt.Window
Так должен выглядеть файл main.py:
import sys
from time import strftime, gmtime
import threading
from time import sleep
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import QObject, pyqtSignal
class Backend(QObject):
def __init__(self):
QObject.__init__(self)
updated = pyqtSignal(str, arguments=['updater'])
def updater(self, curr_time):
self.updated.emit(curr_time)
def bootUp(self):
t_thread = threading.Thread(target=self._bootUp)
t_thread.daemon = True
t_thread.start()
def _bootUp(self):
while True:
curr_time = strftime("%H:%M:%S", gmtime())
self.updater(curr_time)
sleep(0.1)
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('./UI/main.qml')
back_end = Backend()
engine.rootObjects()[0].setProperty('backend', back_end)
back_end.bootUp()
sys.exit(app.exec())
Вот содержимое файла main.qml:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 360
height: 600
x: screen.desktopAvailableWidth - width - 12
y: screen.desktopAvailableHeight - height - 48
title: "HelloApp"
flags: Qt.FramelessWindowHint | Qt.Window
property string currTime: "00:00:00"
property QtObject backend
Rectangle {
anchors.fill: parent
Image {
sourceSize.width: parent.width
sourceSize.height: parent.height
source: "./images/playas.jpg"
fillMode: Image.PreserveAspectFit
}
Text {
anchors {
bottom: parent.bottom
bottomMargin: 12
left: parent.left
leftMargin: 12
}
text: currTime
font.pixelSize: 48
color: "white"
}
}
Connections {
target: backend
function onUpdated(msg) {
currTime = msg;
}
}
}
Сборка приложения
Для сборки десктопного приложения на Python нам понадобится pyinstaller.
>>> pip install pyinstaller
Чтобы в сборку добавились все необходимые ресурсы, создадим файл spec:
Настройки файла spec
Параметр datas можно использовать для того, чтобы включить файл в приложение. Это список кортежей, каждый из которых обязательно должен иметь target path(откуда брать файлы) и destination path(где будет находится приложение). destination path должен быть относительным. Чтобы расположить все ресурсы в одной папке с exe-файлами используйте пустую строку.
Измените параметр datas, на путь к вашей папке с UI:
a = Analysis(['main.py'],
...
datas=[('I:/path/to/helloApp/UI', 'UI')],
hiddenimports=[],
...
exe = EXE(pyz,
a.scripts,
[],
...
name='main',
debug=False,
...
console=False )
coll = COLLECT(exe,
...
upx_exclude=[],
name='main')
Параметр console установим в false, потому что у нас не консольное приложение.
Параметр name внутри вызова Exe, это имя исполняемого файла. name внутри вызова Collect, это имя папки в которой появится готовое приложение. Имена создаются на основании файла для которого мы создали spec — main.py.
Теперь можно запустить сборку:
>>> pyinstaller main.spec
В папке dist появится папка main. Для запуска программы достаточно запустить файл main.exe.
Так будет выглядеть содержимое папки с десктопным приложением на Python:
О том, как использовать Qt Designer для создания UI приложений на Python читайте в нашей статье.
Почему Desktop-приложение на Питоне?
Если Вы, как и я, решили впервые взглянуть в сторону Python после нескольких попыток изучения С++/C# то скорее всего первым проектом станет desktop-приложение. Отходя от темы скажу что тяга к изучению этих языков была безнадежно утрачена в виду классического преподавания в духе «лишь бы сдали» и бесчисленных однотипных и монотонных лекций. Как я сказал выше хоть и на начальном уровне, но я всё же касался разработки приложений для Windows и поэтому мне хотелось посмотреть на принципы работы питона сначала отсюда(а не прыгать в django и прочие мощные фреймворки). Должен предупредить — в статье не приводятся выдержки из кода и она является скорее выражением моих эмоций, полученных за этот проект.
Какое приложение создавать?
В этом я не сомневался ни секунды и сразу выбрал — ТАЙМЕР! Я люблю засыпать под звуки фильмов с различных ресурсов, но то что после их окончания комп работал лишние 6 часов меня решительно не устраивало. Я конечно знаю про существование разного рода приложений с подобным функционалом(например SMtimer), но во-первых его UX/UI это просто привет нулевым, а во-вторых хотелось всё-таки своё.
Задачи намечены — время действовать
Посмотрев полтора гайда с youtube преисполнился и написал первый прототип с использованием стандартной библиотеки tkinter. Выглядело это весьма сомнительно с точки зрения того же UX/UI, но это уже было что-то
Тут каждый может заметить и сказать «но ведь это ещё хуже чем SMtimer» и будут правы, в целом и меня посещали такие мысли и я решил обратиться к поиску чего-то что сможет сделать из этого адекватное приложение которым я смогу пользоваться без зазрения совести.
CustomTkinter или как я пытался познать дзен
После недолгого поиска я нашел его — CustomTkinter. Он обещал буквально сделать из моего прототипа приложение, которое будто изначально было в Windows 10. Непосредственно exampl’ы вы можете смотреть ниже:
Вдобавок к этому библиотека обещала и минимальные правки кода для переноса моего «прототипа» на эту прелесть, но мне предстоял ещё большой путь в понимании как это тут работает…
Куча проблем и нехватка понимания
Именно так бы я описал свой путь в познании этой библиотеки. Буквально пытаясь взаимодействовать с ней я понял как устроены типы данных Python(да, может показаться смешным, но когда впервые встречаешь NoneType слегка удивляешься), понял как взаимодействовать с объектами классов и собственно разделять код проекта на функциональные блоки (что до этого мне казалось чем-то странным). Но для того чтобы понять насколько всё было грустно в моих познаниях — вот референс который я запилил от безысходности( на тот момент я хотел получить хотя бы это):
Проблем была целая гора — я решительно не мог понять как общаться с CusomTkinter — создание и позиционирование его объектов хоть сейчас и кажутся достаточно логичными и понятными, но тогда они были для меня сродни китайским иероглифам. После нескольких вечеров, проведённых за курением мануалов я смог собрать 3ю версию (её первая реинкарнация):
Получив это я уже был рад, что не сдался и приступил к починке расположения всех объектов по экрану. Однако следует упомянуть о «маленькая проблема» — в CustomTkinter нет SpinBox, а значит вводить данные было нужно либо с клавиатуры, либо искать как сделать этот самый SpinBox самому. Выбор пал на второй вариант, на сайте, посвященном самому ctk автор сделал FloatSpinBox, доработав который я уже мог получить то что хотел.
Главным его косяком было отсутствие реакции на колёсико мышки — для меня это было критично — нажимать 32 раза для установки минут меня не прельщало и я решил допилить его самостоятельно:
-
Добавил min и max value — теперь я мог использовать этот класс и для минут и для часов (сначала я просто наспамил несколько классов где вручную установил ограничения для каждого из них)
-
Реакция на колёсико мышки теперь охватывала весь SpinBox и это было чудесно. Если просто повесить обработчик на self.bind(«MouseWheel», self.on_mouse_wheel)» то срабатывание будет только на границах entry и элементах кнопок, что просто ужасно.
-
Ну и конечно реализовал прокрутку в обратную сторону. Это так странно делать то что обычно реализовано уже за тебя.
Ну и немного поиграл с темами самого customtkinter:
Наконец то, что я и хотел получить
Теперь я решил сделать вывод времени до срабатывания. Я прорабатывал разные версии этого таймера и пришел к тому что запилил TimeBox. Главное отличие — никаких кнопок. Вывод конечно же стал гораздо веселее.
После того как я насмотрелся на кастомные темы с просторов github я решил, что мне такого ужаса точно не нужно и вернулся к истокам — стандартной зелёной теме. Победив эффект циклопа (даже 3х) я добавил переключение тем и поправил логику приложения — перезагрузка. выключение, сон и блокировка работают на ура ну и два вида условий таймера — через промежуток времени и В конкретное время. Я был полностью доволен и буквально счастлив что теперь мне не нужно каждый день идти в планировщик заданий и выставлять время выключения вручную! Но тут я увидел то, что впоследствии переведет меня из Notepad++ (да, да, именно там я и писал свой проект до этого момента) в PyCharm. Это была библиотечка pywinstyles, обещающая эффекты прозрачности как из win7 но в стиле десятки. То что нужно.
Мне показалось это просто идеальным дополнением моей програмки. Запускаешь поверх фильма и не мешает и знаешь сколько времени осталось — красота. Но полное отсутствие документации и внятных sampl’ов заставило заново пересмотреть перспективы проекта. Я написал автору pywinstyles и с его описанием я таки смог заставить работать это всё вместе:
Если тут есть такие же странные люди, которые решат потрогать pywinstyles — будьте готовы к множеству проблем. К примеру после смены темы на прозрачную назад вернуть никак. Только перезапуск. У main_frame от ctk тут беловатый фон, что заставляет программу «сиять» на светлых фонах.
Выводы
Я получил то, что хотел и кучу необходимого опыта сверху. В будущем хочу реализовать автоматическую остановку таймера. В случае если фильм ставиться на паузу то и сам таймер останавливается. Как это реализовать при просмотре фильма из браузера я пока не знаю, но если у Вас есть идеи — с радостью почитаю.
Потрогать версию без pywinstyles можно тут
Последнее обновление: 27.02.2024
Установка
Для создания программ на Python нам потребуется интерпретатор. Для его установки перейдем на страницу
https://www.python.org/downloads/ и найдем ссылку на загрузку последней версии языка:
По нажатию на кнопку будет загружен соответствующей текущей ОС установщик Python. Следует учитывать, что Windows 7 и более ранние версии не поддерживаются.
На ОС Windows при запуске инсталлятора запускает окно мастера установки:
Здесь мы можем задать путь, по которому будет устанавливаться интерпретатор. Оставим его по умолчанию, то есть
C:\Users\[имя_пользователя]\AppData\Local\Programs\Python\Python312\.
Кроме того, в самом низу отметим флажок «Add Python 3.12 to PATH», чтобы добавить путь к интерпретатору в переменные среды.
После этого мы можем проверить установку Python и его версию, запустив в командной строке/треминале команду python —version
C:\Users\eugen>python --version Python 3.12.1 C:\Users\eugen>
Запуск интерпретатора
После установки интерпретатора, как было описано в прошлой теме, мы можем начать создавать приложения на Python. Итак, создадим первую простенькую программу.
Если при установке не был изменен адрес, то на Windows Python по умолчанию устанавливается по пути
C:\Users\[имя_пользователя]\AppData\Local\Programs\Python\Python[номер_версии]\ и представляет файл под названием python.exe.
Запустим интерпретатор и введем в него следующую строку:
print("hello metanit.com")
И консоль выведет строку «hello metanit.com»:
Python 3.12.0 (tags/v3.12.0:0fb18b0, Oct 2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> print("hello metanit.com") hello metanit.com >>>
Для этой программы использовалась функция print(), которая выводит некоторую строку на консоль.
Создание файла программы
В реальности, как правило, программы определяются во внешних файлах-скриптах и затем передаются интерпретатору на выполнение. Поэтому создадим файл программы.
Для этого на диске C или где-нибудь в другом месте файловой системы определим для скриптов папку python. А в этой папке создадим новый текстовый файл, который
назовем hello.py. По умолчанию файлы с кодом на языке Python, как правило, имеют расширение py.
Откроем этот файл в любом текстовом редакторе и добавим в него следующий код:
name = input("Введите имя: ") print("Привет,", name)
Скрипт состоит из двух строк. Первая строка с помощью функции input() ожидает ввода пользователем своего имени. Введенное
имя затем попадает в переменную name
.
Вторая строка с помощью функции print()
выводит приветствие вместе с введенным именем.
Теперь запустим командную строку/терминал и с помощью команды cd перейдем к папке, где находится файл с исходным кодом hello.py (например, в моем случае это папка C:\python).
cd c:\python
Далее вначале введем полный путь к интерпретатору, а затем полный путь к файлу скрипта. К примеру, в моем случае в консоль надо будет вести:
C:\Users\eugen\AppData\Local\Programs\Python\Python312\python.exe hello.py
Но если при установке была указана опция «Add Python 3.12 to PATH», то есть путь к интерпретатору Python был добавлен в переменные среды, то вместо полного пути к интерпретатору можно просто написать python:
python hello.py
Либо даже можно сократить:
py hello.py
Варианты с обоими способами запуска:
Microsoft Windows [Version 10.0.22621.2361] (c) Корпорация Майкрософт (Microsoft Corporation). Все права защищены. C:\Users\eugen>cd c:\python c:\python>C:\Users\eugen\AppData\Local\Programs\Python\Python312\python.exe hello.py Введите имя: Eugene Привет, Eugene c:\python>python hello.py Введите имя: Tom Привет, Tom c:\python>py hello.py Введите имя: Bob Привет, Bob c:\python>
В итоге программа выведет приглашение к вводу имени, а затем приветствие.