1. Введение: Почему мы боимся GUI на Python?

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

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

А что, если я скажу, что можно создать современное, красивое и кроссплатформенное приложение, описывая интерфейс так же просто, как вы пишете обычный Python-код?

В этой статье мы познакомимся с Flet — революционным фреймворком, который позволяет делать именно это. Flet дает вам возможность создавать интерактивные GUI на чистом Python, а для отрисовки использует мощный движок Flutter. Результат — быстрые, красивые и нативные приложения для Windows, macOS, Linux и даже для веба, без необходимости писать ни строчки на Dart или JavaScript.

2. Часть I: Установка и "Hello, Flet!"

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

Давайте установим их одной командой в вашем терминале:

pip install flet markdown

Готово! Теперь, чтобы убедиться, что все работает, и познакомиться с базовой структурой Flet-приложения, создадим файл editor.py и напишем в нем классический "Hello, World".

# editor.py
import flet as ft

def main(page: ft.Page):
    # page — это главный "холст" нашего приложения, само окно.
    # Мы можем настраивать его свойства, например, заголовок.
    page.title = "Мое первое Flet-приложение"
    
    # Создаем текстовый элемент (виджет или "контрол" в терминах Flet).
    hello_text = ft.Text(value="Привет, мир Flet!", size=30)
    
    # Добавляем наш элемент на страницу.
    page.add(hello_text)

# Запускаем приложение, указывая нашу главную функцию в качестве цели.
if __name__ == "__main__":
    ft.app(target=main)

Сохраните и запустите этот файл из терминала: python editor.py. Перед вами должно появиться аккуратное окно с нашим приветствием.

Давайте разберем, что здесь произошло:

  1. Мы определили функцию main, которая принимает один обязательный аргумент — page. Объект ft.Page — это, по сути, ваше окно или веб-страница. Он служит контейнером для всех остальных элементов интерфейса.

  2. ft.Text(...) — это виджет (или Control, как их называет Flet). Это строительный блок нашего GUI: текст, кнопка, поле ввода и так далее.

  3. page.add(...) размещает наши виджеты на странице в том порядке, в котором мы их добавляем.

  4. ft.app(target=main) — это команда, которая инициализирует и запускает наше приложение, передавая управление функции main.

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

3. Часть II: Проектирование интерфейса редактора

Теперь, когда мы знаем, как запустить Flet-приложение, давайте набросаем визуальный скелет нашего редактора. Наша цель проста: создать окно, разделенное на две равные вертикальные панели. Слева будет поле для ввода текста, справа — область для предпросмотра.

Для горизонтального расположения элементов в Flet есть идеальный инструмент — контейнер ft.Row. Он принимает список виджетов и выстраивает их в ряд.

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

# editor.py
import flet as ft

def main(page: ft.Page):
    page.title = "Markdown Редактор"

    # 1. Поле для ввода Markdown текста
    input_field = ft.TextField(
        multiline=True, # Позволяет вводить несколько строк текста
        expand=True,    # Растягивает поле на всю доступную высоту и ширину
        min_lines=40,   # Задает минимальную высоту, чтобы окно не было слишком маленьким
        hint_text="Пишите ваш Markdown здесь..." # Подсказка для пользователя
    )

    # 2. Область для отображения отформатированного результата
    preview_area = ft.Markdown(
        value="Здесь будет ваш **отформатированный** текст.",
        expand=True, # Также растягиваем на все доступное пространство
    )

    # 3. Собираем все в один горизонтальный ряд
    app_layout = ft.Row(
        # `controls` — это список дочерних элементов
        controls=[
            input_field,
            ft.VerticalDivider(), # Аккуратный вертикальный разделитель
            preview_area
        ],
        expand=True, # Растягиваем сам ряд на все окно
    )

    page.add(app_layout)

if __name__ == "__main__":
    ft.app(target=main)

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

