Gui приложение для windows

Время на прочтение33 мин

Количество просмотров14K

Привет, Хабр! Меня зовут Леша Жиряков, я техлид бэкенд-команды витрины KION, а еще в МТС я возглавляю Python-гильдию.

В 2025 году Python остается одним из самых популярных языков программирования, а его возможности для создания графических интерфейсов (GUI) продолжают радовать разработчиков. В этой подборке — шесть лучших инструментов, которые выделяются функциями, активностью сообщества и фишками. Погружаемся в мир кнопок, окон и виджетов — от проверенной классики до ярких новичков GitHub.

Tkinter

Tkinter как Python-интерфейс разработали в начале 1990-х, в основном при участии Гвидо ван Россума (Guido van Rossum) и других ранних разработчиков Python. В апреле 2025 года стандартная версия Python (3.12+) использует Tcl/Tk 8.6.13.

Что касается возможностей, то это встроенная библиотека Python для создания графических интерфейсов. Поэтому, в отличие от других библиотек из подборки, я не указываю версию, коммиты и звезды. Она дает вам набор виджетов (кнопки, текстовые поля, метки, меню и так далее) и менеджеры геометрии (pack, grid, place) для организации интерфейса. Поскольку база — Tcl/Tk, то библиотека кроссплатформенная (Windows, macOS, Linux). Tkinter прост в использовании, дополнительных установок нет, а это делает его идеальным для начинающих юзеров и быстрых прототипов. Еще включает модуль ttk (Themed Tk), который добавляет современные виджеты с нативным стилем.

Достоинства:

  • Встроенность: не нужно ничего устанавливать дополнительно, идет с Python.

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

  • Кроссплатформенность: работает везде, где есть Python и Tcl/Tk.

  • Стабильность: зрелая и проверенная временем библиотека.

  • Мощные виджеты: текстовые поля и канвас (Canvas) предлагают гибкость для сложных задач.

Недостатки:

  • Устаревший внешний вид: классические виджеты Tkinter выглядят старомодно без ttk.

  • Несколько ограниченные возможности: нет продвинутых современных виджетов — например, встроенного браузера или комплексных графиков.

В целом, это надежный выбор для не очень сложных GUI-проектов в 2025 году. Особенно если вы цените скорость разработки и не хотите возиться с зависимостями. Для новичков или прототипирования — идеально, но для сложных современных интерфейсов стоит рассмотреть альтернативы. О них — ниже.

В каких проектах используется:

  • IDLE (Python’s Integrated Development Environment) — встроенная IDE Python, которая полностью создана с использованием Tkinter.

  • Thonny — это IDE для Python, ориентированная на образовательные цели, полностью построенная с использованием Tkinter.

  • Matplotlib (интерфейс TkAgg) — популярная библиотека для визуализации данных в Python. Использует Tkinter как один из бэкендов для интерактивных графиков.

  • Pillow (PIL Fork) — популярная библиотека для обработки изображений, включает компоненты для интеграции с Tkinter.

  • Pmw (Python Mega Widgets) — библиотека высокоуровневых виджетов, построенная поверх Tkinter.

  • SymPy (Plotting с Tkinter) — библиотека SymPy для символьных вычислений использует Tkinter в своем модуле построения графиков.

Пример использования:

import tkinter as tk
from tkinter import ttk, messagebox, filedialog

class TkinterDemoApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Демонстрация Tkinter виджетов")
        self.root.geometry("800x600")
        self.root.resizable(True, True)
        
        # Настройка стилей
        self.style = ttk.Style()
        self.style.theme_use('clam')
        
        self.create_widgets()
    
    def create_widgets(self):
        # Создаем основную структуру
        self.main_frame = ttk.Frame(self.root, padding="10")
        self.main_frame.pack(fill=tk.BOTH, expand=True)
        
        # Панель вкладок
        self.notebook = ttk.Notebook(self.main_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # Вкладка "Основные виджеты"
        self.basic_tab = ttk.Frame(self.notebook)
        self.notebook.add(self.basic_tab, text="Основные")
        self.create_basic_widgets()
        
        # Вкладка "Продвинутые виджеты"
        self.advanced_tab = ttk.Frame(self.notebook)
        self.notebook.add(self.advanced_tab, text="Продвинутые")
        self.create_advanced_widgets()
        
        # Вкладка "Действия"
        self.actions_tab = ttk.Frame(self.notebook)
        self.notebook.add(self.actions_tab, text="Действия")
        self.create_actions_widgets()
    
    def create_basic_widgets(self):
        """Создание основных виджетов"""
        frame = ttk.LabelFrame(self.basic_tab, text="Базовые элементы", padding="10")
        frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # Метка и поле ввода
        ttk.Label(frame, text="Имя пользователя:").grid(row=0, column=0, sticky=tk.W, pady=2)
        self.username_entry = ttk.Entry(frame)
        self.username_entry.grid(row=0, column=1, sticky=tk.EW, padx=5, pady=2)
        
        # Пароль
        ttk.Label(frame, text="Пароль:").grid(row=1, column=0, sticky=tk.W, pady=2)
        self.password_entry = ttk.Entry(frame, show="*")
        self.password_entry.grid(row=1, column=1, sticky=tk.EW, padx=5, pady=2)
        
        # Чекбоксы
        self.remember_var = tk.BooleanVar()
        ttk.Checkbutton(frame, text="Запомнить меня", variable=self.remember_var).grid(
            row=2, column=0, columnspan=2, sticky=tk.W, pady=5)
        
        # Радиокнопки
        ttk.Label(frame, text="Выберите вариант:").grid(row=3, column=0, sticky=tk.W, pady=2)
        self.radio_var = tk.StringVar(value="option1")
        ttk.Radiobutton(frame, text="Вариант 1", value="option1", variable=self.radio_var).grid(
            row=4, column=0, sticky=tk.W)
        ttk.Radiobutton(frame, text="Вариант 2", value="option2", variable=self.radio_var).grid(
            row=4, column=1, sticky=tk.W)
        
        # Выпадающий список
        ttk.Label(frame, text="Выберите город:").grid(row=5, column=0, sticky=tk.W, pady=2)
        self.city_combo = ttk.Combobox(frame, values=["Москва", "Санкт-Петербург", "Новосибирск", "Екатеринбург"])
        self.city_combo.grid(row=5, column=1, sticky=tk.EW, padx=5, pady=2)
        self.city_combo.current(0)
        
        # Шкала (Slider)
        ttk.Label(frame, text="Уровень:").grid(row=6, column=0, sticky=tk.W, pady=2)
        self.scale = ttk.Scale(frame, from_=0, to=100, orient=tk.HORIZONTAL)
        self.scale.grid(row=6, column=1, sticky=tk.EW, padx=5, pady=2)
        
        # Настройка веса колонок для правильного растягивания
        frame.columnconfigure(1, weight=1)
    
    def create_advanced_widgets(self):
        """Создание продвинутых виджетов"""
        frame = ttk.LabelFrame(self.advanced_tab, text="Продвинутые элементы", padding="10")
        frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # Дерево (Treeview)
        self.tree = ttk.Treeview(frame, columns=("name", "age", "city"), show="headings")
        self.tree.heading("name", text="Имя")
        self.tree.heading("age", text="Возраст")
        self.tree.heading("city", text="Город")
        
        # Добавляем данные
        self.tree.insert("", tk.END, values=("Иван Иванов", 25, "Москва"))
        self.tree.insert("", tk.END, values=("Петр Петров", 30, "Санкт-Петербург"))
        self.tree.insert("", tk.END, values=("Анна Сидорова", 22, "Новосибирск"))
        
        self.tree.grid(row=0, column=0, columnspan=2, sticky=tk.NSEW, pady=5)
        
        # Прогресс бар
        self.progress = ttk.Progressbar(frame, orient=tk.HORIZONTAL, length=200, mode="determinate")
        self.progress.grid(row=1, column=0, columnspan=2, sticky=tk.EW, pady=5)
        
        # Панель вкладок внутри вкладки
        sub_notebook = ttk.Notebook(frame)
        sub_notebook.grid(row=2, column=0, columnspan=2, sticky=tk.NSEW, pady=5)
        
        tab1 = ttk.Frame(sub_notebook)
        tab2 = ttk.Frame(sub_notebook)
        
        sub_notebook.add(tab1, text="Вкладка 1")
        sub_notebook.add(tab2, text="Вкладка 2")
        
        ttk.Label(tab1, text="Это содержимое первой вкладки").pack(pady=20)
        ttk.Label(tab2, text="Это содержимое второй вкладки").pack(pady=20)
        
        # Настройка веса для правильного растягивания
        frame.columnconfigure(0, weight=1)
        frame.rowconfigure(0, weight=1)
    
    def create_actions_widgets(self):
        """Создание виджетов для выполнения действий"""
        frame = ttk.LabelFrame(self.actions_tab, text="Действия", padding="10")
        frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # Кнопки
        ttk.Button(frame, text="Показать сообщение", command=self.show_message).grid(
            row=0, column=0, sticky=tk.EW, padx=5, pady=5)
        
        ttk.Button(frame, text="Запустить прогресс", command=self.start_progress).grid(
            row=0, column=1, sticky=tk.EW, padx=5, pady=5)
        
        ttk.Button(frame, text="Открыть файл", command=self.open_file).grid(
            row=1, column=0, sticky=tk.EW, padx=5, pady=5)
        
        ttk.Button(frame, text="Сохранить файл", command=self.save_file).grid(
            row=1, column=1, sticky=tk.EW, padx=5, pady=5)
        
        # Текстовое поле с прокруткой
        self.text = tk.Text(frame, wrap=tk.WORD, height=10)
        self.scroll = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.text.yview)
        self.text.configure(yscrollcommand=self.scroll.set)
        
        self.text.grid(row=2, column=0, columnspan=2, sticky=tk.NSEW, pady=5)
        self.scroll.grid(row=2, column=2, sticky=tk.NS, pady=5)
        
        # Настройка веса для правильного растягивания
        frame.columnconfigure(0, weight=1)
        frame.columnconfigure(1, weight=1)
        frame.rowconfigure(2, weight=1)
    
    def show_message(self):
        """Показать диалоговое окно с информацией"""
        username = self.username_entry.get()
        city = self.city_combo.get()
        level = self.scale.get()
        
        message = f"Имя: {username}\nГород: {city}\nУровень: {level:.0f}"
        messagebox.showinfo("Информация", message)
    
    def start_progress(self):
        """Запустить анимацию прогресс-бара"""
        self.progress["value"] = 0
        self.root.after(100, self.update_progress)
    
    def update_progress(self):
        """Обновить прогресс-бар"""
        if self.progress["value"] < 100:
            self.progress["value"] += 5
            self.root.after(100, self.update_progress)
    
    def open_file(self):
        """Открыть диалог выбора файла"""
        file_path = filedialog.askopenfilename(
            title="Открыть файл",
            filetypes=(("Текстовые файлы", "*.txt"), ("Все файлы", "*.*"))
        )
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    content = file.read()
                    self.text.delete(1.0, tk.END)
                    self.text.insert(tk.END, content)
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось открыть файл:\n{str(e)}")
    
    def save_file(self):
        """Открыть диалог сохранения файла"""
        file_path = filedialog.asksaveasfilename(
            title="Сохранить файл",
            defaultextension=".txt",
            filetypes=(("Текстовые файлы", "*.txt"), ("Все файлы", "*.*"))
        )
        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as file:
                    file.write(self.text.get(1.0, tk.END))
                messagebox.showinfo("Успех", "Файл успешно сохранен")
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось сохранить файл:\n{str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = TkinterDemoApp(root)
    root.mainloop()

Что получилось:

Qt

Разработка Qt началась в 1991 году в норвежской компании Trolltech, основанной Хаавардом Нордом и Эйриком Чумбсом. Первая стабильная версия фреймворка вышла в 1995 году. В 2008 году Trolltech была приобретена Nokia, позже Qt перешел к Digia, а с 2014 года поддерживается независимой компанией The Qt Company. Фреймворк изначально разрабатывался для кроссплатформенных приложений на C++ и стал стандартом для создания графических интерфейсов.

  • Количество звезд на GitHub: 2,7k.

  • Коммиты: свыше 72k.

  • Последняя стабильная версия: 6.9.0.

  • Дата последнего релиза: 8 апреля 2025 года.

  • Дата последнего коммита: 17 апреля 2025 года.

Qt — кроссплатформенный фреймворк с открытым исходным кодом (GPL v3). Его можно использовать бесплатно. Но если вы планируете создавать приложение, не делясь исходным кодом, придется приобрести коммерческую лицензию. Предназначен Qt для создания высокопроизводительных графических интерфейсов. Работает на Windows, macOS, Linux, Android, iOS, встраиваемых системах и даже WebAssembly. Использует C++ и OpenGL для рендеринга, обеспечивая быструю работу и нативный вид приложений. Предлагает обширный набор виджетов, поддержку 2D/3D-графики, анимаций и инструмент Qt Designer для визуального дизайна интерфейсов. Подходит для десктопных программ, мобильных приложений, автомобильных систем и IoT-устройств.

Достоинства:

  • Кроссплатформенность: один код для множества платформ с нативным стилем интерфейса.

  • Производительность: благодаря C++ и OpenGL фреймворк справляется с тяжелыми задачами.

  • Инструменты: Qt Designer и QML упрощают создание сложных и динамичных интерфейсов.

  • Экосистема: поддержка сетей, баз данных, мультимедиа и 3D-графики в одном пакете.

Недостатки:

  • Сложность: обилие API и возможностей требует времени на освоение.

  • Размер сборок: зависимости Qt увеличивают объем приложений.

  • Настройка: сборка и установка могут быть сложными для специфичных платформ.

В 2025 году Qt остается прекрасным инструментом для разработчиков разных приложений — от корпоративного ПО и медиацентров до встраиваемых систем в автомобилях и IoT. Все хорошо с библиотекой, но она не так проста в изучении.

В каких проектах используется:

  • VLC Media Player — один из популярнейших медиаплееров, полностью построен на Qt.

  • Telegram Desktop официальный десктопный клиент Telegram использует Qt.

  • KDE Plasma — вся экосистема KDE, включая рабочий стол Plasma и приложения. Построена на Qt.

  • Autodesk Maya — профессиональное ПО для 3D-моделирования использует Qt для своего интерфейса.

  • Google Earth использует Qt для десктопной версии приложения.

  • Blender использует Qt для своего файлового браузера.

  • Spotify (ранние версии) — десктопный клиент Spotify ранее был построен на Qt.

  • Wolfram Mathematica — система компьютерной алгебры Mathematica использует Qt.

  • Scribus — профессиональное приложение для верстки с открытым исходным кодом.

  • LumaFusion — профессиональный мобильный видеоредактор, недавно портированный на Qt для поддержки новых платформ.

Пример использования:

import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                            QHBoxLayout, QLabel, QPushButton, QLineEdit,
                            QComboBox, QCheckBox, QRadioButton, QSlider,
                            QProgressBar, QSpinBox, QDoubleSpinBox, QTextEdit,
                            QListWidget, QTabWidget, QMessageBox, QInputDialog)
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtGui import QIcon, QPixmap


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        
        self.setWindowTitle("PyQt6 Widgets Demo")
        self.setGeometry(100, 100, 800, 600)
        
        # Центральный виджет и основной layout
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout()
        central_widget.setLayout(main_layout)
        
        # Создаем вкладки для разных групп виджетов
        self.tabs = QTabWidget()
        main_layout.addWidget(self.tabs)
        
        # Вкладка с базовыми виджетами
        self.create_basic_widgets_tab()
        # Вкладка с виджетами ввода
        self.create_input_widgets_tab()
        # Вкладка с индикаторами
        self.create_indicator_widgets_tab()
        # Вкладка с контейнерами
        self.create_container_widgets_tab()
        
        # Статус бар
        self.statusBar().showMessage("Готово")
    
    def create_basic_widgets_tab(self):
        """Создаем вкладку с базовыми виджетами"""
        tab = QWidget()
        layout = QVBoxLayout()
        tab.setLayout(layout)
        
        # Метка с изображением
        label = QLabel("Демонстрация PyQt6 Widgets")
        label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        font = label.font()
        font.setPointSize(16)
        label.setFont(font)
        layout.addWidget(label)
        
        # Изображение
        image_label = QLabel()
        pixmap = QPixmap(":images/python-logo.png")  # Используем встроенный ресурс
        if not pixmap.isNull():
            image_label.setPixmap(pixmap.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio))
        image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(image_label)
        
        # Кнопки
        btn_layout = QHBoxLayout()
        
        btn1 = QPushButton("Обычная кнопка")
        btn1.clicked.connect(self.on_button_click)
        btn_layout.addWidget(btn1)
        
        btn2 = QPushButton(QIcon(":images/python-logo.png"), "Кнопка с иконкой")
        btn2.clicked.connect(self.on_button_click)
        btn_layout.addWidget(btn2)
        
        btn3 = QPushButton("Отключенная кнопка")
        btn3.setEnabled(False)
        btn_layout.addWidget(btn3)
        
        layout.addLayout(btn_layout)
        
        # Горизонтальная линия
        layout.addWidget(QLabel("<hr>"), 1)
        
        # Текстовое поле
        self.line_edit = QLineEdit()
        self.line_edit.setPlaceholderText("Введите текст здесь...")
        self.line_edit.textChanged.connect(self.on_text_changed)
        layout.addWidget(self.line_edit)
        
        self.tabs.addTab(tab, "Базовые виджеты")
    
    def create_input_widgets_tab(self):
        """Создаем вкладку с виджетами ввода"""
        tab = QWidget()
        layout = QVBoxLayout()
        tab.setLayout(layout)
        
        # Комбобокс (выпадающий список)
        combo_label = QLabel("Выберите вариант:")
        layout.addWidget(combo_label)
        
        self.combo_box = QComboBox()
        self.combo_box.addItems(["Вариант 1", "Вариант 2", "Вариант 3", "Вариант 4"])
        self.combo_box.currentIndexChanged.connect(self.on_combo_changed)
        layout.addWidget(self.combo_box)
        
        # Чекбоксы
        check_label = QLabel("Выберите опции:")
        layout.addWidget(check_label)
        
        self.check1 = QCheckBox("Опция 1")
        self.check1.stateChanged.connect(self.on_check_changed)
        layout.addWidget(self.check1)
        
        self.check2 = QCheckBox("Опция 2")
        self.check2.stateChanged.connect(self.on_check_changed)
        layout.addWidget(self.check2)
        
        # Радио кнопки
        radio_label = QLabel("Выберите один вариант:")
        layout.addWidget(radio_label)
        
        self.radio1 = QRadioButton("Вариант A")
        self.radio1.toggled.connect(self.on_radio_toggled)
        layout.addWidget(self.radio1)
        
        self.radio2 = QRadioButton("Вариант B")
        self.radio2.toggled.connect(self.on_radio_toggled)
        layout.addWidget(self.radio2)
        
        self.radio3 = QRadioButton("Вариант C")
        self.radio3.toggled.connect(self.on_radio_toggled)
        layout.addWidget(self.radio3)
        
        # Слайдер
        slider_label = QLabel("Регулировка значения:")
        layout.addWidget(slider_label)
        
        self.slider = QSlider(Qt.Orientation.Horizontal)
        self.slider.setMinimum(0)
        self.slider.setMaximum(100)
        self.slider.setValue(50)
        self.slider.valueChanged.connect(self.on_slider_changed)
        layout.addWidget(self.slider)
        
        # Спинбоксы
        spin_layout = QHBoxLayout()
        
        self.spin_box = QSpinBox()
        self.spin_box.setRange(0, 100)
        self.spin_box.setValue(25)
        self.spin_box.valueChanged.connect(self.on_spin_changed)
        spin_layout.addWidget(self.spin_box)
        
        self.double_spin = QDoubleSpinBox()
        self.double_spin.setRange(0, 10)
        self.double_spin.setSingleStep(0.1)
        self.double_spin.setValue(2.5)
        self.double_spin.valueChanged.connect(self.on_double_spin_changed)
        spin_layout.addWidget(self.double_spin)
        
        layout.addLayout(spin_layout)
        
        self.tabs.addTab(tab, "Виджеты ввода")
    
    def create_indicator_widgets_tab(self):
        """Создаем вкладку с индикаторами"""
        tab = QWidget()
        layout = QVBoxLayout()
        tab.setLayout(layout)
        
        # Прогресс бар
        progress_label = QLabel("Прогресс бар:")
        layout.addWidget(progress_label)
        
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setValue(0)
        layout.addWidget(self.progress_bar)
        
        # Кнопка для запуска прогресса
        self.progress_btn = QPushButton("Запустить прогресс")
        self.progress_btn.clicked.connect(self.start_progress)
        layout.addWidget(self.progress_btn)
        
        # Таймер для прогресс бара
        self.progress_timer = QTimer()
        self.progress_timer.timeout.connect(self.update_progress)
        self.progress_value = 0
        
        # Метка для отображения значений
        self.value_label = QLabel("Значения будут отображаться здесь")
        self.value_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(self.value_label)
        
        self.tabs.addTab(tab, "Индикаторы")
    
    def create_container_widgets_tab(self):
        """Создаем вкладку с контейнерными виджетами"""
        tab = QWidget()
        layout = QVBoxLayout()
        tab.setLayout(layout)
        
        # Текстовый редактор
        text_edit_label = QLabel("Текстовый редактор:")
        layout.addWidget(text_edit_label)
        
        self.text_edit = QTextEdit()
        self.text_edit.setPlaceholderText("Введите текст здесь...")
        layout.addWidget(self.text_edit)
        
        # Кнопки для работы с текстом
        text_btn_layout = QHBoxLayout()
        
        clear_btn = QPushButton("Очистить")
        clear_btn.clicked.connect(self.text_edit.clear)
        text_btn_layout.addWidget(clear_btn)
        
        get_text_btn = QPushButton("Показать текст")
        get_text_btn.clicked.connect(self.show_text)
        text_btn_layout.addWidget(get_text_btn)
        
        layout.addLayout(text_btn_layout)
        
        # Список
        list_label = QLabel("Список элементов:")
        layout.addWidget(list_label)
        
        self.list_widget = QListWidget()
        self.list_widget.addItems(["Элемент 1", "Элемент 2", "Элемент 3"])
        self.list_widget.itemClicked.connect(self.on_list_item_clicked)
        layout.addWidget(self.list_widget)
        
        # Кнопки для работы со списком
        list_btn_layout = QHBoxLayout()
        
        add_item_btn = QPushButton("Добавить")
        add_item_btn.clicked.connect(self.add_list_item)
        list_btn_layout.addWidget(add_item_btn)
        
        remove_item_btn = QPushButton("Удалить")
        remove_item_btn.clicked.connect(self.remove_list_item)
        list_btn_layout.addWidget(remove_item_btn)
        
        layout.addLayout(list_btn_layout)
        
        self.tabs.addTab(tab, "Контейнеры")
    
    # Методы обработки событий
    def on_button_click(self):
        sender = self.sender()
        QMessageBox.information(self, "Кнопка нажата", f"Нажата кнопка: {sender.text()}")
    
    def on_text_changed(self, text):
        self.statusBar().showMessage(f"Текст изменен: {text}", 2000)
    
    def on_combo_changed(self, index):
        self.value_label.setText(f"Выбран комбобокс: {self.combo_box.currentText()} (индекс {index})")
    
    def on_check_changed(self, state):
        sender = self.sender()
        checked = "включена" if state else "отключена"
        self.value_label.setText(f"Опция {sender.text()} {checked}")
    
    def on_radio_toggled(self, checked):
        if checked:
            sender = self.sender()
            self.value_label.setText(f"Выбран радио-вариант: {sender.text()}")
    
    def on_slider_changed(self, value):
        self.value_label.setText(f"Значение слайдера: {value}")
    
    def on_spin_changed(self, value):
        self.value_label.setText(f"Значение спинбокса: {value}")
    
    def on_double_spin_changed(self, value):
        self.value_label.setText(f"Значение double спинбокса: {value:.2f}")
    
    def start_progress(self):
        if not self.progress_timer.isActive():
            self.progress_value = 0
            self.progress_bar.setValue(0)
            self.progress_timer.start(100)
            self.progress_btn.setText("Остановить прогресс")
        else:
            self.progress_timer.stop()
            self.progress_btn.setText("Запустить прогресс")
    
    def update_progress(self):
        self.progress_value += 1
        self.progress_bar.setValue(self.progress_value)
        
        if self.progress_value >= 100:
            self.progress_timer.stop()
            self.progress_btn.setText("Запустить прогресс")
            QMessageBox.information(self, "Готово", "Прогресс завершен!")
    
    def show_text(self):
        text = self.text_edit.toPlainText()
        if text:
            QMessageBox.information(self, "Текст", text)
        else:
            QMessageBox.warning(self, "Ошибка", "Текст не введен!")
    
    def on_list_item_clicked(self, item):
        self.value_label.setText(f"Выбран элемент списка: {item.text()}")
    
    def add_list_item(self):
        text, ok = QInputDialog.getText(self, "Добавить элемент", "Введите текст элемента:")
        if ok and text:
            self.list_widget.addItem(text)
    
    def remove_list_item(self):
        current_item = self.list_widget.currentItem()
        if current_item:
            self.list_widget.takeItem(self.list_widget.row(current_item))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    # Устанавливаем стиль для приложения
    app.setStyle("Fusion")
    
    window = MainWindow()
    window.show()
    
    sys.exit(app.exec())

Что получилось:Особенности примера:

  1. Структура приложения:

    • Использование QMainWindow как основного окна.

    • Организация кода в методы для создания разных вкладок.

    • Чистая архитектура с разделением на UI и логику.

  2. Демонстрация виджетов:

    • Базовые: QLabel, QPushButton, QLineEdit.

    • Ввод данных: QComboBox, QCheckBox, QRadioButton, QSlider, QSpinBox.

    • Индикаторы: QProgressBar с QTimer.

    • Контейнеры: QTextEdit, QListWidget, QTabWidget.

  3. Обработка событий:

    • Подключение сигналов к слотам.

    • Примеры обработки кликов, изменений значений и т. д.

  4. Дополнительные возможности:

    • Использование QMessageBox для диалогов.

    • Работа с QInputDialog.

    • Статус-бар.

Kivy

Kivy появилась в феврале 2011 года как продолжение проекта PyMT (Python MultiTouch). Основные разработчики — команда Kivy Organization, включая таких участников, как Матье Вирбел (Mathieu Virbel, известен как @tito). Проект эволюционировал из PyMT, созданного для мультитач-приложений, в более профессиональный и универсальный фреймворк.

  • Количество звезд на GitHub: 18,2k.

  • Коммиты: почти 13k.

  • Последняя стабильная версия: 2.3.1.

  • Дата последнего релиза: 26 декабря 2024 года.

  • Дата последнего коммита: 8 апреля 2025 года.

Это кроссплатформенная библиотека Python с открытым исходным кодом, предназначенная для создания приложений с естественным пользовательским интерфейсом (NUI), включая поддержку мультитач. Работает на Windows, macOS, Linux, Android, iOS и даже Raspberry Pi. Kivy использует OpenGL ES 2.0 для рендеринга графики, что обеспечивает высокую производительность и гибкость. Библиотека предлагает большой выбор виджетов и свой язык Kv, с помощью которого удобно описывать интерфейс отдельно от логики. Чаще всего ее используют для мобильных приложений, но она хорошо подходит и для десктопных программ, и для простых игр.

Достоинства:

  • Кроссплатформенность: один код для всех платформ — от десктопа до мобильных девайсов.

  • Мультитач: встроенная поддержка сенсорных интерфейсов, идеально для современных устройств.

  • Открытый код: лицензия MIT позволяет свободное использование и модификацию.

  • Графика: использование OpenGL дает высокую скорость и возможность создавать визуально насыщенные приложения.

Недостатки:

  • Не нативный вид: Kivy рисует собственные виджеты, что отличает их от системного стиля платформ (например, iOS или Windows).

  • Сложность установки: много зависимостей (SDL2, GStreamer и др.), что может затруднить настройку, особенно на Windows.

  • Крутая кривая обучения: новичкам может быть сложно освоить архитектуру Kivy.

  • Размер приложений: даже простые APK для Android могут быть больше 10 Мб из-за зависимостей.

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

В каких проектах используется:

  • Python для Android (Pydroid 3) — популярное мобильное приложение для программирования на Python, используемое миллионами пользователей.

  • KivyMD. Хотя это технически расширение Kivy, KivyMD — популярная библиотека компонентов в стиле Material Design, используемая во многих приложениях.

  • Buildozer — инструмент для упаковки приложений Kivy для мобильных платформ, разработанный создателями Kivy.

  • TouchViz — приложение от NASA для визуализации научных данных и проведения презентаций на больших сенсорных экранах.

  • Bord от Novation — приложение-компаньон для музыкального оборудования Novation Launchpad.

  • Plyer — библиотека платформенно-независимого доступа к возможностям различных устройств.

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

Пример использования:

Для запуска устанавливаем Kivy:

pip install kivy

Базовое приложение Kivy с основными виджетами:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.slider import Slider
from kivy.uix.switch import Switch
from kivy.uix.progressbar import ProgressBar
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window