Ключевые моменты, на которые стоит обратить внимание:

  • ft.TextField: Это наш основной инструмент для ввода. Свойство multiline=True превращает его из однострочного поля в полноценную текстовую область.

  • ft.Markdown: Это специальный, очень удобный виджет Flet. Вам не нужно вручную вызывать библиотеку markdown и работать с HTML. Вы просто передаете ему строку с Markdown-разметкой в свойство value, и он сам ее красиво отрисовывает.

  • expand=True: Это одно из самых важных свойств в Flet. Оно говорит виджету: "займи все свободное пространство, которое тебе выделил твой родительский контейнер". Мы применили его к обеим панелям, чтобы они поделили ft.Row поровну, и к самому ft.Row, чтобы он занял все окно page.

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

4. Часть III: Оживляем редактор — логика предпросмотра

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

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

Для нашего TextField ключевым является событие on_change. Оно срабатывает каждый раз, когда пользователь вводит или удаляет хотя бы один символ. Это именно то, что нам нужно для предпросмотра в реальном времени.

Давайте напишем функцию-обработчик и привяжем ее к нашему полю ввода.

Дополним наш код:

# editor.py
import flet as ft

def main(page: ft.Page):
    page.title = "Markdown Редактор"

    # --- 1. Создаем функцию-обработчик ---
    def handle_text_change(e):
        # 'e' — это объект события, который Flet передает в функцию.
        # e.control — это ссылка на виджет, который вызвал событие (наш TextField).
        # e.control.value — это текущий текст в этом виджете.
        
        # Обновляем содержимое правой панели (preview_area)
        preview_area.value = e.control.value
        
        # --- 3. Самый важный шаг: обновляем страницу ---
        page.update()

    input_field = ft.TextField(
        multiline=True,
        expand=True,
        min_lines=40,
        hint_text="Пишите ваш Markdown здесь...",
        # --- 2. Привязываем обработчик к событию on_change ---
        on_change=handle_text_change,
    )

    preview_area = ft.Markdown(
        value="Здесь будет ваш **отформатированный** текст.",
        expand=True,
    )

    app_layout = ft.Row(
        controls=[
            input_field,
            ft.VerticalDivider(),
            preview_area
        ],
        expand=True,
    )

    page.add(app_layout)

if __name__ == "__main__":
    ft.app(target=main)

Давайте разберем, что мы только что сделали:

  1. Создали функцию handle_text_change(e). Это наш обработчик. Flet автоматически передает в него объект события, который мы назвали e. Через e.control.value мы можем получить актуальный текст из поля ввода.

  2. Привязали обработчик к виджету. При создании input_field мы добавили новый параметр: on_change=handle_text_change. Обратите внимание: мы передаем саму функцию, без скобок. Теперь Flet знает, какую функцию нужно вызывать при каждом изменении текста.

  3. Вызвали page.update(). Это, пожалуй, самый важный шаг. После того как мы программно изменили свойство виджета (preview_area.value = ...), Flet не перерисовывает интерфейс автоматически (это сделано для оптимизации). Команда page.update() — это явный приказ: "Эй, Flet, я внес изменения, пожалуйста, обнови окно, чтобы пользователь их увидел!".

Теперь сохраните и снова запустите приложение. Начните вводить в левое поле текст с Markdown-разметкой, например:

# Это главный заголовок

Это обычный параграф с **жирным** и *курсивным* текстом.

- Пункт списка 1
- Пункт списка 2

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

5. Часть IV: Финальные штрихи и полный код

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

Все эти настройки применяются к объекту page в самом начале нашей функции main.

def main(page: ft.Page):
    # --- Настройки окна ---
    page.title = "Flet Markdown Editor"
    page.window_width = 1200
    page.window_height = 800
    page.padding = 10
    
    # Устанавливаем светлую тему по умолчанию
    page.theme_mode = ft.ThemeMode.LIGHT
    
    # ... остальной код ...

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

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

Показать полный код приложения
# editor.py
import flet as ft