class KivyWidgetsDemo(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.orientation = 'vertical'
        self.spacing = 15
        self.padding = 30
        
        # Устанавливаем фон
        with self.canvas.before:
            Color(0.95, 0.95, 0.95, 1)  # Серый фон
            self.rect = Rectangle(size=Window.size, pos=self.pos)
        
        # Привязка к изменению размера окна
        Window.bind(size=self._update_rect)
        
        # Добавляем виджеты
        self._add_widgets()
    
    def _update_rect(self, instance, value):
        self.rect.size = value
    
    def _add_widgets(self):
        """Добавление и настройка виджетов"""
        
        # Метка (Label)
        self.label = Label(
            text='Демонстрация виджетов Kivy',
            font_size='24sp',
            color=(0.1, 0.2, 0.6, 1),
            bold=True
        )
        self.add_widget(self.label)
        
        # Поле ввода (TextInput)
        self.text_input = TextInput(
            hint_text='Введите текст здесь',
            size_hint=(1, None),
            height=40,
            multiline=False
        )
        self.text_input.bind(text=self.on_text_change)
        self.add_widget(self.text_input)
        
        # Кнопка (Button)
        self.button = Button(
            text='Нажми меня',
            size_hint=(0.5, None),
            height=50,
            pos_hint={'center_x': 0.5},
            background_color=(0.4, 0.7, 0.9, 1)
        )
        self.button.bind(on_press=self.on_button_press)
        self.add_widget(self.button)
        
        # Переключатель (Switch)
        self.switch = Switch(
            active=False,
            pos_hint={'center_x': 0.5}
        )
        self.switch.bind(active=self.on_switch_active)
        self.add_widget(self.switch)
        
        # Слайдер (Slider)
        self.slider = Slider(
            min=0,
            max=100,
            value=30,
            step=1,
            size_hint=(1, None),
            height=40
        )
        self.slider.bind(value=self.on_slider_change)
        self.add_widget(self.slider)
        
        # Индикатор прогресса (ProgressBar)
        self.progress = ProgressBar(
            max=100,
            value=30,
            size_hint=(1, None),
            height=30
        )
        self.add_widget(self.progress)
        
        # Изображение (Image)
        self.image = Image(
            source='kivy-logo-black-512.png',  # Замените на свой файл
            size_hint=(1, None),
            height=150,
            allow_stretch=True
        )
        self.add_widget(self.image)
        
        # Обновление прогресс-бара по таймеру
        Clock.schedule_interval(self.update_progress, 0.1)
    
    def on_text_change(self, instance, value):
        self.label.text = f'Вы ввели: {value}'
    
    def on_button_press(self, instance):
        self.label.text = 'Кнопка нажата!'
        instance.text = 'Спасибо!'
    
    def on_switch_active(self, instance, value):
        status = 'включен' if value else 'выключен'
        self.label.text = f'Переключатель {status}'
    
    def on_slider_change(self, instance, value):
        self.progress.value = value
        self.label.text = f'Значение: {int(value)}'
    
    def update_progress(self, dt):
        if self.progress.value < 100:
            self.progress.value += 1
        else:
            self.progress.value = 0

class KivyDemoApp(App):
    def build(self):
        self.title = 'Kivy Widgets Demo'
        return KivyWidgetsDemo()

if __name__ == '__main__':
    KivyDemoApp().run()

Что получилось:Особенности примера:

  1. Структура приложения:

    • Четкое разделение на методы для лучшей читаемости.

    • Использование классов для организации кода.

  2. Основные виджеты:

    • Label — для отображения текста.

    • Button — интерактивная кнопка.

    • TextInput — поле для ввода текста.

    • Slider — ползунок для выбора значений.

    • Switch — переключатель.

    • ProgressBar — индикатор прогресса.

    • Image — отображение изображений.

  3. Профессиональные техники:

    • Обработка событий (bind).

    • Динамическое обновление через Clock.

    • Кастомизация внешнего вида.

    • Адаптация к разным размерам экрана.

  4. Дополнительные возможности:

    • Работа с canvas для кастомного фона.

    • Использование pos_hint и size_hint для адаптивности.

    • Изменение свойств виджетов динамически.

BeeWare Toga

Toga — часть проекта BeeWare, основанного Расселом Китом-Маге (Russell Keith-Magee) и другими участниками сообщества. Первая публичная версия появилась в 2014 году, но активная разработка началась позже, со значительными обновлениями после 2020 года.

  • Количество звезд на GitHub: 4,7k.

  • Коммиты: свыше 10k.

  • Последняя стабильная версия: 0.5.0.

  • Дата последнего релиза: 14 марта 2025 года.

  • Дата последнего коммита: 14 апреля 2025 года.

Toga — кроссплатформенный GUI-фреймворк из экосистемы BeeWare, разработанный для создания нативных приложений на Python. Он поддерживает Windows, macOS, Linux (GTK), Android, iOS и даже веб-приложения (на экспериментальном уровне) и консольные интерфейсы через разные бэкенды. Toga использует настоящие системные виджеты, поэтому приложения выглядят и работают как обычные программы на той платформе, где запускаются. Библиотеку легко установить через pip, а ее интерфейс одинаково хорошо работает везде. Главное удобство — можно писать один и тот же код для десктопа и мобильных устройств, а с помощью инструментов вроде Briefcase — еще и собирать готовые аппы.

Достоинства:

  • Нативность: использует системные виджеты, что делает интерфейс неотличимым от других приложений платформы.

  • Кроссплатформенность: поддержка множества платформ, включая мобильные, без изменения кода.

  • Простота установки: не требует компиляции расширений, достаточно стандартного pip install.

  • Интеграция с BeeWare: работает в связке с Briefcase для создания автономных приложений.

  • Python-ориентированность: API спроектирован с учетом особенностей языка, таких как генераторы и контекстные менеджеры.

Недостатки:

  • Небольшой набор виджетов: по сравнению с Qt или Tkinter, Toga предлагает меньше готовых компонентов.

  • Документация: скудная, что затрудняет обучение.

  • Производительность: нативность иногда идет в ущерб скорости из-за дополнительных абстракций.

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

Приложение BeeWare Toga с виджетами:

import toga
from toga.style import Pack
from toga.style.pack import CENTER, COLUMN, ROW


class TogaDemoApp(toga.App):
   def startup(self):
       """Создание и отображение главного окна с виджетами"""
       self.name = "Toga Demo App"

       # Создаем главное окно
       self.main_window = toga.MainWindow(title=self.name, size=(800, 600))

       # Основные виджеты будут организованы в ScrollContainer для поддержки больших форм
       main_box = toga.ScrollContainer(style=Pack(direction=COLUMN, margin=10))

       # Создаем вкладки
       self.tabs = toga.OptionContainer(style=Pack(flex=1))

       # Добавляем вкладки
       self.tabs.content.append("Основные", self.create_basic_widgets())
       self.tabs.content.append("Ввод данных", self.create_input_widgets())
       self.tabs.content.append("Списки", self.create_list_widgets())
       self.tabs.content.append("Действия", self.create_action_widgets())

       main_box.content = self.tabs

       # Устанавливаем содержимое главного окна
       self.main_window.content = main_box
       self.main_window.show()

   def create_basic_widgets(self):
       """Создаем вкладку с основными виджетами"""
       box = toga.Box(style=Pack(direction=COLUMN, margin=10))

       # Метка
       label = toga.Label(
           "Демонстрация виджетов BeeWare Toga",
           style=Pack(margin=(0, 5), font_size=16, text_align=CENTER),
       )
       box.add(label)

       # Разделитель
       box.add(toga.Divider(style=Pack(margin_top=10, margin_bottom=10)))

       # Кнопки
       btn_box = toga.Box(style=Pack(direction=ROW, margin=5))

       btn1 = toga.Button(
           "Обычная кнопка",
           on_press=self.on_button_press,
           style=Pack(margin=5, flex=1),
       )
       btn_box.add(btn1)

       btn2 = toga.Button(
           "Отключенная кнопка", enabled=False, style=Pack(margin=5, flex=1)
       )
       btn_box.add(btn2)

       box.add(btn_box)

       # Изображение (используем встроенную иконку)
       try:
           image = toga.Image("resources/icon.png")
           imageview = toga.ImageView(
               image, style=Pack(margin=10, width=100, height=100)
           )
           box.add(imageview)
       except FileNotFoundError:
           box.add(toga.Label("Изображение не найдено", style=Pack(color="red")))

       # Текстовое поле
       self.text_input = toga.TextInput(
           placeholder="Введите текст...", style=Pack(margin=5)
       )
       self.text_input.on_change = self.on_text_change
       box.add(self.text_input)

       # Многострочный текст
       self.multiline_text = toga.MultilineTextInput(
           placeholder="Многострочный текст...", style=Pack(margin=5, height=100)
       )

       box.add(self.multiline_text)

       return box

   def create_input_widgets(self):
       """Создаем вкладку с виджетами ввода данных"""
       box = toga.Box(style=Pack(direction=COLUMN, margin=10))

       # Чекбоксы
       self.checkbox1 = toga.Switch("Опция 1", on_change=self.on_switch_change)
       self.checkbox2 = toga.Switch(
           "Опция 2", value=True, on_change=self.on_switch_change
       )
       box.add(self.checkbox1)
       box.add(self.checkbox2)

       box.add(toga.Divider(style=Pack(margin_top=10, margin_bottom=10)))

       # Слайдер
       self.slider = toga.Slider(
           min=0,
           max=100,
           value=50,
           on_change=self.on_slider_change,
           style=Pack(margin=5),
       )
       box.add(toga.Label("Регулировка значения:", style=Pack(margin_left=5)))
       box.add(self.slider)
       self.slider_value_label = toga.Label("50", style=Pack(text_align=CENTER))
       box.add(self.slider_value_label)

       return box

   def create_list_widgets(self):
       """Создаем вкладку с виджетами списков"""
       box = toga.Box(style=Pack(direction=COLUMN, margin=10))

       # Простой список
       self.simple_list = toga.DetailedList(
           data=[
               {
                   "icon": toga.Icon("resources/icon.png"),
                   "title": "Элемент 1",
                   "subtitle": "",
               },
               {
                   "icon": toga.Icon("resources/icon.png"),
                   "title": "Элемент 2",
                   "subtitle": "",
               },
               {
                   "icon": toga.Icon("resources/icon.png"),
                   "title": "Элемент 3",
                   "subtitle": "",
               },
           ],
           on_select=self.on_list_select,
           style=Pack(margin=5, height=100),
       )
       box.add(toga.Label("Простой список:", style=Pack(margin_left=5)))
       box.add(self.simple_list)

       # Таблица (более сложный список)
       table_data = [
           ("Иван", 25, "Москва"),
           ("Мария", 30, "Санкт-Петербург"),
           ("Алексей", 28, "Новосибирск"),
       ]
       self.table = toga.Table(
           headings=["Имя", "Возраст", "Город"],
           data=table_data,
           on_select=self.on_table_select,
           style=Pack(margin=5, height=150),
       )
       box.add(toga.Label("Таблица:", style=Pack(margin_left=5)))
       box.add(self.table)

       # Дерево
       tree_data = {
           ("Россия",): [
               ("Москва", "Столица"),
               ("Санкт-Петербург", "Культурная столица"),
           ],
           ("США",): [
               ("Нью-Йорк", "Экономический центр"),
               ("Лос-Анджелес", "Киноиндустрия"),
           ],
       }
       self.tree = toga.Tree(
           headings=["Город", "Описание"],
           data=tree_data,
           on_select=self.on_tree_select,
           style=Pack(margin=5, height=150),
       )
       box.add(toga.Label("Дерево:", style=Pack(margin_left=5)))
       box.add(self.tree)

       return box

   def create_action_widgets(self):
       """Создаем вкладку с виджетами действий"""
       box = toga.Box(style=Pack(direction=COLUMN, margin=10))

       # Кнопки действий
       btn_box = toga.Box(style=Pack(direction=ROW, margin=5))

       # Кнопка для показа диалога
       dialog_btn = toga.Button(
           "Показать диалог", on_press=self.show_dialog, style=Pack(margin=5, flex=1)
       )
       btn_box.add(dialog_btn)

       # Кнопка для выбора файла
       file_btn = toga.Button(
           "Выбрать файл", on_press=self.select_file, style=Pack(margin=5, flex=1)
       )
       btn_box.add(file_btn)

       box.add(btn_box)

       # Индикатор выполнения
       self.progress_bar = toga.ProgressBar(
           max=100, value=0, running=False, style=Pack(margin=10)
       )
       box.add(self.progress_bar)

       # Кнопки управления прогрессом
       progress_btn_box = toga.Box(style=Pack(direction=ROW, margin=5))

       start_btn = toga.Button(
           "Старт", on_press=self.start_progress, style=Pack(margin=5, flex=1)
       )
       progress_btn_box.add(start_btn)

       stop_btn = toga.Button(
           "Стоп", on_press=self.stop_progress, style=Pack(margin=5, flex=1)
       )
       progress_btn_box.add(stop_btn)

       box.add(progress_btn_box)

       # WebView (браузер)
       self.webview = toga.WebView(style=Pack(margin=5, height=200))
       box.add(toga.Label("WebView:", style=Pack(margin_left=5)))
       box.add(self.webview)

       # Кнопка для загрузки URL
       url_box = toga.Box(style=Pack(direction=ROW, margin=5))

       self.url_input = toga.TextInput(
           placeholder="Введите URL",
           value="https://beeware.org",
           style=Pack(margin=5, flex=3),
       )
       url_box.add(self.url_input)

       load_btn = toga.Button(
           "Загрузить", on_press=self.load_url, style=Pack(margin=5, flex=1)
       )
       url_box.add(load_btn)

       box.add(url_box)

       return box

   # Обработчики событий
   def on_button_press(self, widget):
       self.main_window.info_dialog(
           "Уведомление", f"Кнопка '{widget.text}' была нажата!"
       )

   def on_text_change(self, widget):
       print(f"Текст изменен: {widget.value}")

   def on_switch_change(self, widget):
       state = "включена" if widget.value else "выключена"
       print(f"Опция '{widget.text}' {state}")

   def on_date_change(self, widget):
       selected_date = (
           widget.value.strftime("%d.%m.%Y") if widget.value else "не выбрана"
       )
       print(f"Выбрана дата: {selected_date}")

   def on_time_change(self, widget):
       selected_time = widget.value.strftime("%H:%M") if widget.value else "не выбрано"
       print(f"Выбрано время: {selected_time}")

   def on_slider_change(self, widget):
       self.slider_value_label.text = str(int(widget.value))
       print(f"Значение слайдера: {widget.value}")

   def on_list_select(self, widget):
       if widget.selection:
           print(f"Выбран элемент списка: {widget.selection}")

   def on_table_select(self, widget):
       if widget.selection:
           name, age, city = widget.selection
           print(f"Выбрана строка: {name}, {age} лет, {city}")

   def on_tree_select(self, widget):
       if widget.selection:
           city, desc = widget.selection
           print(f"Выбран узел дерева: {city} - {desc}")

   def show_dialog(self, widget):
       self.main_window.question_dialog(
           "Подтверждение",
           "Вы уверены, что хотите выполнить это действие?",
           on_result=self.dialog_result,
       )

   def dialog_result(self, widget, result):
       if result:
           print("Пользователь подтвердил действие")
       else:
           print("Пользователь отменил действие")

   def select_file(self, widget):
       try:
           file_path = self.main_window.open_file_dialog(
               title="Выберите файл", multiselect=False
           )
           if file_path:
               print(f"Выбран файл: {file_path}")
       except ValueError as exception:
           print(f"Ошибка выбора файла: {exception}")

   def start_progress(self, widget):
       self.progress_bar.start()
       print("Прогресс запущен")

   def stop_progress(self, widget):
       self.progress_bar.stop()
       print("Прогресс остановлен")

   def load_url(self, widget):
       url = self.url_input.value.strip()
       if url:
           if not url.startswith(("http://", "https://")):
               url = "https://" + url
           self.webview.url = url
           print(f"Загружаем URL: {url}")


def main():
   return TogaDemoApp(
       "BeeWare Toga Demo",
       app_id="beeware.toga.demo",
       app_name="Example",
       icon="resources/icon",
   )


if __name__ == "__main__":
   app = main()
   app.main_loop()

Что получилось:Особенности примера:

  1. Структура приложения:

    • Использование toga.App как базового класса.

    • Организация виджетов по вкладкам с помощью OptionContainer.

    • Чистая архитектура с разделением на UI и обработчики событий.

  2. Демонстрация виджетов:

    • Базовые: Label, Button, TextInput, MultilineTextInput, ImageView.

    • Ввод данных: Switch, DatePicker, TimePicker, Slider.

    • Списки: ListBox, Table, Tree.

    • Действия: ProgressBar, WebView.

    • Диалоги: info_dialog, question_dialog, open_file_dialog.

  3. Обработка событий:

    • Подключение обработчиков через on_press, on_change, on_select.

    • Примеры обработки различных событий виджетов.

  4. Особенности BeeWare Toga:

    • Кроссплатформенность (работает на Windows, macOS, Linux, мобильных платформах).

    • Нативный внешний вид на каждой платформе.

    • Простота создания пользовательских интерфейсов.

Для работы этого примера вам потребуется создать папку resources и поместить туда файл иконки icon.png.

В каких проектах используется BeeWare Toga для GUI:

  • Cricket — это графический инструмент для запуска тестов в Python, разработанный командой BeeWare. Он использует Toga для своего пользовательского интерфейса.

  • Podium — это презентационный инструмент, работающий на Toga, также разработанный командой BeeWare.

  • Travel Tips — пример полноценного мобильного приложения, созданного с помощью Toga и опубликованного в качестве примера реального приложения командой BeeWare.

BeeWare Toga — относительно молодой фреймворк по сравнению с PyQt или wxPython, поэтому количество крупных сторонних проектов, использующих его, ограничено. Большинство проектов — часть экосистемы BeeWare или экспериментальные приложения.

WxPython

Python-обертка для кроссплатформенной библиотеки wxWidgets, написанной на C++. wxPython создал Джулиан Смарт (Julian Smart) в 1992 году. 

  • Количество звезд на GitHub: 1,4k.

  • Коммиты: свыше 5,9k.

  • Последняя стабильная версия: 4.2.2.

  • Дата последнего релиза: 10 апреля 2025 года.

  • Дата последнего коммита: 12 апреля 2025 года.

wxPython дает доступ к возможностям wxWidgets и позволяет создавать нативные интерфейсы для Windows, macOS и Linux. Она использует системные элементы управления, поэтому приложения выглядят привычно на каждой платформе. В библиотеке есть все — от простых кнопок до сложных диалогов и графических областей. Подходит как для небольших утилит, так и для крупных проектов вроде редакторов или IDE. Устанавливается через pip, но важно учитывать поддержку нужной версии Python (начиная с 3.8).

Кстати, репозиторий wxPython на GitHub называется Phoenix (Феникс), потому что это имя было выбрано для новой версии wxPython — полной переработки оригинального проекта. 

Достоинства:

  • Нативный вид: интерфейсы выглядят как часть операционной системы.

  • Богатый функционал: огромное количество виджетов и инструментов для сложных приложений.

  • Кроссплатформенность: единый код работает на всех основных ОС.

  • Сообщество: долгая история и активная поддержка разработчиков;

  • Гибкость: поддерживает кастомизацию и сложные макеты.

Недостатки:

  • Сложность: крутая кривая обучения из-за обилия классов и методов.

  • Размер: приложения могут быть тяжелее из-за зависимостей wxWidgets.

  • Устаревшая документация: некоторые части не обновлялись годами.

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

В каких проектах используется:

  • Dropbox — облачное хранилище данных. 

  • BitTorrent — оригинальный клиент BitTorrent, разработанный Брэмом Коэном. Использовал wxPython для своего интерфейса.

  • Диск Google — настольный клиент для системы облачного хранения данных Google.

  • Editra — это кроссплатформенный текстовый редактор с подсветкой синтаксиса для множества языков программирования. Проект был создан Криком Мацциотти (Cody Precord), одним из основных разработчиков wxPython.

Примеры использования:

import wx

class MyFrame(wx.Frame):
    def __init__(self, parent, title):
        super(MyFrame, self).__init__(parent, title=title, size=(600, 400))
        
        self.panel = wx.Panel(self)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        
        # Создаем различные виджеты
        self.create_widgets()
        
        # Настраиваем обработчики событий
        self.bind_events()
        
        self.panel.SetSizer(self.sizer)
        self.Centre()
        self.Show()
    
    def create_widgets(self):
        """Создание и размещение виджетов на панели"""
        
        # Статический текст
        self.title = wx.StaticText(self.panel, label="Демонстрация wxPython виджетов")
        self.title.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
        self.sizer.Add(self.title, 0, wx.ALL | wx.CENTER, 10)
        
        # Поле ввода текста
        self.text_ctrl = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE, size=(400, 100))
        self.sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 10)
        
        # Кнопки
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        self.btn_ok = wx.Button(self.panel, label="OK")
        self.btn_cancel = wx.Button(self.panel, label="Cancel")
        self.btn_clear = wx.Button(self.panel, label="Clear")
        
        button_sizer.Add(self.btn_ok, 0, wx.ALL, 5)
        button_sizer.Add(self.btn_cancel, 0, wx.ALL, 5)
        button_sizer.Add(self.btn_clear, 0, wx.ALL, 5)
        
        self.sizer.Add(button_sizer, 0, wx.CENTER)
        
        # Чекбокс
        self.checkbox = wx.CheckBox(self.panel, label="Включить опцию")
        self.sizer.Add(self.checkbox, 0, wx.ALL, 10)
        
        # Радио-кнопки
        radio_box = wx.RadioBox(self.panel, 
                               label="Выберите вариант",
                               choices=["Вариант 1", "Вариант 2", "Вариант 3"],
                               majorDimension=1,
                               style=wx.RA_SPECIFY_COLS)
        self.sizer.Add(radio_box, 0, wx.ALL | wx.EXPAND, 10)
        
        # Выпадающий список
        choices = ["Элемент 1", "Элемент 2", "Элемент 3"]
        self.combo = wx.ComboBox(self.panel, choices=choices, style=wx.CB_READONLY)
        self.sizer.Add(self.combo, 0, wx.ALL | wx.EXPAND, 10)
        
        # Ползунок
        self.slider = wx.Slider(self.panel, minValue=0, maxValue=100, style=wx.SL_HORIZONTAL | wx.SL_LABELS)
        self.sizer.Add(self.slider, 0, wx.ALL | wx.EXPAND, 10)
    
    def bind_events(self):
        """Привязка обработчиков событий"""
        self.btn_ok.Bind(wx.EVT_BUTTON, self.on_ok)
        self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_cancel)
        self.btn_clear.Bind(wx.EVT_BUTTON, self.on_clear)
        self.checkbox.Bind(wx.EVT_CHECKBOX, self.on_check)
        self.combo.Bind(wx.EVT_COMBOBOX, self.on_combo)
        self.slider.Bind(wx.EVT_SLIDER, self.on_slide)
    
    def on_ok(self, event):
        """Обработчик нажатия кнопки OK"""
        wx.MessageBox("Вы нажали OK!", "Информация", wx.OK | wx.ICON_INFORMATION)
    
    def on_cancel(self, event):
        """Обработчик нажатия кнопки Cancel"""
        self.Close()
    
    def on_clear(self, event):
        """Обработчик нажатия кнопки Clear"""
        self.text_ctrl.Clear()
    
    def on_check(self, event):
        """Обработчик изменения состояния чекбокса"""
        state = "включена" if self.checkbox.GetValue() else "выключена"
        self.text_ctrl.AppendText(f"Опция {state}\n")
    
    def on_combo(self, event):
        """Обработчик выбора в комбобоксе"""
        selection = self.combo.GetStringSelection()
        self.text_ctrl.AppendText(f"Выбран: {selection}\n")
    
    def on_slide(self, event):
        """Обработчик перемещения ползунка"""
        value = self.slider.GetValue()
        self.text_ctrl.AppendText(f"Значение ползунка: {value}\n")

if __name__ == "__main__":
    app = wx.App()
    frame = MyFrame(None, "wxPython Demo")
    app.MainLoop()

Основные виджеты wxPython:

  1. Основные элементы:

    • wx.Frame — главное окно приложения.

    • wx.Panel — контейнер для виджетов.

    • wx.BoxSizer — менеджер компоновки.

  2. Виджеты:

    • wx.StaticText — текстовая метка.

    • wx.TextCtrl — поле ввода текста (однострочное/многострочное).

    • wx.Button — кнопка.

    • wx.CheckBox — чекбокс.

    • wx.RadioBox — группа радиокнопок.

    • wx.ComboBox — выпадающий список.

    • wx.Slider — ползунок для выбора значения.

  3. Обработка событий:

    • Привязка обработчиков через метод Bind().

    • Примеры обработчиков для кнопок, чекбокса, комбобокса и ползунка.

Что получилось:

Для запуска примера требуется установленный wxPython (pip install wxPython). Приложение создает окно с различными элементами управления и демонстрирует их взаимодействие.

Remi

Remi создан Давидом Розой (Davide Rosa, известен как @dddomodossola) и впервые представлен в 2015 году. Проект активно развивается благодаря сообществу open-source.

  • Количество звезд на GitHub: 3,6k.

  • Коммиты: свыше 1,4k.

  • Последняя стабильная версия: 2022.7.27.

  • Дата последнего релиза: 27 июля 2022 года.

  • Дата последнего коммита: 9 марта 2025 года.

Remi (Remote Interface) — легковесная библиотека GUI для Python, которая рендерит интерфейс в веб-браузере. Она не требует нативных зависимостей, как Kivy или PyGObject, а использует встроенный веб-сервер для отображения приложения через HTML. Код пишется на Python, а Remi автоматически преобразует его в HTML и JavaScript, что избавляет от необходимости знать веб-технологии. Библиотека весит меньше 100 Кб, что удобно для проектов, где важна компактность — например, на Raspberry Pi. Ее можно использовать как для локальных, так и для удаленных интерфейсов, и она работает на разных платформах.

Достоинства:

  • Легковесность: минимальный размер и отсутствие сложных зависимостей.

  • Кроссплатформенность: работает везде, где есть браузер (Windows, Linux, macOS, Android).

  • Простота: не нужно знать HTML/CSS/JavaScript, только Python.

  • Удаленный доступ: встроенный веб-сервер позволяет управлять приложением через сеть.

Недостатки:

  • Ограниченная функциональность: меньше виджетов и возможностей по сравнению с Qt или GTK+.

  • Не нативный вид: интерфейс в браузере не всегда выглядит как системное приложение.

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

Remi идеальна для прототипов, проектов на устройствах вроде Raspberry Pi или ситуаций, где важна простота развертывания. Однако для сложных или визуально насыщенных интерфейсов ее возможностей может не хватить.

Пример кода для библиотеки Remi с различными виджетами:

from remi import gui
from remi import start, App


class RemiWidgetsExample(App):
    def __init__(self, *args):
        super(RemiWidgetsExample, self).__init__(*args)

    def main(self):
        # Основной контейнер
        container = gui.VBox(width=500, height=500, margin='0px auto')
        container.style['overflow'] = 'auto'
        container.style['padding'] = '20px'
        container.style['font-family'] = 'Arial, sans-serif'

        # Заголовок
        title = gui.Label('Примеры виджетов Remi', width='100%', height='30px')
        title.style['font-size'] = '24px'
        title.style['text-align'] = 'center'
        title.style['margin-bottom'] = '20px'
        container.append(title)

        # 1. Кнопка с обработчиком события
        self.btn = gui.Button('Нажми меня', width=200, height=100)
        self.btn.style['margin'] = '10px auto'
        self.btn.onclick.do(self.on_button_pressed)
        container.append(self.btn)

        # 2. Текстовое поле
        self.text_input = gui.TextInput(width=300, height=150)
        self.text_input.style['margin'] = '10px auto'
        self.text_input.set_text('Введите текст здесь')
        container.append(self.text_input)

        # 3. Метка для вывода текста
        self.output_label = gui.Label('Здесь будет результат', width='90%')
        self.output_label.style['margin'] = '10px auto'
        self.output_label.style['padding'] = '10px'
        self.output_label.style['background-color'] = '#f0f0f0'
        container.append(self.output_label)

        # 4. Выпадающий список
        self.dropdown = gui.DropDown(width=200, height=30)
        self.dropdown.style['margin'] = '10px auto'
        for item in ['Вариант 1', 'Вариант 2', 'Вариант 3']:
            self.dropdown.append(gui.DropDownItem(item))
        self.dropdown.onchange.do(self.on_dropdown_change)
        container.append(self.dropdown)

        # 5. Чекбокс
        self.checkbox = gui.CheckBoxLabel('Согласен с условиями', width=200)
        self.checkbox.style['margin'] = '10px auto'
        self.checkbox.onchange.do(self.on_checkbox_change)
        container.append(self.checkbox)

        # 6. Слайдер
        self.slider = gui.Slider(min=0, max=100, step=1, width=300)
        self.slider.style['margin'] = '20px auto'
        self.slider_label = gui.Label('Значение: 50', width=300)
        self.slider_label.style['margin'] = '0 auto'
        self.slider.onchange.do(self.on_slider_change)
        container.append(self.slider)
        container.append(self.slider_label)

        # 7. Изображение
        try:
            self.image = gui.Image('/res:logo.png', width=100, height=100)
            self.image.style['margin'] = '10px auto'
            container.append(self.image)
        except:
            pass

        return container

    # Обработчики событий
    def on_button_pressed(self, widget):
        self.output_label.set_text(f'Кнопка нажата! Текст: {self.text_input.get_text()}')

    def on_dropdown_change(self, widget, value):
        self.output_label.set_text(f'Выбран: {value}')

    def on_checkbox_change(self, widget, value):
        status = "Да" if value else "Нет"
        self.output_label.set_text(f'Согласен: {status}')

    def on_slider_change(self, widget, value):
        self.slider_label.set_text(f'Значение: {value}')


# Запуск приложения
if __name__ == "__main__":
    start(RemiWidgetsExample, address='0.0.0.0', port=0, 
          multiple_instance=False, enable_file_cache=True, 
          update_interval=0.1, start_browser=True)

Что получилось:Особенности примера:

  1. Структура приложения:

    • Создается класс, наследующий от App.

    • В методе main() определяются все виджеты.

  2. Используемые виджеты:

    • Button — кнопка с обработчиком события.

    • TextInput — поле для ввода текста.

    • Label — метка для вывода информации.

    • DropDown — выпадающий список.

    • CheckBoxLabel — чекбокс с текстом.

    • Slider — ползунок для выбора значения.

    • Image — отображение изображения.

  3. Особенности:

    • Все виджеты центрированы с помощью CSS-стилей.

    • Реализованы обработчики событий для каждого интерактивного элемента.

    • Используется вертикальное расположение (VBox).

Для запуска сохраните код в файл remi_example.py и выполните:

python remi_example.py

Приложение откроется в браузере по умолчанию.

Почему remi не так популярна в крупных проектах

По сравнению с более популярными решениями, у remi есть несколько слабых мест:

  • Меньшая экосистема и сообщество разработчиков.

  • Ограниченные возможности стилизации и кастомизации.

  • Меньшая производительность при высоких нагрузках.

  • Нет такой гибкости, как связка Flask/Django + современный JavaScript фреймворк.

Remi максимально полезна для быстрых прототипов, образовательных проектов и случаев, когда разработчик хочет создать GUI, не погружаясь в веб-разработку.

Что в итоге

Выбирать GUI-библиотеку для Python в 2025 году стоит под конкретные задачи:

  • Tkinter — простой и встроенный.

  • Qt обладает целой экосистемой для создания сложных и динамичных интерфейсов.

  • Kivy подходит для кроссплатформенных приложений, особенно на Android и iOS.

  • Toga делает нативные интерфейсы на десктопе.

  • wxPython хорош для серьезных приложений под Windows, macOS и Linux.

  • PyGObject — для GNOME- и Linux-сред.

  • Remi — легкое решение для браузерных интерфейсов, особенно на Raspberry Pi.

Конечно, у каждой библиотеки есть свои плюсы и минусы. Лучше попробовать несколько и выбрать то, что подходит именно вам.

А если у вас есть свои фавориты, расскажите о них в комментариях. Будет интересно почитать!

#статьи


  • 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

Что здесь происходит:

  • Мы подключаем библиотеку Tkinter с помощью директивы import.
  • Создаём главное окно приложения, в котором будут размещаться все графические элементы.
  • Добавляем виджеты — визуальные элементы, выполняющие определённые действия.
  • Создаём главный цикл событий — он включает в себя все события, происходящие при взаимодействии пользователя с интерфейсом.

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

  • Button — кнопки;
  • Canvas — «холст», на котором рисуют графические фигуры;
  • Entry — виджет для создания полей ввода;
  • Label — контейнер для размещения текста или изображения;
  • Menu — виджет для создания пунктов меню.

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

Мы напишем калькулятор индекса массы тела. ИМТ — это важный медицинский показатель, который позволяет оценить, есть ли у человека избыточный вес или ожирение. Он рассчитывается по следующей формуле: ​​

Результаты расчётов оценивают с помощью специальной таблицы. У врачей она имеет много градаций, мы же воспользуемся упрощённой версией:

Изображение: Skillbox Media

Писать код на Python лучше всего в специальной IDE, например в PyCharm или Visual Studio Code. Они подсвечивают синтаксис и предлагают продолжение кода — это сильно упрощает работу программиста. Весь код из этой статьи мы писали в Visual Studio Code.

Библиотека Tkinter предустановлена в Python. Поэтому её нужно только импортировать:

import tkinter as tk

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


Прежде чем писать код, необходимо ответить на несколько вопросов:

  • Какие данные мы хотим получить от пользователя и в каком виде?
  • Какое событие будет запускать расчёт ИМТ: нажатие кнопки, получение приложением всех необходимых данных или что-то другое?
  • Как будем показывать результат?

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

Схематично графический интерфейс нашего калькулятора будет выглядеть так:

Скриншот: Tkinter / Skillbox Media

Теперь попробуем реализовать интерфейс и работу калькулятора с помощью Python и Tkinter.


После импорта библиотеки в Python загрузим её методы:

from tkinter import *
from tkinter import messagebox

Первая строка позволяет нам загрузить все методы Tkinter и использовать их в коде без ссылки на их наименование. Второй строкой мы явно импортируем метод messagebox, который будем использовать для вывода всплывающего окна с результатом. Это удобно, так как метод потребуется нам несколько раз.

Теперь создадим окно нашего приложения. Для этого воспользуемся модулем Tk. Приложение назовём «Калькулятор индекса массы тела (ИМТ)»:

window = Tk() #Создаём окно приложения.
window.title("Калькулятор индекса массы тела (ИМТ)") #Добавляем название приложения.

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

window.mainloop()

Запустив код, увидим экран приложения:

Скриншот: Tkinter / Skillbox Media

Мы не указали размер окна, поэтому название приложения не помещается в него полностью. Исправим это с помощью метода geometry:

window.geometry('400x300')

Теперь название приложения видно полностью:

Скриншот: Tkinter / Skillbox Media

В окне приложения необходимо разместить несколько элементов с нашего эскиза: два поля ввода информации с подписями и одну кнопку. Важно, чтобы поля не накладывались друг на друга и не уходили за пределы окна. В 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-й столбец». Если запустим код, то увидим там единственный элемент:

Скриншот: Tkinter / Skillbox Media

Сейчас элемент расположен в центре окна, но он займёт правильное положение, когда мы напишем другие элементы.

Добавим вторую надпись о весе аналогичным образом, но при позиционировании в grid укажем следующую, четвёртую строку:

weight_lb = Label(
   frame,
   text="Введите свой вес (в кг)  ",
)
weight_lb.grid(row=4, column=1)

Запускаем код и смотрим на результат:

Скриншот: Tkinter / Skillbox Media

Теперь добавим поля для ввода пользовательской информации, используя виджет Entry:

height_tf = Entry(
   frame, #Используем нашу заготовку с настроенными отступами.
)
height_tf.grid(row=3, column=2)

Для позиционирования мы также воспользовались методом grid. Обратите внимание, что наш элемент должен быть расположен напротив надписи «Введите свой рост (в см)». Поэтому мы используем ячейку в той же строке, но уже во втором столбце. Запустим код и посмотрим на результат:

Скриншот: Tkinter / Skillbox Media

Всё получилось. Остаётся по аналогии добавить поле ввода веса:

weight_tf = Entry(
   frame,
)
weight_tf.grid(row=4, column=2, pady=5)

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

Скриншот: Tkinter / Skillbox Media

Теперь добавим кнопку, которая будет запускать расчёт ИМТ. Сделаем это с помощью виджета Button:

cal_btn = Button(
   frame, #Заготовка с настроенными отступами.
   text='Рассчитать ИМТ', #Надпись на кнопке.
)
cal_btn.grid(row=5, column=2) #Размещаем кнопку в ячейке, расположенной ниже, чем наши надписи, но во втором столбце, то есть под ячейками для ввода информации.

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

Скриншот: Tkinter / Skillbox Media

Теперь в приложении есть все графические элементы. Остаётся лишь написать код, который будет получать информацию из виджетов 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)

Запустим код и посмотрим на результат:

Источник: Tkinter / Skillbox Media

Всё работает. Функция получает данные из полей ввода и рассчитывает индекс массы тела, показывая результат на экране.

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-разработчик»
Узнать о курсе

Парадигмы консольного и графического пользовательского интерфейса


Введение

Пользовательский интерфейс (UI) — это важная часть взаимодействия человека с компьютером. Он определяет, как пользователь воспринимает и использует программное обеспечение. В истории развития компьютеров сформировались две основные парадигмы интерфейсов: консольный интерфейс (CUI, Command Line Interface) и графический интерфейс (GUI, Graphical User Interface). Каждая из этих парадигм имеет свои особенности, преимущества и недостатки.


1. Консольный пользовательский интерфейс (CUI)

1.1. Определение и история

Консольный интерфейс — это текстовый способ взаимодействия пользователя с компьютером. Пользователь вводит команды с клавиатуры, а система выводит результаты в текстовом виде. Этот тип интерфейса был основным в ранних компьютерах (например, в системах с операционными системами Unix, MS-DOS).


1.2. Особенности

  • Текстовый ввод/вывод: Все команды и результаты отображаются в виде текста.
  • Минимализм: Не требует графических ресурсов, что делает его легковесным.
  • Гибкость: Позволяет выполнять сложные операции с помощью команд и скриптов.
  • Автоматизация: Легко автоматизируется с помощью сценариев (скриптов).

1.3. Преимущества

  • Производительность: Подходит для опытных пользователей, которые могут быстро выполнять задачи.
  • Ресурсоэффективность: Не требует мощного железа для работы.
  • Универсальность: Подходит для удаленного управления через SSH или Telnet.

1.4. Недостатки

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

1.5. Примеры использования

  • Администрирование серверов.
  • Разработка программного обеспечения (например, использование Git через командную строку).
  • Автоматизация задач (например, написание bash-скриптов).

2. Графический пользовательский интерфейс (GUI)

2.1. Определение и история

Графический интерфейс — это визуальный способ взаимодействия пользователя с компьютером. Он использует графические элементы, такие как окна, кнопки, иконки и меню. GUI стал популярным с появлением операционных систем, таких как Windows, macOS и графических оболочек для Linux (например, GNOME, KDE).


2.2. Особенности

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

2.3. Преимущества

  • Удобство: Подходит для начинающих пользователей.
  • Наглядность: Визуальное представление данных упрощает восприятие.
  • Мультизадачность: Возможность работы с несколькими окнами одновременно.

2.4. Недостатки

  • Ресурсоемкость: Требует больше вычислительных ресурсов.
  • Ограниченная гибкость: Некоторые сложные операции сложнее выполнять через GUI.
  • Зависимость от оборудования: Требует наличия графического оборудования (монитор, видеокарта).