def main(page: ft.Page):
    # --- 1. Настройки окна ---
    page.title = "Flet Markdown Editor"
    page.window_width = 1200
    page.window_height = 800
    page.padding = 10
    
    # Устанавливаем светлую тему по умолчанию
    page.theme_mode = ft.ThemeMode.LIGHT

    # --- 2. Логика приложения: обработчик событий ---
    def handle_text_change(e):
        """
        Вызывается при каждом изменении текста в поле ввода.
        Обновляет область предпросмотра.
        """
        preview_area.value = e.control.value
        page.update()

    # --- 3. Виджеты интерфейса ---
    input_field = ft.TextField(
        multiline=True,
        expand=True,
        min_lines=40,
        hint_text="Пишите ваш Markdown здесь...\n\n# Заголовок\n\n* Список\n* Элементов",
        # Убираем стандартную рамку для чистого вида
        border_color="transparent",
        on_change=handle_text_change,
    )

    preview_area = ft.Markdown(
        value="Здесь будет ваш **отформатированный** текст.",
        expand=True,
        # Выбираем тему для подсветки синтаксиса в блоках кода
        code_theme="atom-one-dark",
    )

    # --- 4. Сборка интерфейса ---
    app_layout = ft.Row(
        controls=[
            input_field,
            ft.VerticalDivider(),
            preview_area
        ],
        expand=True,
    )

    # --- 5. Добавляем все на страницу и обновляем ---
    page.add(app_layout)
    page.update()

# --- 6. Запуск приложения ---
if __name__ == "__main__":
    ft.app(target=main)

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

6. Заключение и "Домашнее задание"

Всего за несколько десятков строк кода на чистом Python мы создали полезное и современно выглядящее десктопное приложение. Мы не возились со сложными классами окон, низкоуровневыми API или громоздкими компоновщиками. Мы просто описали, что хотим видеть, а Flet сам позаботился о том, как это реализовать.

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

Проект 1: Полноценная работа с файлами

Задача: Любому редактору нужны функции открытия и сохранения файлов. Добавьте в приложение верхнюю панель (AppBar) с двумя кнопками-иконками: "Открыть" и "Сохранить".

Что для этого нужно:

  1. ft.AppBar: Создайте верхнюю панель, в которую можно добавлять элементы, например, ft.IconButton.

  2. ft.FilePicker: Это специальный "невидимый" виджет, который нужно добавить в page.overlay. Он умеет вызывать нативный диалог операционной системы для выбора или сохранения файла.

  3. Логика:

    • При нажатии на кнопку "Открыть" вызывайте метод file_picker.pick_files().

    • При нажатии на "Сохранить" — file_picker.save_file().

    • Результат выбора файла (путь) придет в обработчик события on_result у FilePicker. В этом обработчике вам нужно будет либо прочитать содержимое файла и поместить его в input_field, либо записать input_field.value в выбранный файл.

Проект 2: Менеджер тем и настроек

Задача: Дайте пользователю возможность переключаться между светлой и темной темой. Добавьте в AppBar кнопку, которая будет менять оформление приложения.

Что для этого нужно:

  1. ft.IconButton: Добавьте в AppBar иконку (например, луны или солнца).

  2. Логика:

    • Напишите функцию-обработчик для события on_click этой кнопки.

    • Внутри функции проверяйте текущее значение page.theme_mode.

    • Если тема светлая (ft.ThemeMode.LIGHT), меняйте ее на темную (DARK), и наоборот.

    • Бонусный балл: Сделайте так, чтобы иконка на кнопке тоже менялась в зависимости от выбранной темы (солнце для темной, луна для светлой).

    • Не забудьте в конце обработчика вызвать page.update(), чтобы применить изменения.

Проект 3: Информационная строка состояния (Status Bar)

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