2.5. Примеры использования

  • Операционные системы (Windows, macOS, Linux с графической оболочкой).
  • Офисные приложения (Microsoft Office, LibreOffice).
  • Веб-браузеры (Google Chrome, Mozilla Firefox).

3. Сравнение CUI и GUI

Характеристика CUI (Консольный интерфейс) GUI (Графический интерфейс)
Удобство для новичков Низкое Высокое
Ресурсоемкость Низкая Высокая
Гибкость Высокая Ограниченная
Автоматизация Легко автоматизируется Сложнее автоматизировать
Визуализация Текстовая Графическая

4. Современные тенденции

4.1. Гибридные интерфейсы

Современные системы часто сочетают в себе элементы CUI и GUI. Например, в операционных системах, таких как Windows и macOS, можно использовать командную строку (PowerShell, Terminal) вместе с графическим интерфейсом.


4.2. Веб-интерфейсы

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


4.3. Голосовые интерфейсы

С развитием искусственного интеллекта и технологий распознавания речи появляются новые парадигмы взаимодействия, такие как голосовые помощники (Siri, Alexa).


Заключение

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


Класс  окна  в операционных  системах,  предопределенные  классы, получение  и изменение  данных  окна  и класса.


1. Введение

В операционных системах (ОС), таких как Windows, окна являются основными элементами графического интерфейса пользователя (GUI). Каждое окно принадлежит определенному классу, который определяет его поведение, внешний вид и функциональность. Класс окна — это шаблон, на основе которого создаются экземпляры окон.


2. Понятие класса окна

Класс окна — это структура данных, которая содержит информацию о: — Процедуре окна (Window Procedure) — функция, обрабатывающая сообщения, отправляемые окну. — Стилях окна (Window Styles) — параметры, определяющие внешний вид и поведение окна (например, наличие рамки, заголовка, кнопок управления). — Иконке и курсоре — ресурсы, используемые окном. — Фоне окна — цвет или кисть, используемая для заливки фона. — Меню — меню по умолчанию, связанное с окном.

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


3. Предопределенные классы окон

Операционные системы предоставляют набор предопределенных классов, которые можно использовать для создания стандартных окон. Примеры предопределенных классов в Windows: — BUTTON — класс для создания кнопок. — EDIT — класс для создания текстовых полей. — LISTBOX — класс для создания списков. — COMBOBOX — класс для создания комбинированных списков. — STATIC — класс для создания статических текстовых элементов.

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


4. Регистрация пользовательского класса окна

Если предопределенные классы не подходят, можно зарегистрировать собственный класс окна. Для этого используется функция RegisterClass или RegisterClassEx (в Windows). Пример:

WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc; // Указатель на процедуру окна
wc.hInstance = hInstance;    // Дескриптор экземпляра приложения
wc.lpszClassName = L"MyWindowClass"; // Имя класса

// Регистрация класса
RegisterClass(&wc);

После регистрации класса можно создавать окна на его основе.


5. Получение данных окна и класса

Для получения информации об окне и его классе используются следующие функции: — GetWindowInfo — возвращает информацию об окне. — GetClassInfo — возвращает информацию о классе окна. — GetClassName — возвращает имя класса, к которому принадлежит окно.

Пример:

TCHAR className[256];
GetClassName(hwnd, className, 256); // Получение имени класса окна

6. Изменение данных окна и класса

Некоторые параметры окна и класса можно изменить динамически: — SetWindowLong — изменяет атрибуты окна (например, стили). — SetClassLong — изменяет атрибуты класса (например, иконку или курсор).

Пример:

SetWindowLong(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); // Изменение стиля окна
SetClassLong(hwnd, GCL_HICON, (LONG)newIcon); // Изменение иконки класса

7. Пример использования

Рассмотрим пример создания окна с пользовательским классом:

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"MyWindowClass";

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(0, L"MyWindowClass", L"Мое окно", WS_OVERLAPPEDWINDOW,
                               CW_USEDEFAULT, CW_USEDEFAULT, 500, 300, NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, nCmdShow);

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

8. Заключение

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


Событийное  управление  приложениями


1. Событийное управление приложениями

Событийное управление (Event-Driven Programming) — это парадигма, при которой выполнение программы определяется событиями (например, клик мыши, нажатие клавиши, закрытие окна).


Основные понятия:
Событие (Event):
— Это действие или изменение состояния, которое обрабатывается программой (например, нажатие кнопки).
Обработчик событий (Event Handler):
— Это функция, которая вызывается при возникновении определенного события.


Цикл обработки событий:
1. Программа ожидает события (например, ввод пользователя).
2. Когда событие происходит, система отправляет его в очередь событий.
3. Программа извлекает событие из очереди и вызывает соответствующий обработчик.
4. Обработчик выполняет необходимые действия.


Примеры событий в графическом интерфейсе:
Click — клик мыши.
KeyPress — нажатие клавиши.
Resize — изменение размера окна.
Close — закрытие окна.


Преимущества событийного управления:
— Удобство для создания интерактивных приложений.
— Эффективное использование ресурсов (программа не занимает процессорное время в ожидании).
— Поддержка многозадачности.


2. Пример работы с окнами и событиями (на примере Windows API)

#include <windows.h>

// Обработчик событий (Window Procedure)
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0); // Завершение программы при закрытии окна
            return 0;
        case WM_PAINT:
            // Отрисовка содержимого окна
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            TextOut(hdc, 50, 50, L"Hello, World!", 13);
            EndPaint(hwnd, &ps);
            return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // Регистрация класса окна
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"MainWindowClass";
    RegisterClass(&wc);

    // Создание окна
    HWND hwnd = CreateWindowEx(0, L"MainWindowClass", L"Hello, World!", WS_OVERLAPPEDWINDOW,
                               CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, nCmdShow);

    // Цикл обработки событий
    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

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

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

Использование GDI32


Простой пример с рисованием фигур

Пример простого приложения на Win32, которое демонстрирует возможности библиотеки gdi32, может включать в себя создание окна и рисование различных фигур и текста с использованием функций GDI (Graphics Device Interface). Вот пример такого приложения:


#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT("GDIExample");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName, TEXT("GDI Example"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;

    switch (message) {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        // Рисуем прямоугольник
        Rectangle(hdc, 50, 50, 200, 200);

        // Рисуем эллипс
        Ellipse(hdc, 250, 50, 400, 200);

        // Рисуем линию
        MoveToEx(hdc, 50, 250, NULL);
        LineTo(hdc, 400, 250);

        // Выводим текст
        SetTextAlign(hdc, TA_CENTER);
        TextOut(hdc, 225, 300, TEXT("Hello, GDI!"), 11);

        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Описание кода:

  1. WinMain: Это точка входа приложения. Здесь регистрируется класс окна, создается окно и запускается цикл обработки сообщений.
  2. WndProc: Это процедура окна, которая обрабатывает сообщения, отправляемые окну. В данном примере она обрабатывает сообщение WM_PAINT, которое отправляется, когда окно нужно перерисовать.
  3. WM_PAINT: В этом блоке кода используется контекст устройства (HDC) для рисования различных фигур и текста:
    • Rectangle рисует прямоугольник.
    • Ellipse рисует эллипс.
    • MoveToEx и LineTo рисуют линию.
    • TextOut выводит текст на экран.

Компиляция:

Для компиляции этого кода вам понадобится компилятор, поддерживающий WinAPI, например, MinGW или Visual Studio. В Visual Studio можно создать новый проект Win32 Application и вставить этот код в файл main.cpp.


Запуск:

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


Использование различных режимов наложения

Добавим в пример использование функций для работы с битмапами и различными режимами наложения (ROP2). Мы загрузим битмап из файла, отобразим его на экране и продемонстрируем различные режимы наложения с помощью функции SetROP2.


#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT("GDIExample");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName, TEXT("GDI Example with Bitmaps and ROP2"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    HDC hdc;
    PAINTSTRUCT ps;
    HBITMAP hBitmap;
    BITMAP bitmap;
    HDC hdcMem;
    HGDIOBJ oldBitmap;

    switch (message) {
    case WM_CREATE:
        // Загружаем битмап из файла
        hBitmap = (HBITMAP)LoadImage(NULL, TEXT("example.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        if (hBitmap == NULL) {
            MessageBox(hwnd, TEXT("Не удалось загрузить битмап!"), TEXT("Ошибка"), MB_ICONERROR);
        }
        // Сохраняем битмап в глобальной переменной
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)hBitmap);
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        // Получаем битмап из глобальной переменной
        hBitmap = (HBITMAP)GetWindowLongPtr(hwnd, GWLP_USERDATA);
        if (hBitmap != NULL) {
            // Создаем контекст устройства в памяти
            hdcMem = CreateCompatibleDC(hdc);
            oldBitmap = SelectObject(hdcMem, hBitmap);

            // Получаем информацию о битмапе
            GetObject(hBitmap, sizeof(bitmap), &bitmap);

            // Отображаем битмап на экране
            BitBlt(hdc, 50, 50, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);

            // Демонстрация режимов наложения (ROP2)
            SetROP2(hdc, R2_NOT); // Инвертирование цвета
            Rectangle(hdc, 100, 100, 300, 200);

            SetROP2(hdc, R2_XORPEN); // Исключающее ИЛИ
            Ellipse(hdc, 150, 150, 350, 250);

            SetROP2(hdc, R2_MERGEPEN); // Объединение с цветом пера
            MoveToEx(hdc, 50, 300, NULL);
            LineTo(hdc, 400, 300);

            // Восстанавливаем контекст устройства
            SelectObject(hdcMem, oldBitmap);
            DeleteDC(hdcMem);
        }

        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        // Удаляем битмап
        hBitmap = (HBITMAP)GetWindowLongPtr(hwnd, GWLP_USERDATA);
        if (hBitmap != NULL) {
            DeleteObject(hBitmap);
        }
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Описание:

  1. Загрузка битмапа:
    • В обработчике WM_CREATE загружается битмап из файла example.bmp с помощью функции LoadImage.
    • Битмап сохраняется в глобальной переменной, связанной с окном, с использованием SetWindowLongPtr.
  2. Отображение битмапа:
    • В обработчике WM_PAINT создается контекст устройства в памяти (CreateCompatibleDC), и битмап отображается на экране с помощью BitBlt.

  1. Режимы наложения (ROP2):
    • Используется функция SetROP2 для изменения режима наложения:
      • R2_NOT: Инвертирование цвета.
      • R2_XORPEN: Исключающее ИЛИ.
      • R2_MERGEPEN: Объединение с цветом пера.
    • Эти режимы применяются к прямоугольнику, эллипсу и линии.
  2. Удаление битмапа:
    • В обработчике WM_DESTROY битмап удаляется с помощью DeleteObject.

Как использовать:

  1. Создайте файл example.bmp в той же директории, где находится исполняемый файл.
  2. Скомпилируйте и запустите программу.
  3. Вы увидите:
    • Загруженный битмап.
    • Прямоугольник, эллипс и линию, нарисованные с разными режимами наложения.

Примечания:

  • Если файл example.bmp отсутствует, программа выведет сообщение об ошибке.
  • Вы можете заменить example.bmp на любой другой битмап, чтобы увидеть, как он отображается.

Использование анимации и двойной буферизации

Для создания анимации движения объекта в окне с использованием двойной буферизации (double buffering) в Win32 API, мы будем использовать следующие техники: 1. Двойная буферизация: Рисование происходит в памяти (в буфере), а затем буфер копируется на экран. Это предотвращает мерцание. 2. Таймер: Для обновления позиции объекта и перерисовки окна через определенные интервалы времени.

В этом примере мы создадим анимацию движения круга по экрану.


Пример кода:

#include <windows.h>
#include <math.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT("AnimationExample");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName, TEXT("Animation with Double Buffering"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    static int x = 100, y = 100; // Начальные координаты круга
    static int dx = 5, dy = 5;   // Скорость движения по осям X и Y
    static int radius = 50;      // Радиус круга

    switch (message) {
    case WM_CREATE:
        // Устанавливаем таймер для обновления анимации каждые 16 мс (~60 FPS)
        SetTimer(hwnd, 1, 16, NULL);
        return 0;

    case WM_TIMER:
        // Обновляем позицию круга
        x += dx;
        y += dy;

        // Проверяем столкновение с границами окна
        RECT rect;
        GetClientRect(hwnd, &rect);

        if (x - radius < 0 || x + radius > rect.right) {
            dx = -dx; // Меняем направление по оси X
        }
        if (y - radius < 0 || y + radius > rect.bottom) {
            dy = -dy; // Меняем направление по оси Y
        }

        // Перерисовываем окно
        InvalidateRect(hwnd, NULL, TRUE);
        return 0;

    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        // Создаем контекст устройства в памяти для двойной буферизации
        HDC hdcMem = CreateCompatibleDC(hdc);
        HBITMAP hBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
        SelectObject(hdcMem, hBitmap);

        // Очищаем фон
        HBRUSH hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
        FillRect(hdcMem, &rect, hBrush);

        // Рисуем круг
        HBRUSH hCircleBrush = CreateSolidBrush(RGB(255, 0, 0)); // Красный круг
        SelectObject(hdcMem, hCircleBrush);
        Ellipse(hdcMem, x - radius, y - radius, x + radius, y + radius);
        DeleteObject(hCircleBrush);

        // Копируем буфер на экран
        BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

        // Освобождаем ресурсы
        DeleteObject(hBitmap);
        DeleteDC(hdcMem);

        EndPaint(hwnd, &ps);
        return 0;
    }

    case WM_DESTROY:
        // Удаляем таймер
        KillTimer(hwnd, 1);
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Описание кода:

  1. Двойная буферизация:
    • В обработчике WM_PAINT создается контекст устройства в памяти (hdcMem), в котором рисуется весь кадр.
    • После завершения рисования буфер копируется на экран с помощью BitBlt.
  2. Таймер:
    • В обработчике WM_CREATE устанавливается таймер, который срабатывает каждые 16 мс (примерно 60 кадров в секунду).
    • В обработчике WM_TIMER обновляется позиция круга и вызывается перерисовка окна с помощью InvalidateRect.

  1. Анимация:
    • Круг движется по экрану, меняя направление при столкновении с границами окна.
    • Позиция круга обновляется в обработчике WM_TIMER.
  2. Столкновение с границами:
    • Если круг достигает границы окна, его скорость по соответствующей оси инвертируется.

Как это работает:

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

Результат:

  • Красный круг движется по экрану, отскакивая от границ окна.
  • Анимация плавная, без мерцания, благодаря двойной буферизации.

Примечания:

  • Вы можете изменить скорость движения, радиус круга или цвет, чтобы настроить анимацию.
  • Для более сложной анимации можно добавить несколько объектов или изменить их траектории.

Наложение с использованием маски

Для демонстрации наложения изображений с использованием маски в Win32 API, мы создадим пример, где два изображения накладываются друг на друга с использованием маски. Маска позволяет определить, какие части изображения будут прозрачными, а какие — непрозрачными. Это часто используется для создания спрайтов с прозрачным фоном.


Пример кода:

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT("MaskedImageExample");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName, TEXT("Masked Image Example"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    static HBITMAP hBitmapImage = NULL;    // Основное изображение
    static HBITMAP hBitmapMask = NULL;     // Маска
    static HBITMAP hBitmapBackground = NULL; // Фоновое изображение

    switch (message) {
    case WM_CREATE:
        // Загружаем изображения
        hBitmapImage = (HBITMAP)LoadImage(NULL, TEXT("image.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        hBitmapMask = (HBITMAP)LoadImage(NULL, TEXT("mask.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        hBitmapBackground = (HBITMAP)LoadImage(NULL, TEXT("background.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

        if (!hBitmapImage || !hBitmapMask || !hBitmapBackground) {
            MessageBox(hwnd, TEXT("Не удалось загрузить изображения!"), TEXT("Ошибка"), MB_ICONERROR);
        }
        return 0;

    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        // Получаем размеры клиентской области
        RECT rect;
        GetClientRect(hwnd, &rect);

        // Создаем контекст устройства в памяти для двойной буферизации
        HDC hdcMem = CreateCompatibleDC(hdc);
        HBITMAP hBitmapBuffer = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
        SelectObject(hdcMem, hBitmapBuffer);

        // Рисуем фоновое изображение
        HDC hdcBackground = CreateCompatibleDC(hdc);
        SelectObject(hdcBackground, hBitmapBackground);
        BitBlt(hdcMem, 0, 0, rect.right, rect.bottom, hdcBackground, 0, 0, SRCCOPY);
        DeleteDC(hdcBackground);

        // Рисуем изображение с маской
        if (hBitmapImage && hBitmapMask) {
            HDC hdcImage = CreateCompatibleDC(hdc);
            HDC hdcMask = CreateCompatibleDC(hdc);

            // Выбираем изображение и маску в контексты устройств
            SelectObject(hdcImage, hBitmapImage);
            SelectObject(hdcMask, hBitmapMask);

            // Шаг 1: Наложение маски на фоновое изображение (AND-операция)
            BitBlt(hdcMem, 100, 100, rect.right, rect.bottom, hdcMask, 0, 0, SRCAND);

            // Шаг 2: Наложение изображения на результат (OR-операция)
            BitBlt(hdcMem, 100, 100, rect.right, rect.bottom, hdcImage, 0, 0, SRCPAINT);

            // Освобождаем контексты устройств
            DeleteDC(hdcImage);
            DeleteDC(hdcMask);
        }

        // Копируем буфер на экран
        BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

        // Освобождаем ресурсы
        DeleteObject(hBitmapBuffer);
        DeleteDC(hdcMem);

        EndPaint(hwnd, &ps);
        return 0;
    }

    case WM_DESTROY:
        // Удаляем загруженные изображения
        if (hBitmapImage) DeleteObject(hBitmapImage);
        if (hBitmapMask) DeleteObject(hBitmapMask);
        if (hBitmapBackground) DeleteObject(hBitmapBackground);
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Описание кода:

  1. Загрузка изображений:
    • В обработчике WM_CREATE загружаются три изображения:
      • Основное изображение (image.bmp).
      • Маска (mask.bmp).
      • Фоновое изображение (background.bmp).
    • Маска должна быть черно-белой: черный цвет (0x000000) обозначает прозрачные области, белый цвет (0xFFFFFF) — непрозрачные.

  1. Наложение изображений:
    • В обработчике WM_PAINT используется двойная буферизация для предотвращения мерцания.
    • Фоновое изображение рисуется на буфере.
    • Основное изображение накладывается на фон с использованием маски:
      • Сначала маска применяется к фону с помощью операции SRCAND (AND-операция). Это делает прозрачные области черными.
      • Затем основное изображение накладывается с помощью операции SRCPAINT (OR-операция). Это заполняет прозрачные области цветами из изображения.
  2. Освобождение ресурсов:
    • В обработчике WM_DESTROY удаляются загруженные изображения.

Как это работает:

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

Результат:

  • На фоновом изображении отображается основное изображение с прозрачными областями, заданными маской.

Примечания:

  1. Файлы изображений:
    • Создайте три файла: image.bmp, mask.bmp и background.bmp.
    • Маска (mask.bmp) должна быть черно-белой. Черный цвет (0x000000) — прозрачные области, белый цвет (0xFFFFFF) — непрозрачные.
  2. Пример изображений:
    • image.bmp: Основное изображение (например, спрайт).
    • mask.bmp: Черно-белая маска, соответствующая основному изображению.
    • background.bmp: Фоновое изображение.

  1. Компиляция:
    • Скомпилируйте код с помощью компилятора, поддерживающего WinAPI (например, MinGW или Visual Studio).
  2. Запуск:
    • Убедитесь, что файлы image.bmp, mask.bmp и background.bmp находятся в той же директории, что и исполняемый файл.

Ресурсы  приложения,  их  создание  и использование


Ресурсы в Windows-приложениях — это данные, которые хранятся внутри исполняемых файлов (EXE, DLL) или в отдельных файлах и используются программой во время выполнения. Ресурсы позволяют включать в приложение такие элементы, как иконки, строки, диалоговые окна, меню, курсоры, изображения и другие данные, не загромождая основной код программы.


1. Типы ресурсов в Windows

Ресурсы в Windows делятся на несколько типов, каждый из которых имеет своё назначение:


1.1. Стандартные типы ресурсов

  • Иконки (ICON): используются для отображения значков приложения.
  • Курсоры (CURSOR): определяют внешний вид курсора мыши.
  • Диалоговые окна (DIALOG): шаблоны для создания окон с элементами управления.
  • Меню (MENU): определяют структуру меню приложения.
  • Строки (STRINGTABLE): текстовые данные, которые могут быть локализованы.
  • Битовые изображения (BITMAP): растровые изображения.
  • Версия (VERSIONINFO): информация о версии приложения.

1.2. Пользовательские типы ресурсов

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


2. Создание ресурсов

Ресурсы в Windows создаются с помощью файлов ресурсов (.rc), которые компилируются в бинарный формат и включаются в исполняемый файл.


2.1. Файл ресурсов (.rc)

Файл ресурсов — это текстовый файл, который описывает все ресурсы, используемые в приложении. Пример простого .rc файла:


// Иконка приложения
IDI_ICON1 ICON "icon.ico"

// Курсор
IDC_CURSOR1 CURSOR "cursor.cur"

// Строковая таблица
STRINGTABLE
BEGIN
    101 "Привет, мир!"
    102 "Это пример строки."
END

// Диалоговое окно
IDD_DIALOG1 DIALOGEX 0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
CAPTION "Пример диалога"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 10, 70, 50, 14
    PUSHBUTTON "Отмена", IDCANCEL, 70, 70, 50, 14
END

2.2. Компиляция ресурсов

Файл ресурсов компилируется с помощью компилятора ресурсов (rc.exe), который создает бинарный файл (.res). Этот файл затем включается в исполняемый файл на этапе линковки.

Пример команды для компиляции:


3. Использование ресурсов

Ресурсы загружаются и используются в коде программы с помощью API Windows.

3.1. Загрузка иконки

Пример загрузки иконки:

HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));

3.2. Загрузка строки

Пример загрузки строки из строковой таблицы:

char buffer[100];
LoadString(hInstance, 101, buffer, sizeof(buffer));
MessageBox(NULL, buffer, "Ресурс", MB_OK);

3.3. Создание диалогового окна

Пример создания диалогового окна:

DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);

3.4. Загрузка битового изображения

Пример загрузки изображения:

HBITMAP hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1));

4. Управление ресурсами

Ресурсы в Windows могут быть как встроенными (внутри EXE или DLL), так и внешними (отдельные файлы). Встроенные ресурсы защищены от изменений, но их сложнее обновлять. Внешние ресурсы проще обновлять, но они менее безопасны.

4.1. Встроенные ресурсы

  • Хранятся внутри исполняемого файла.

  • Загружаются с помощью функций API, таких как LoadIcon, LoadString, LoadBitmap.

  • Пример:

    HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));

4.2. Внешние ресурсы

  • Хранятся в отдельных файлах.

  • Загружаются с помощью функций, таких как LoadImage, LoadLibrary (для DLL).

  • Пример:

    HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, "image.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

5. Локализация ресурсов

Локализация — это процесс адаптации приложения для разных языков и регионов. В Windows локализация ресурсов достигается за счет создания отдельных файлов ресурсов для каждого языка.


5.1. Создание локализованных ресурсов

  • Создайте отдельный .rc файл для каждого языка.

  • Используйте директиву LANGUAGE для указания языка и кодовой страницы.

  • Пример:

    LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
    STRINGTABLE
    BEGIN
        101 "Привет, мир!"
    END

5.2. Загрузка локализованных ресурсов

  • Используйте функции API, такие как LoadString, для загрузки строк на нужном языке.

  • Пример:

    LoadString(hInstance, 101, buffer, sizeof(buffer));

6. Пример программы с использованием ресурсов

Рассмотрим пример простого Windows-приложения, которое использует ресурсы:

6.1. Файл ресурсов (app.rc)

IDI_ICON1 ICON "app.ico"
IDD_DIALOG1 DIALOGEX 0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
CAPTION "Пример диалога"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 10, 70, 50, 14
    PUSHBUTTON "Отмена", IDCANCEL, 70, 70, 50, 14
END

6.2. Код программы (main.c)

#include <windows.h>

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_COMMAND:
            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
                EndDialog(hwndDlg, LOWORD(wParam));
                return TRUE;
            }
            break;
    }
    return FALSE;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
    return 0;
}

Организация  интерфейса  на основе меню


Организация интерфейса на основе меню

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


1. Типы меню

1.1. Главное меню

Главное меню располагается в верхней части окна под заголовком. Оно содержит основные команды приложения, такие как “Файл”, “Правка”, “Вид” и т.д.

1.2. Контекстное меню

Контекстное меню появляется при щелчке правой кнопкой мыши на определённом элементе интерфейса. Оно содержит команды, актуальные для текущего контекста.

2. Создание меню

Меню в Windows-приложениях создается с помощью файла ресурсов (.rc) или программно в коде.

2.1. Создание меню в файле ресурсов

Пример создания главного меню в файле ресурсов:


// Файл ресурсов (app.rc)
IDR_MAINMENU MENU
BEGIN
    POPUP "&Файл"
    BEGIN
        MENUITEM "&Открыть", ID_FILE_OPEN
        MENUITEM "&Сохранить", ID_FILE_SAVE
        MENUITEM SEPARATOR
        MENUITEM "В&ыход", ID_FILE_EXIT
    END
    POPUP "&Правка"
    BEGIN
        MENUITEM "&Вырезать", ID_EDIT_CUT
        MENUITEM "&Копировать", ID_EDIT_COPY
        MENUITEM "В&ставить", ID_EDIT_PASTE
    END
    POPUP "&Справка"
    BEGIN
        MENUITEM "&О программе", ID_HELP_ABOUT
    END
END

2.2. Создание меню программно

Пример создания меню в коде:

HMENU hMenu = CreateMenu();
HMENU hFileMenu = CreatePopupMenu();
HMENU hEditMenu = CreatePopupMenu();

AppendMenu(hFileMenu, MF_STRING, ID_FILE_OPEN, "&Открыть");
AppendMenu(hFileMenu, MF_STRING, ID_FILE_SAVE, "&Сохранить");
AppendMenu(hFileMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hFileMenu, MF_STRING, ID_FILE_EXIT, "В&ыход");

AppendMenu(hEditMenu, MF_STRING, ID_EDIT_CUT, "&Вырезать");
AppendMenu(hEditMenu, MF_STRING, ID_EDIT_COPY, "&Копировать");
AppendMenu(hEditMenu, MF_STRING, ID_EDIT_PASTE, "В&ставить");

AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, "&Файл");
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hEditMenu, "&Правка");

SetMenu(hwnd, hMenu);

3. Обработка команд меню

Команды меню обрабатываются в оконной процедуре (WndProc) с помощью сообщения WM_COMMAND.


Пример обработки команд:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case ID_FILE_OPEN:
                    MessageBox(hwnd, "Команда: Открыть", "Меню", MB_OK);
                    break;
                case ID_FILE_SAVE:
                    MessageBox(hwnd, "Команда: Сохранить", "Меню", MB_OK);
                    break;
                case ID_FILE_EXIT:
                    PostQuitMessage(0);
                    break;
                case ID_EDIT_CUT:
                    MessageBox(hwnd, "Команда: Вырезать", "Меню", MB_OK);
                    break;
                case ID_EDIT_COPY:
                    MessageBox(hwnd, "Команда: Копировать", "Меню", MB_OK);
                    break;
                case ID_EDIT_PASTE:
                    MessageBox(hwnd, "Команда: Вставить", "Меню", MB_OK);
                    break;
                case ID_HELP_ABOUT:
                    MessageBox(hwnd, "О программе", "Меню", MB_OK);
                    break;
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

4. Контекстное меню

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

Пример создания и отображения контекстного меню:

case WM_RBUTTONUP: {
    HMENU hMenu = CreatePopupMenu();
    AppendMenu(hMenu, MF_STRING, ID_CONTEXT_COPY, "Копировать");
    AppendMenu(hMenu, MF_STRING, ID_CONTEXT_PASTE, "Вставить");

    POINT pt;
    GetCursorPos(&pt);
    TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
    DestroyMenu(hMenu);
    break;
}

5. Управление состоянием меню

Состояние пунктов меню (активен/неактивен, отмечен/не отмечен) можно изменять динамически.

5.1. Активация/деактивация пунктов меню

Пример:

EnableMenuItem(hMenu, ID_FILE_SAVE, MF_GRAYED); // Деактивировать пункт
EnableMenuItem(hMenu, ID_FILE_SAVE, MF_ENABLED); // Активировать пункт

5.2. Отметка пунктов меню

Пример:

CheckMenuItem(hMenu, ID_VIEW_TOOLBAR, MF_CHECKED); // Отметить пункт
CheckMenuItem(hMenu, ID_VIEW_TOOLBAR, MF_UNCHECKED); // Снять отметку

6. Пример программы с меню

Рассмотрим пример простого Windows-приложения с главным меню.


6.1. Файл ресурсов (app.rc)

IDR_MAINMENU MENU
BEGIN
    POPUP "&Файл"
    BEGIN
        MENUITEM "&Открыть", ID_FILE_OPEN
        MENUITEM "&Сохранить", ID_FILE_SAVE
        MENUITEM SEPARATOR
        MENUITEM "В&ыход", ID_FILE_EXIT
    END
    POPUP "&Правка"
    BEGIN
        MENUITEM "&Вырезать", ID_EDIT_CUT
        MENUITEM "&Копировать", ID_EDIT_COPY
        MENUITEM "В&ставить", ID_EDIT_PASTE
    END
END

6.2. Код программы (main.c)

#include <windows.h>

#define ID_FILE_OPEN 101
#define ID_FILE_SAVE 102
#define ID_FILE_EXIT 103
#define ID_EDIT_CUT 201
#define ID_EDIT_COPY 202
#define ID_EDIT_PASTE 203

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case ID_FILE_OPEN:
                    MessageBox(hwnd, "Открыть файл", "Меню", MB_OK);
                    break;
                case ID_FILE_SAVE:
                    MessageBox(hwnd, "Сохранить файл", "Меню", MB_OK);
                    break;
                case ID_FILE_EXIT:
                    PostQuitMessage(0);
                    break;
                case ID_EDIT_CUT:
                    MessageBox(hwnd, "Вырезать", "Меню", MB_OK);
                    break;
                case ID_EDIT_COPY:
                    MessageBox(hwnd, "Копировать", "Меню", MB_OK);
                    break;
                case ID_EDIT_PASTE:
                    MessageBox(hwnd, "Вставить", "Меню", MB_OK);
                    break;
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc = {0};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = "MyWindowClass";
    RegisterClass(&wc);

    HWND hwnd = CreateWindow("MyWindowClass", "Окно с меню", WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}


Диалоговые окна — это специальные окна, которые используются для взаимодействия с пользователем. Они могут отображать информацию, запрашивать данные или предоставлять выбор опций. Диалоговые окна бывают двух типов: модальные и немодальные.

  • Модальные окна блокируют взаимодействие с родительским окном до тех пор, пока пользователь не закроет диалог.
  • Немодальные окна позволяют пользователю взаимодействовать как с диалогом, так и с родительским окном.

1. Создание диалогового окна

Диалоговые окна создаются с помощью файла ресурсов (.rc) или программно в коде.


1.1. Создание диалога в файле ресурсов

Пример создания диалогового окна в файле ресурсов:

// Файл ресурсов (app.rc)
IDD_DIALOG1 DIALOGEX 0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Пример диалога"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 10, 70, 50, 14
    PUSHBUTTON "Отмена", IDCANCEL, 70, 70, 50, 14
    LTEXT "Введите текст:", IDC_STATIC, 10, 10, 80, 10
    EDITTEXT IDC_EDIT1, 10, 25, 180, 14
END

1.2. Создание диалога программно

Пример создания диалога в коде:

HWND hwndDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hwndParent, DialogProc);
ShowWindow(hwndDlg, SW_SHOW);

2. Обработка диалогового окна

Обработка диалогового окна выполняется в специальной процедуре диалога (DialogProc). Эта процедура обрабатывает сообщения, такие как нажатие кнопок или ввод текста.


Пример процедуры диалога:

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_INITDIALOG:
            // Инициализация диалога
            return TRUE;
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK: {
                    char text[100];
                    GetDlgItemText(hwndDlg, IDC_EDIT1, text, sizeof(text));
                    MessageBox(hwndDlg, text, "Введенный текст", MB_OK);
                    EndDialog(hwndDlg, IDOK);
                    return TRUE;
                }
                case IDCANCEL:
                    EndDialog(hwndDlg, IDCANCEL);
                    return TRUE;
            }
            break;
        case WM_CLOSE:
            EndDialog(hwndDlg, IDCANCEL);
            return TRUE;
    }
    return FALSE;
}

3. Отображение модального диалога

Модальные диалоги блокируют взаимодействие с родительским окном до закрытия. Для отображения модального диалога используется функция DialogBox.

Пример:

DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hwndParent, DialogProc);

4. Отображение немодального диалога

Немодальные диалоги позволяют пользователю взаимодействовать с родительским окном. Для отображения немодального диалога используется функция CreateDialog.

Пример:

HWND hwndDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hwndParent, DialogProc);
ShowWindow(hwndDlg, SW_SHOW);

5. Работа с элементами управления

Диалоговые окна содержат элементы управления, такие как кнопки, текстовые поля, списки и т.д. Для работы с этими элементами используются функции API, такие как GetDlgItemText, SetDlgItemText, SendDlgItemMessage.

5.1. Получение текста из текстового поля

Пример:

char text[100];
GetDlgItemText(hwndDlg, IDC_EDIT1, text, sizeof(text));

5.2. Установка текста в текстовое поле

Пример:

SetDlgItemText(hwndDlg, IDC_EDIT1, "Привет, мир!");

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

Пример обработки нажатия кнопки:

case WM_COMMAND:
    if (LOWORD(wParam) == IDC_BUTTON1) {
        MessageBox(hwndDlg, "Кнопка нажата!", "Уведомление", MB_OK);
    }
    break;

6. Пример программы с диалоговым окном

Рассмотрим пример программы, которая отображает модальное диалоговое окно с текстовым полем и кнопками.


6.1. Файл ресурсов (app.rc)

IDD_DIALOG1 DIALOGEX 0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Пример диалога"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON "OK", IDOK, 10, 70, 50, 14
    PUSHBUTTON "Отмена", IDCANCEL, 70, 70, 50, 14
    LTEXT "Введите текст:", IDC_STATIC, 10, 10, 80, 10
    EDITTEXT IDC_EDIT1, 10, 25, 180, 14
END

6.2. Код программы (main.c)

#include <windows.h>