Что для этого нужно:

  1. Изменение компоновки: Вам понадобится обернуть текущий app_layout (наш ft.Row) в ft.Column. Column будет содержать два элемента: app_layout, который растягивается (expand=True), и новый ft.Row внизу, который и будет нашей строкой состояния.

  2. ft.Text: Добавьте в строку состояния один или два текстовых виджета.

  3. Логика:

    • Модифицируйте существующий обработчик handle_text_change.

    • После обновления preview_area, добавьте расчет статистики: len(e.control.value) для символов и len(e.control.value.split()) для слов.

    • Обновите свойство value у текстовых виджетов в строке состояния.

    • Один вызов page.update() в конце функции обновит и предпросмотр, и счетчики.

Анонс новых статей, полезные материалы, а так же если в процессе решения возникнут сложности, обсудить их или задать вопрос по статье можно в моём Telegram-сообществе.

Уверен, у вас все получится. Вперед, к практике!

Комментарии (20)


  1. Emelian
    03.11.2025 12:19

    Почему мы боимся GUI на Python?

    Потому, что проще и выгоднее делать интерфейс пользователя на сверхлегком C++ / WTL, а данные к нему на Питоне.


  1. plustilino
    03.11.2025 12:19

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

    В tkinter есть модуль ttk с более няшными виджетами, если прям очень надо. Есть простой упаковщик pack, если с grid'ом возиться не хочется. Tkinter - хорошее решение для внутренних нужд организации. Распространяемые приложения на нем вряд ли когда особо писались. Так что с чего вдруг он устаревший?


    1. kAIST
      03.11.2025 12:19

      А в чем проблема распространения?

      Распространял софт на питоне + tkinter ещё со временем python 2.5. Исталяшка весила от 3-4 мегабайт.

      Сейчас готовлю к релизу софтину - 50 мегабайт, но там жирные библиотеки в комплекте.


      1. plustilino
        03.11.2025 12:19

        Проблема скорее не в распространении, а в некоторой нестандартности виджетов tkinter.


        1. kAIST
          03.11.2025 12:19

          Если говорить о ttk (который с третьей версии питона вообще идёт в поставке), то вполне себе нативненько все выглядит. Про какие виджеты вы говорите?


          1. plustilino
            03.11.2025 12:19

            Если надо тонко настраивать стили в ttk, это делается не так, как у виджет из основной библиотеки. Придется доучивать. В этом смысле преимущество перед другими библиотеками теряется. Да и набор виджет ttk и tkinter полностью не совпадает. В сложном GUI стилизация может оказаться смешанной. Если предположить, что программа будет монетизироваться, распространяться на как можно более широкую аудиторию, то рекомендовать tkinter мне кажется странным.


  1. hazard2005
    03.11.2025 12:19

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


  1. george3
    03.11.2025 12:19

    Тоже начинал делать свой GUI фреймворк с Flutter и desktop-app. Для заказчиков оказалось этого мало, потому что они хотят под Web и многопользователя. Пришлось Flutter поменять на Vue/Quasar (у Flutter c web тормоза/грабли с отображением были серьезные), вкрутить aio/http/websocket и сделать еще много чего. тогда стало практически полезно.


    1. me21
      03.11.2025 12:19

      Такой фреймворк есть, NiceGUI. Посмотрите https://nicegui.io/

      Работает как в веб режиме, так и в десктопном.


      1. george3
        03.11.2025 12:19

        как по мне https://nicegui.io/ это мура:

        ui.sub_pages({ '/': table_page, '/map/{lat}/{lon}': map_page, }).classes('w-full')

        какие роутеры-классы и зачем, если решили GUI автомат делать?!

        не, мой подход умнее и практичней https://github.com/unisi-tech/unisi

        ничего лишнего, полный автомат на всех уровнях.


        1. me21
          03.11.2025 12:19

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

          Классы - это классы CSS из библиотеки Tailwind. Если они не нужны, их тоже можно не указывать.


          1. george3
            03.11.2025 12:19

            Классы - это классы CSS из библиотеки Tailwind. Если они не нужны, их тоже можно не указывать.

            Если они в Hello word примере, то это врядли. Роутеры зачем? Стили, размеры? Это все должно считаться автоматом или выставляться в сеттинге: "ios Style, Vasya Style, .." Автоматические GUI нужны спецам, которым интересен Web-программинг как мойка посуды или уборка подъездов. Рассказывать спецам по ML или системной инженерии про Tailwind или роутеры или прочую web-шнягу - антигуманно. Им нужно спроецировать их данные в GUI автоматом или, максимум, парой строк.


            1. me21
              03.11.2025 12:19

              Ну я вам говорю. Я просто с использованием этой библиотеки делал интерфейсы. Тут стиль w-full можно убрать, ну внешний вид как-то изменится, наверное. Это не принципиально.
              Ещё раз. Это пример SPA. У них там примеров куча -- https://nicegui.io/#examples.
              Парой строк вполне можно отобразить данные, в стили надо лезть, если внешний вид надо настроить какой-то особенный. Спец по ML + ИИ сейчас это вполне осилит.
              В вашей библиотеке, наверное, тоже стили надо применять, если ширину кнопки надо установить или там отступы, выравнивание по ширине или по левому краю? Как без этого-то. Ну максимум пробросить всё в параметры конструктора, но это дополнительная работа.


              1. george3
                03.11.2025 12:19

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

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


  1. Vdm55
    03.11.2025 12:19

    C# в плане простоты лучше.


  1. laviol
    03.11.2025 12:19

    когда речь заходит о создании десктопного приложения с графическим интерфейсом, энтузиазм часто угасает

    Да потому что самая большая боль не в том, чтобы освоить "монстров", типа PyQt или Tk, у которых я, честно сказать, вижу мало отличий от Flet и при сравнении окажется, что они довольно сильно похожи.

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

    А из статьи я вижу те же яйца, только сбоку, уж простите.


  1. 010011011000101110101
    03.11.2025 12:19

    Когда-то давным давно появился Delphi, а также всякие Вижуал Васики и прочие Вижуал * включая Фокспро. Тогда казалось, что вопросы создания GUI решены окончательно и навсегда. Где и когда мы свернули не туда, что прошло 30 лет и мы опять вырезаем гланды автогеном через анус?


    1. ReinRaus
      03.11.2025 12:19

      Внезапно оказалось, что у нас есть несколько десятков тысяч устройств с разными размерами экранов, разрешением экранов, dpi, ориентацией, цветовыми схемами, настройками доступности чтения, оказалось, что помимо мышки и клавиатуры есть ещё сенсорные экраны и стилус, и Визивиг внезапно оказался ковырянием в песочке, а не серебряной пулей :)

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


  1. AndreyAseev
    03.11.2025 12:19

    Вопрос не в том, насколько страшен GUI, а в том, насколько они нужен. Во времена быстрого интернета и облачных вычислений десктопные приложения вымирают, как класс. С другой стороны, ваше предложение сделать Markdown редактор - это хорошо. Но он же уже есть во всех IDE. А, если честно, сколько GUI приложений вы именно устанавливали на свой ПК за последние 5 лет? Ну, кроме браузера и IDE?

    Последний раз делал на заказ оконные приложения лет 6 назад. NET Forms - неплохо, конечно, но C# - это на любителя. Для Python зашло PyQT. Великолепный редактор форм, простой бутстрап на Python и поддержка PyInstaller из коробки. Да, бинарник жирный на выходе, и лицензия не простая. Но для тех редких случаев вполне хватало.

    К тому же, проблема в том, что Python очень плохо портируется. Где-то за версию ОС цепляется, где-то библиотеки не те, где-то антивирус не пропускает. Поверьте мне, вам это не нужно.


  1. sergeym69
    03.11.2025 12:19

    Ну PyQt приложения пишутся ненамного сложнее, а с использованием CoPilotа все пишется вообще влёт. Даже с со сложной логикой и большим количеством элементов в форме. И работает это везде одинаково.