#define IDC_EDIT1 101

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_INITDIALOG:
            return TRUE;
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK: {
                    char text[100];
                    GetDlgItemText(hwndDlg, IDC_EDIT1, text, sizeof(text));
                    MessageBox(hwndDlg, text, "Введенный текст", MB_OK);
                    EndDialog(hwndDlg, IDOK);
                    return TRUE;
                }
                case IDCANCEL:
                    EndDialog(hwndDlg, IDCANCEL);
                    return TRUE;
            }
            break;
        case WM_CLOSE:
            EndDialog(hwndDlg, IDCANCEL);
            return TRUE;
    }
    return FALSE;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
    return 0;
}

Прикладной интерфейс для обработки пользовательского ввода


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


1. Обработка ввода с клавиатуры

Клавиатурный ввод обрабатывается с помощью сообщений WM_KEYDOWN, WM_KEYUP, WM_CHAR и других.

1.1. Сообщения клавиатуры

  • WM_KEYDOWN: генерируется при нажатии клавиши.

  • WM_KEYUP: генерируется при отпускании клавиши.

  • WM_CHAR: генерируется при вводе символа (с учетом раскладки клавиатуры).

Пример обработки клавиатурного ввода:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_KEYDOWN:
            MessageBox(hwnd, "Клавиша нажата", "Клавиатура", MB_OK);
            break;
        case WM_CHAR:
            if (wParam == 'A') {
                MessageBox(hwnd, "Нажата клавиша 'A'", "Клавиатура", MB_OK);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

1.2. Параметры сообщений клавиатуры

  • WPARAM: виртуальный код клавиши (Virtual Key Code). Например, VK_SPACE для пробела, VK_RETURN для Enter.
  • LPARAM: содержит дополнительную информацию, такую как:
    • Повторение нажатия (биты 0–15).
    • Скан-код клавиши (биты 16–23).
    • Флаги расширенных клавиш (бит 24).
    • Состояние клавиш-модификаторов (Alt, Ctrl, Shift) (биты 29–31).

Пример обработки WM_KEYDOWN:

case WM_KEYDOWN:
    if (wParam == VK_SPACE) {
        MessageBox(hwnd, "Пробел нажат", "Клавиатура", MB_OK);
    }
    break;

1.3. Функции для работы с клавиатурой

  • GetAsyncKeyState: проверяет состояние клавиши (нажата/не нажата).
  • GetKeyboardState: получает состояние всех клавиш клавиатуры.

Пример использования GetAsyncKeyState:

if (GetAsyncKeyState(VK_SPACE) {
    MessageBox(hwnd, "Пробел нажат", "Клавиатура", MB_OK);
}

2. Обработка ввода с мыши

Мышиный ввод обрабатывается с помощью сообщений, таких как WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_RBUTTONDOWN и других.

2.1. Сообщения мыши

  • WM_MOUSEMOVE: генерируется при перемещении мыши.
  • WM_LBUTTONDOWN: генерируется при нажатии левой кнопки мыши.
  • WM_RBUTTONDOWN: генерируется при нажатии правой кнопки мыши.
  • WM_MOUSEWHEEL: генерируется при прокрутке колеса мыши.

Пример обработки мышиного ввода:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_LBUTTONDOWN:
            MessageBox(hwnd, "Левая кнопка мыши нажата", "Мышь", MB_OK);
            break;
        case WM_RBUTTONDOWN:
            MessageBox(hwnd, "Правая кнопка мыши нажата", "Мышь", MB_OK);
            break;
        case WM_MOUSEMOVE: {
            int x = LOWORD(lParam);
            int y = HIWORD(lParam);
            char buffer[50];
            sprintf(buffer, "Мышь: X=%d, Y=%d", x, y);
            SetWindowText(hwnd, buffer);
            break;
        }
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

2.2. Параметры сообщений мыши

  • WPARAM: состояние клавиш-модификаторов (Ctrl, Shift, Alt) и кнопок мыши.
    • MK_LBUTTON: нажата левая кнопка мыши.
    • MK_RBUTTON: нажата правая кнопка мыши.
    • MK_MBUTTON: нажата средняя кнопка мыши.
    • MK_CONTROL: нажата клавиша Ctrl.
    • MK_SHIFT: нажата клавиша Shift.
  • LPARAM: координаты курсора (X и Y) относительно верхнего левого угла клиентской области окна.
    • LOWORD(lParam): координата X.

    • HIWORD(lParam): координата Y.

Пример обработки WM_MOUSEMOVE:

case WM_MOUSEMOVE: {
    int x = LOWORD(lParam);
    int y = HIWORD(lParam);
    char buffer[50];
    sprintf(buffer, "Мышь: X=%d, Y=%d", x, y);
    SetWindowText(hwnd, buffer);
    break;
}

2.3. Сообщение WM_MOUSEWHEEL

  • WPARAM:
    • HIWORD(wParam): значение прокрутки (положительное — вверх, отрицательное — вниз).
    • LOWORD(wParam): состояние клавиш-модификаторов.
  • LPARAM: координаты курсора (X и Y) в экранных координатах.

Пример обработки WM_MOUSEWHEEL:

case WM_MOUSEWHEEL: {
    int delta = GET_WHEEL_DELTA_WPARAM(wParam); // Значение прокрутки
    if (delta > 0) {
        MessageBox(hwnd, "Прокрутка вверх", "Мышь", MB_OK);
    } else {
        MessageBox(hwnd, "Прокрутка вниз", "Мышь", MB_OK);
    }
    break;
}

2.4. Функции для работы с мышью

  • GetCursorPos: получает текущие координаты курсора.
  • SetCursorPos: устанавливает позицию курсора.

Пример использования GetCursorPos:

POINT pt;
GetCursorPos(&pt);
char buffer[50];
sprintf(buffer, "Курсор: X=%d, Y=%d", pt.x, pt.y);
MessageBox(hwnd, buffer, "Мышь", MB_OK);

3. Обработка сенсорного ввода

Сенсорный ввод обрабатывается с помощью сообщений, таких как WM_TOUCH, и функций, таких как GetTouchInputInfo.

3.1. Сообщения сенсорного ввода

  • WM_TOUCH: генерируется при касании экрана.

Параметры сообщения WM_TOUCH: — WPARAM: количество точек касания. — LPARAM: дескриптор структуры HTOUCHINPUT, содержащей информацию о касаниях.


Пример обработки сенсорного ввода:

case WM_TOUCH: {
    UINT inputCount = LOWORD(wParam);
    HTOUCHINPUT hTouchInput = (HTOUCHINPUT)lParam;
    TOUCHINPUT* pTouchInputs = new TOUCHINPUT[inputCount];
    if (GetTouchInputInfo(hTouchInput, inputCount, pTouchInputs, sizeof(TOUCHINPUT))) {
        for (UINT i = 0; i < inputCount; i++) {
            int x = pTouchInputs[i].x / 100;
            int y = pTouchInputs[i].y / 100;
            char buffer[50];
            sprintf(buffer, "Касание: X=%d, Y=%d", x, y);
            MessageBox(hwnd, buffer, "Сенсорный ввод", MB_OK);
        }
    }
    delete[] pTouchInputs;
    CloseTouchInputHandle(hTouchInput);
    break;
}

4. Обработка голосового ввода

Голосовой ввод может быть обработан с помощью API распознавания речи, такого как Windows Speech Recognition или Microsoft Speech API (SAPI).


4.1. Использование SAPI

Пример инициализации распознавания речи:

#include <sapi.h>

ISpRecognizer* pRecognizer = NULL;
CoInitialize(NULL);
CoCreateInstance(CLSID_SpSharedRecognizer, NULL, CLSCTX_ALL, IID_ISpRecognizer, (void**)&pRecognizer);

ISpRecoContext* pRecoContext = NULL;
pRecognizer->CreateRecoContext(&pRecoContext);

pRecoContext->SetNotifyWindowMessage(hwnd, WM_USER, 0, 0);
pRecoContext->SetInterest(SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION));

ISpRecoGrammar* pGrammar = NULL;
pRecoContext->CreateGrammar(0, &pGrammar);
pGrammar->LoadDictation(NULL, SPLO_STATIC);
pGrammar->SetDictationState(SPRS_ACTIVE);

5. Пример программы для обработки пользовательского ввода

Рассмотрим пример программы, которая обрабатывает клавиатурный и мышиный ввод.


5.1. Код программы (main.c)

#include <windows.h>
#include <stdio.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_KEYDOWN:
            MessageBox(hwnd, "Клавиша нажата", "Клавиатура", MB_OK);
            break;
        case WM_CHAR:
            if (wParam == 'A') {
                MessageBox(hwnd, "Нажата клавиша 'A'", "Клавиатура", MB_OK);
            }
            break;
        case WM_LBUTTONDOWN:
            MessageBox(hwnd, "Левая кнопка мыши нажата", "Мышь", MB_OK);
            break;
        case WM_RBUTTONDOWN:
            MessageBox(hwnd, "Правая кнопка мыши нажата", "Мышь", MB_OK);
            break;
        case WM_MOUSEMOVE: {
            int x = LOWORD(lParam);
            int y = HIWORD(lParam);
            char buffer[50];
            sprintf(buffer, "Мышь: X=%d, Y=%d", x, y);
            SetWindowText(hwnd, buffer);
            break;
        }
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc = {0};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = "MyWindowClass";
    RegisterClass(&wc);

    HWND hwnd = CreateWindow("MyWindowClass", "Обработка ввода", WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

Наверх

Эта статья предназначена для тех, кто только начинает своё знакомство с созданием приложений с графическим интерфейсом (GUI) на Python. В ней мы рассмотрим основы использования PyQt в связке с Qt Designer. Шаг за шагом мы создадим простое Python GUI приложение, которое будет отображать содержимое выбранной директории.

Что нам потребуется

Нам понадобятся PyQt и Qt Designer, ну и Python, само собой.

В этой статье используется PyQt5 с Python 3, но особых различий между PyQt и PySide или их версиями для Python 2 нет.

Windows: PyQt можно скачать здесь. В комплекте с ним идёт Qt Designer.

macOS: Вы можете установить PyQt с помощью Homebrew:

$ brew install pyqt5

Скачать пакет с большинством компонентов и инструментов Qt, который содержит Qt Designer, можно по этой ссылке.

Linux: Всё нужное, вероятно, есть в репозиториях вашего дистрибутива. Qt Designer можно установить из Центра Приложений, но PyQt придётся устанавливать через терминал. Установить всё, что нам понадобится, одной командой можно, например, так:

			# для Fedora:
$ sudo dnf install python3-qt5 qt-creator
# для Debian/Ubuntu:
$ sudo apt install python3-qt5 pyqt5-dev-tools qtcreator
		

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

			$ pyuic5
Error: one input ui-file must be specified
		

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

Если вы используете Windows, то, скорее всего, путь C:\Python36\Scripts (измените 36 на вашу версию Python) не прописан в вашем PATH. Загляните в этот тред на Stack Overflow, чтобы узнать, как решить проблему.

Дизайн

Основы

Теперь, когда у нас всё готово к работе, давайте начнём с простого дизайна.

Откройте Qt Designer, где вы увидите диалог новой формы, выберите Main Window и нажмите Create.

Python GUI: создаём простое приложение с PyQt и Qt Designer 1

После этого у вас должна появиться форма — шаблон для окна, размер которого можно менять и куда можно вставлять объекты из окна виджетов и т.д. Ознакомьтесь с интерфейсом, он довольно простой.

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

Все элементы формы и их иерархия по умолчанию отображаются в правой части окна Qt Designer под названием Object Inspector. Вы с лёгкостью можете удалять объекты, кликая по ним правой кнопкой мыши в этом окне. Или же вы можете выбрать их в основной форме и нажать клавишу DEL на клавиатуре.

Python GUI: создаём простое приложение с PyQt и Qt Designer 2

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

Теперь перетащите куда-нибудь в основную форму List Widget (не List View) и Push Button из Widget Box.

Макеты

Вместо использования фиксированных позиций и размеров элементов в приложении лучше использовать макеты. Фиксированные позиции и размеры у вас будут выглядеть хорошо (пока вы не измените размер окна), но вы никогда не можете быть уверены, что всё будет точно так же на других машинах и/или операционных системах.

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

Давайте создадим нашу первую форму без использования макетов. Перетащите список и кнопку в форме и измените их размер, чтобы вышло вот так:

Python GUI: создаём простое приложение с PyQt и Qt Designer 3

Теперь в меню Qt Designer нажмите Form, затем выберите Preview и увидите что-то похожее на скриншот выше. Выглядит хорошо, не так ли? Но вот что случится, когда мы изменим размер окна:

Python GUI: создаём простое приложение с PyQt и Qt Designer 4

Наши объекты остались на тех же местах и сохранили свои размеры, несмотря на то что размер основного окна изменился и кнопку почти не видно. Вот поэтому в большинстве случаев стоит использовать макеты. Конечно, бывают случаи, когда вам, например, нужна фиксированная или минимальная/максимальная ширина объекта. Но вообще при разработке приложения лучше использовать макеты.

Основное окно уже поддерживает макеты, поэтому нам ничего не нужно добавлять в нашу форму. Просто кликните правой кнопкой мыши по Main Window в Object Inspector и выберите Lay outLay out vertically. Также вы можете кликнуть правой кнопкой по пустой области в форме и выбрать те же опции:

Python GUI: создаём простое приложение с PyQt и Qt Designer 5

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

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

Python GUI: создаём простое приложение с PyQt и Qt Designer 6

Если у вас не получается переместить элемент в главном окне, вы можете сделать это в окне Object Inspector.

Последние штрихи

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

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

Свойства элементов можно изменить в разделе Property Editor.

Подсказка: вы можете менять размер, передвигать или добавлять часто используемые элементы в интерфейс Qt Designer для ускорения рабочего процесса. Вы можете добавлять скрытые/закрытые части интерфейса через пункт меню View.

Нажмите на кнопку, которую вы добавили в форму. Теперь в Property Editor вы должны видеть все свойства этого элемента. В данный момент нас интересуют objectName и text в разделе QAbstractButton. Вы можете сворачивать разделы в Property Editor нажатием по названию раздела.

Измените значение objectName на btnBrowse и text на Выберите папку.

Должно получиться так:

Python GUI: создаём простое приложение с PyQt и Qt Designer 7

Именем объекта списка является listWidget, что вполне подходит в данном случае.

Сохраните дизайн как design.ui в папке проекта.

Превращаем дизайн в код

Конечно, можно использовать .ui-файлы напрямую из Python-кода, однако есть и другой путь, который может показаться легче. Можно конвертировать код .ui-файла в Python-файл, который мы потом сможем импортировать и использовать. Для этого мы используем команду pyuic5 из терминала/командной строки.

Чтобы конвертировать .ui-файл в Python-файл с названием design.py, используйте следующую команду:

			$ pyuic5 path/to/design.ui -o output/path/to/design.py
		

Пишем код

Теперь у нас есть файл design.py с нужной частью дизайна нашего приложения и мы начинать работу над созданием его логики.

Создайте файл main.py в папке, где находится design.py.

Используем дизайн

Для Python GUI приложения понадобятся следующие модули:

			import sys  # sys нужен для передачи argv в QApplication
from PyQt5 import QtWidgets
		

Также нам нужен код дизайна, который мы создали ранее, поэтому его мы тоже импортируем:

			import design  # Это наш конвертированный файл дизайна
		

Так как файл с дизайном будет полностью перезаписываться каждый раз при изменении дизайна, мы не будем изменять его. Вместо этого мы создадим новый класс ExampleApp, который объединим с кодом дизайна для использования всех его функций:

			class ExampleApp(QtWidgets.QMainWindow, design.Ui_MainWindow):
    def __init__(self):
        # Это здесь нужно для доступа к переменным, методам
        # и т.д. в файле design.py
        super().__init__()
        self.setupUi(self)  # Это нужно для инициализации нашего дизайна
		

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

			def main():
    app = QtWidgets.QApplication(sys.argv)  # Новый экземпляр QApplication
    window = ExampleApp()  # Создаём объект класса ExampleApp
    window.show()  # Показываем окно
    app.exec_()  # и запускаем приложение
		

И чтобы выполнить эту функцию, мы воспользуемся привычной конструкцией:

			if __name__ == '__main__':  # Если мы запускаем файл напрямую, а не импортируем
    main()  # то запускаем функцию main()
		

В итоге main.py выглядит таким образом:

			import sys  # sys нужен для передачи argv в QApplication
from PyQt5 import QtWidgets
import design  # Это наш конвертированный файл дизайна

class ExampleApp(QtWidgets.QMainWindow, design.Ui_MainWindow):
    def __init__(self):
        # Это здесь нужно для доступа к переменным, методам
        # и т.д. в файле design.py
        super().__init__()
        self.setupUi(self)  # Это нужно для инициализации нашего дизайна

def main():
    app = QtWidgets.QApplication(sys.argv)  # Новый экземпляр QApplication
    window = ExampleApp()  # Создаём объект класса ExampleApp
    window.show()  # Показываем окно
    app.exec_()  # и запускаем приложение

if __name__ == '__main__':  # Если мы запускаем файл напрямую, а не импортируем
    main()  # то запускаем функцию main()
		

Если запустить этот код: $ python3 main.py, то наше приложение запустится!

Python GUI: создаём простое приложение с PyQt и Qt Designer 8

Но нажатие на кнопку ничего не даёт, поэтому нам придётся с этим разобраться.

Добавляем функциональность в наше Python GUI приложение

Примечание Весь дальнейший код пишется внутри класса ExampleApp.

Начнём с кнопки Выберите папку. Привязать к функции событие вроде нажатия на кнопку можно следующим образом:

			self.btnBrowse.clicked.connect(self.browse_folder)
		

Добавьте эту строку в метод __init__ класса ExampleApp, чтобы выполнить привязку при запуске приложения. А теперь взглянем на неё поближе:

  • self.btnBrowse: здесь btnBrowse — имя объекта, который мы определили в Qt Designer. self говорит само за себя и означает принадлежность к текущему классу;
  • clicked — событие, которое мы хотим привязать. У разных элементов разные события, например, у виджетов списка есть itemSelectionChanged и т.д.;
  • connect() — метод, который привязывает событие к вызову переданной функции;
  • self.browse_folder — просто функция (метод), которую мы описали в классе ExampleApp.

Для открытия диалога выбора папки мы можем использовать встроенный метод QtWidgets.QFileDialog.getExistingDirectory:

			directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Выберите папку")
		

Если пользователь выберет директорию, переменной directory присвоится абсолютный путь к выбранной директории, в противном случае она будет равна None. Чтобы не выполнять код дальше, если пользователь закроет диалог, мы используем команду if directory:.

Для отображения содержимого директории нам нужно импортировать os:

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

Для добавления элементов в listWidget мы используем метод addItem(), а для удаления всех элементов у нас есть self.listWidget.clear().

В итоге функция browse_folder должна выглядеть так:

			def browse_folder(self):
    self.listWidget.clear()  # На случай, если в списке уже есть элементы
    directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Выберите папку")
    # открыть диалог выбора директории и установить значение переменной
    # равной пути к выбранной директории

    if directory:  # не продолжать выполнение, если пользователь не выбрал директорию
        for file_name in os.listdir(directory):  # для каждого файла в директории
            self.listWidget.addItem(file_name)   # добавить файл в listWidget
		

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

Python GUI: создаём простое приложение с PyQt и Qt Designer 9

Так выглядит весь код нашего Python GUI приложения:

			import sys  # sys нужен для передачи argv в QApplication
import os  # Отсюда нам понадобятся методы для отображения содержимого директорий

from PyQt5 import QtWidgets

import design  # Это наш конвертированный файл дизайна

class ExampleApp(QtWidgets.QMainWindow, design.Ui_MainWindow):
    def __init__(self):
        # Это здесь нужно для доступа к переменным, методам
        # и т.д. в файле design.py
        super().__init__()
        self.setupUi(self)  # Это нужно для инициализации нашего дизайна
        self.btnBrowse.clicked.connect(self.browse_folder)  # Выполнить функцию browse_folder
                                                            # при нажатии кнопки

    def browse_folder(self):
        self.listWidget.clear()  # На случай, если в списке уже есть элементы
        directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Выберите папку")
        # открыть диалог выбора директории и установить значение переменной
        # равной пути к выбранной директории

        if directory:  # не продолжать выполнение, если пользователь не выбрал директорию
            for file_name in os.listdir(directory):  # для каждого файла в директории
                self.listWidget.addItem(file_name)   # добавить файл в listWidget

def main():
    app = QtWidgets.QApplication(sys.argv)  # Новый экземпляр QApplication
    window = ExampleApp()  # Создаём объект класса ExampleApp
    window.show()  # Показываем окно
    app.exec_()  # и запускаем приложение

if __name__ == '__main__':  # Если мы запускаем файл напрямую, а не импортируем
    main()  # то запускаем функцию main()
		

Это были основы использования Qt Designer и PyQt для разработки Python GUI приложения. Теперь вы можете спокойно изменять дизайн приложения и использовать команду pyuic5 без страха потерять написанный код.

Данная статья является переводом. Ссылка на оригинал.

Tkinter

Tkinter

Tkinter

Оптимален для простых утилит.

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

Разработан создателем Python Гвидо ван Россумом совместно со Стином Ламхолтом. Распространяется под лицензией Python Software Foundation, которая позволяет использовать библиотеку как в открытых, так и в проприетарных проектах.

Простое Hello-world приложение:

        import tkinter as tk

window = tk.Tk()
window.title("Hello World")


def handle_button_press(event):
    window.destroy()


button = tk.Button(text="My simple app.")
button.bind("", handle_button_press)
button.pack()

# Start the event loop.
window.mainloop()

    
🐍🎨 Собираем идеальный GUI на Python: ТОП-8 библиотек 2025 года

  • Туториал
  • Документация

Статьи по теме

  • 🐍 Python, Tkinter и SQL: разрабатываем приложение для создания словарей и запоминания иностранных слов
  • 🐍 Самоучитель по Python для начинающих. Часть 20: Графический интерфейс на Tkinter

PyQt и PySide

PyQt

PyQt

Инструменты для создания профессиональных десктопных приложений.

PyQt и PySide — это обертки для фреймворка Qt, позволяющие создавать современные приложения для Windows, macOS, Linux и Android. Их главное преимущество — Qt Creator с визуальным редактором интерфейсов и обширная экосистема с документацией и поддержкой сообщества.

Qt предоставляет не просто инструменты для создания GUI, а полноценный фреймворк разработки. Помимо стандартных виджетов, он включает:

  • Способ организации кода MVC
  • Работу с базами данных
  • Построение графиков и векторную графику
  • Поддержку мультимедиа и звука
  • Интерфейсы для работы с периферией

PyQt (1998) распространяется под GPL v3, что требует открытия исходного кода приложений или покупки коммерческой лицензии. PySide (2009) под LGPL позволяет использование в закрытых проектах без дополнительных платежей.

Установка: pip install pyqt6 или pip install pyside6

Hello-world приложение на PyQt6:

        from PyQt6.QtWidgets import QMainWindow, QApplication, QPushButton

import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Hello World")

        button = QPushButton("My simple app.")
        button.pressed.connect(self.close)

        self.setCentralWidget(button)
        self.show()


app = QApplication(sys.argv)
w = MainWindow()
app.exec()
    

Hello-world приложение на PySide6:

        from PySide6.QtWidgets import QMainWindow, QApplication, QPushButton

import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Hello World")

        button = QPushButton("My simple app.")
        button.pressed.connect(self.close)

        self.setCentralWidget(button)
        self.show()


app = QApplication(sys.argv)
w = MainWindow()
app.exec()
    
🐍🎨 Собираем идеальный GUI на Python: ТОП-8 библиотек 2025 года

PySide6:

  • Учебник по PySide6
  • Сайт PySide
  • Документация PySide
  • Репозиторий на GitHub

PyQt6:

  • Учебник по PyQt6
  • Сайт PyQt
  • Документация PyQt6

PyQt5:

  • Книга по PyQt5
  • Учебник по PyQt5
  • Документация PyQt6

Qt Quick/QML в PyQt и PySide

Идеальное решение для встраиваемых систем и сенсорных интерфейсов.

PyQt и PySide предлагают два подхода к созданию интерфейсов:

  • Qt Widgets — для классических десктопных приложений
  • Qt Quick/QML — декларативный подход для современных интерфейсов

Архитектура приложения разделена на:

  • Python-код для логики приложения
  • QML-файлы для описания интерфейса

Такое разделение позволяет:

  • Управлять интерфейсом из Python
  • Обрабатывать события и анимации через JavaScript

Основные области применения

  • Встраиваемые системы (Raspberry Pi)
  • Промышленные контроллеры
  • Бытовая электроника с сенсорным управлением
  • Кастомизированные десктопные приложения и игры

Установка: pip install pyqt6 или pip install pyside6

Приложение Hello World на PyQt6 с использованием QML. Сохраните файл QML в той же папке, что и файл Python, и запустите его как обычно.

main.py:

        import sys

from PyQt6.QtGui import QGuiApplication
from PyQt6.QtQml import QQmlApplicationEngine


app = QGuiApplication(sys.argv)

engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('main.qml')

sys.exit(app.exec())

    

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
    }

}
    
🐍🎨 Собираем идеальный GUI на Python: ТОП-8 библиотек 2025 года

PyQt:

  • Урок по QML/PyQt5
  • Урок по QML/PyQt6
  • Веб-сайт PyQt
  • Документация PyQt6

PySide:

  • Урок по QML/PySide2
  • Урок по QML/PySide6
  • Веб-сайт PySide
  • Документация PySide
  • Репозиторий на GitHub

🐍🎓 Библиотека Python для собеса

Kivy

Kivy

Kivy

Идеальный фреймворк для мобильной разработки.

Ключевые особенности

  • Написан преимущественно на чистом Python (в отличие от других GUI-фреймворков)
  • Специализируется на сенсорных интерфейсах для Android и iOS
  • Работает также на Windows, macOS и Linux
  • Широкое сообщество и богатая база обучающих материалов

Возможности и компоненты

  • Высокая гибкость настройки интерфейса
  • Собственный язык разметки Kv для отделения дизайна от логики
  • Альтернатива Pygame для создания игр
  • KivyMD — дополнение с виджетами в стиле Material Design
        from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.core.window import Window

Window.size = (300, 200)


class MainWindow(BoxLayout):
    def __init__(self):
        super().__init__()
        self.button = Button(text="Hello, World?")
        self.button.bind(on_press=self.handle_button_clicked)

        self.add_widget(button)

    def handle_button_clicked(self, event):
        self.button.text = "Hello, World!"


class MyApp(App):
    def build(self):
        self.title = "Hello, World!"
        return MainWindow()


app = MyApp()
app.run()
    
🐍🎨 Собираем идеальный GUI на Python: ТОП-8 библиотек 2025 года

Эквивалентное приложение, созданное с использованием декларативного языка Kv, показано ниже.

main.py:

        import kivy
kivy.require('1.0.5')

from kivy.uix.floatlayout import FloatLayout
from kivy.app import App
from kivy.properties import ObjectProperty, StringProperty


class Controller(FloatLayout):
    '''Create a controller that receives a custom widget from the kv lang file.

    Add an action to be called from the kv lang file.
    '''
    def button_pressed(self):
        self.button_wid.text = 'Hello, World!'


class ControllerApp(App):

    def build(self):
        return Controller()


if __name__ == '__main__':
    ControllerApp().run()
    

controller.kv:

        #:kivy 1.0

:
    button_wid: custom_button

    BoxLayout:
        orientation: 'vertical'
        padding: 20

        Button:
            id: custom_button
            text: 'Hello, World?'
            on_press: root.button_pressed()
    

Имя файла Kv должно совпадать с именем класса из основного приложения — здесь это Controller и controller.kv.

🐍🎨 Собираем идеальный GUI на Python: ТОП-8 библиотек 2025 года

  • Веб-сайт Kivy
  • Документация Kivy
  • Репозиторий на GitHub
  • Документация KivyMD
  • Документация языка Kv

🐍🧩 Библиотека задач по Python

BeeWare Toga

BeeWare Toga

BeeWare Toga

Создание кроссплатформенных приложений с нативным интерфейсом.

BeeWare — это экосистема инструментов для разработки приложений на Python с использованием нативных элементов интерфейса. Ваши программы будут выглядеть и работать как приложения операционной системы, используя системные виджеты и стандартное поведение.

Основные компоненты

  • Toga — библиотека нативных виджетов для всех платформ
  • Briefcase — инструмент для создания готовых пакетов приложений
  • Нативные API — библиотеки для работы с системными функциями

Особенности

  • Единый код для всех платформ
  • Нативный внешний вид на каждой ОС
  • Простота развертывания через Briefcase
  • Открытый исходный код (лицензия BSD 3-Clause)

Установка: pip install toga

        import toga
from toga.style import Pack


class HelloWorld(toga.App):
    def startup(self):
        layout = toga.Box()

        self.button = toga.Button(
            "Say Hello!",
            on_press=self.say_hello,
            style=Pack(margin=5),
        )
        layout.add(self.button)

        self.main_window = toga.MainWindow(title="Hello world!")
        self.main_window.content = layout
        self.main_window.show()

    def say_hello(self, source_widget):
        # Receives the button that was clicked.
        source_widget.text = "Hello, world!"


app = HelloWorld(formal_name="Hello, world!", app_id="hello.world")
app.main_loop()
    
🐍🎨 Собираем идеальный GUI на Python: ТОП-8 библиотек 2025 года

  • Урок по Toga
  • Главная страница BeeWare

WxPython

WxPython

WxPython

Лучший выбор для простых настольных кроссплатформенных приложений.

WxPython представляет собой оболочку для популярного GUI-фреймворка WxWidgets. Реализован в виде набора Python-модулей расширения, которые обеспечивают работу с компонентами графического интерфейса известной кроссплатформенной библиотеки wxWidgets. Сама библиотека написана на C++.

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

Как WxWidgets, так и WxPython распространяются под лицензией WxWindows Library License — свободной лицензией, схожей с LGPL (с особыми условиями). Эта лицензия позволяет использовать и модифицировать WxPython как в проприетарных, так и в открытых проектах.

Установка: pip install wxpython

        import wx

class MainWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(200, -1))

        self.button = wx.Button(self, label="My simple app.")
        self.Bind(
            wx.EVT_BUTTON, self.handle_button_click, self.button
        )

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.button)

        self.SetSizer(self.sizer)
        self.SetAutoLayout(True)
        self.Show()

    def handle_button_click(self, event):
        self.Close()


app = wx.App(False)
w = MainWindow(None, "Hello World")
app.MainLoop()
    
🐍🎨 Собираем идеальный GUI на Python: ТОП-8 библиотек 2025 года

  • Веб-сайт WxPython
  • Вики WxPython
  • Репозиторий на GitHub

PyGObject (GTK+)

PyGObject (GTK+)

PyGObject (GTK+)

Оптимальный выбор для разработки приложений под GNOME.

Если вы планируете создать приложение, которое будет органично работать в GNOME и других GTK-окружениях Linux, PyGObject станет для вас оптимальным решением. Это библиотека, обеспечивающая связь Python с инструментарием GTK+. С ее помощью можно создавать современные адаптивные интерфейсы, соответствующие рекомендациям по проектированию интерфейсов GNOME (HIG).

PyGObject позволяет разрабатывать универсальные приложения, способные работать как на компьютерах, так и на мобильных устройствах с Linux на борту. Разработчикам доступны как официальные, так и сторонние инструменты от сообщества. Среди них — GNOME Builder и Glade (визуальный редактор для быстрого создания графических интерфейсов).

Библиотека распространяется под лицензией LGPL версии 2.1. Хотя эта версия отличается от текущей LGPL 3.0, она по-прежнему разрешает использование в проприетарном ПО, но требует открытия исходного кода при модификации самой библиотеки.

Установка:

  • Ubuntu/Debian:
    sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-4.0
  • macOS:
    brew install pygobject4 gtk+4
        import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk

def on_activate(app):
    win = Gtk.ApplicationWindow(application=app)
    btn = Gtk.Button(label="Hello, World!")
    btn.connect('clicked', lambda x: win.close())
    win.set_child(btn)
    win.present()

app = Gtk.Application(application_id='org.gtk.Example')
app.connect('activate', on_activate)
app.run(None)
    
🐍🎨 Собираем идеальный GUI на Python: ТОП-8 библиотек 2025 года

  • Главная страница проекта PyGObject
  • Документация PyGObject
  • Репозиторий на GitLab

Remi

Remi

Remi

Лучшее решение для создания веб-интерфейсов.

Remi (аббревиатура от REMote Interface – «удаленный интерфейс») – идеальное решение для приложений, работающих на серверах и системах без графического интерфейса, таких как Raspberry Pi. В отличие от большинства других фреймворков для создания GUI, Remi отображается в браузере благодаря встроенному веб-серверу. Это делает его по-настоящему кроссплатформенным решением, одинаково хорошо работающим на любой платформе.

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

Важно понимать: Remi создан для разработки классических приложений с графическим интерфейсом, а не для создания веб-сайтов. При одновременном подключении нескольких пользователей они будут работать с одним и тем же интерфейсом – словно приложением пользуется один пользователь.

Для работы с Remi достаточно базовых знаний Python – код автоматически преобразуется в HTML. В комплект входит визуальный редактор с поддержкой перетаскивания элементов (drag-and-drop), похожий на Qt Designer для PyQt и PySide.

Remi распространяется под лицензией Apache License v2.0 – это «разрешительная» лицензия, схожая с MIT License. Она позволяет использовать фреймворк как в открытых, так и в проприетарных проектах, включая возможность вносить закрытые изменения в сам фреймворк. Основные требования лицензии касаются сохранения уведомлений об авторских правах и условий лицензирования.

        import remi.gui as gui
from remi import start, App

class MyApp(App):

    def main(self):
        container = gui.VBox(width=120, height=100)

        # Create a button, with the label "Hello, World!"
        self.bt = gui.Button('Hello, World?')
        self.bt.onclick.do(self.on_button_pressed)

        # Add the button to the container, and return it.
        container.append(self.bt)
        return container

    def on_button_pressed(self, widget):
        self.bt.set_text('Hello, World!')

start(MyApp)
    
🐍🎨 Собираем идеальный GUI на Python: ТОП-8 библиотек 2025 года

  • Репозиторий на GitHub
  • Reddit (r/RemiGUI)

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Windows 2000 vmware driver
  • Как сделать двойной рабочий стол в windows 10
  • Proxy клиент для windows
  • Как создать учетную запись на компьютере windows 10 одном вторую
  • Windows 10 уменьшить высоту панели задач в windows