Штош. Наверное, каждый начинающий программист после "Hello, world!" хочет написать какой-нибудь простенький проект. Почти всегда в голову приходит идея создания калькулятора. Но консольный калькулятор - это как-то скучно и просто. Хочется сделать приложение вот прямо как в системе. Ну или хотя бы что-то похожее.

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

Мы будем использовать язык Python, фреймворк Qt, библиотеку PySide6, сразу установим её:

pip install PySide6

Qt Designer

Создавать интерфейс мы будем в приложении Qt Designer. Его можно скачать отдельно или найти в папке установленного PySide. Для этого перейдем по пути:

python(или venv*)/Lib/site-packages/PySide6/designer.exe

Создаем Main Window, т.е. главное окно приложения.

Сразу убираем ненужные menubar и statusbar

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

Элементы калькулятора

Перетащим нужные элементы в интерфейс. В нашем калькуляторе будет поле ввода Line Edit.

Label с временным выражением над этим полем ввода.

Grid Layout для кнопок.

Просто закинем эти элементы и выберем "Lay Out Vertically" для центрального виджета.

Теперь закинем кнопки в Grid Layout, у меня будет 4 колонки и 5 рядов. Чтобы скопировать и вставить элемент, можно перетащить его с зажатой клавишей Ctrl.

Поставим текст во все кнопки. Для Backspace мы позже поставим иконку.

Проставим горячие клавиши для всех кнопок, кроме Clear и отрицания. За это отвечает свойство shortcut. К сожалению, в Qt Designer нельзя указать несколько горячих клавиш для одной кнопки. Хотелось бы, чтобы клавиши Enter, Return и = выполняли вычисление. Мы сделаем это позже в коде. А пока поставим для вычисления одинокую клавишу =

Запишем 0 в Line Edit и выберем правое горизонтальное выравнивание для текста.

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

Укажем максимальную длину в 16 символов, как в калькуляторе Windows.

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

Чтобы посмотреть превью дизайна используйте сочетание клавиш Ctrl + R.

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

Размерная политика элементов

Вы спросите: "Почему интерфейс так плохо выглядит?". Все потому, что у элементов не настроена вертикальная политика. Для лейбла и поля поставим Maximum.

Конечно же не забываем сохранить файл интерфейса. Он имеет расширение ui. Обычно я называю файл design.ui

Для всех кнопок поставим Expanding.

Стилизация калькулятора

Сначала нужно определиться с цветовой палитрой. Я буду использовать 4 цвета:

  1. Почти черный #121212 для фона.

  2. Белый #FFF для текста кнопок и поля ввода.

  3. Серый #666 для фона кнопок при наведении.

  4. Серый посветлее #888 для текста временного выражения и фона кнопок при нажатии.

В Qt Designer поддерживается язык css. Напишем простенький stylesheet для главного окна. Для всего виджета указываем белый цвет текста и почти черный цвет #121212 для фона.

Я буду использовать бесплатный шрифт Rubik из библиотеки Google Fonts. Он довольно приятный.

QWidget {
	color: white;
	background-color: #121212;
	font-family: Rubik;
	font-size: 16pt;
	font-weight: 600;
}

Давайте посмотрим, что получается.

Давайте изменим кнопки на плоские с прозрачным фоном.

QPushButton {
	background-color: transparent;
	border: none;
}

Теперь напишем изменение фона кнопок при наведении и нажатии. При наведении цвет фона будет меняться на серый #666, при нажатии на серый #888.

QPushButton:hover {
	background-color: #666;
}

QPushButton:pressed {
	background-color: #888;
}

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

Стили для Line Edit и Label

Сначала разберемся с Line Edit. Поставим размер шрифта 40pt и уберем границы. Я не буду делать какие-то изменения при наведении и нажатии, потому что пользователь не может взаимодействовать с этим полем.

font-size: 40pt;
border: none;

Для лейбла укажем только цвет #888. С этим элементом пользователь тоже не может взаимодействовать.

color: #888;

Иконки

Теперь зайдем на Google Icons и возьмем черную иконку калькулятора и белую иконку backspace. Я возьму Sharp иконки с размером 24 пикселя. Формат выбирайте на ваше усмотрение. По опыту скажу, что лучше SVG. И лучше оно не только в том, что оно без труда масштабируется без потери качества (векторная графика), но еще и скачивается одним файлом. При скачивании PNG вам нужно будет распаковать архив, зайти в одну из двух папок и вытащить саму иконку.

В статье я скачивал PNG, не делайте так. Я думал, что Qt Designer не поддерживает иконки с векторной графикой, даже не попробовав.

Создадим файл ресурсов:

Resource Browser > Edit Resources > New Resource File.

Я сохранил файл с названием files.qrc. Добавим префикс для иконок.

Закинем туда наши две иконки.

Поставим иконку Backspace:

icon > choose Resource

Поставим размер 24 x 24 пикселя в свойстве iconSize

То же самое проделаем для иконки приложения.

Финальные штрихи

Почти готово. Убираем текст из лейбла. Ставим размер главного окна. У меня будет 300 на 500 пикселей. Такой же размер поставлю минимальным для приложения.

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

Редактируем интерфейс в коде

Файл интерфейса представляет собой файл с xml разметкой. Мы можем найти блок кода с указывающей рукой, введя в поиске по коду Pointing

<property name="cursor">
 <cursorShape>PointingHandCursor</cursorShape>
</property>

Заметим, что этот блок кода идет после блока размерной политики. Поэтому нам нужно заменить:

<property name="sizePolicy">
 <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
  <horstretch>0</horstretch>
  <verstretch>0</verstretch>
 </sizepolicy>
</property>

на:

<property name="sizePolicy">
 <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
  <horstretch>0</horstretch>
  <verstretch>0</verstretch>
 </sizepolicy>
</property>
<property name="cursor">
 <cursorShape>PointingHandCursor</cursorShape>
</property>

В современных редакторах это сделать очень просто. Например, в VS Code нужно нажать Ctrl + H.

Впишем нужные блоки кода и нажмем Replace All (Ctrl + Alt + Enter).

Проверяем в дизайне.

Дизайн сделан, поздравляю!

Конвертируем файл ресурсов и интерфейса

Для начала нам нужно конвертировать файл ресурсов в питоновский файл. Для этого напишем в терминале:

pyside6-rcc "название файла ресурсов" > "название Python файла на выходе"

В нашем случае:

pyside6-rcc files.qrc > files_rc.py

Теперь конвертируем в Python файл интерфейса. Для этого введем в терминал тот же самый синтаксис, но теперь используем pyside6-uic:

pyside6-uic design.ui > design.py

Если у вас на выходе получаются файлы с кодировкой UTF-16, конвертируйте их в UTF-8 во избежание дальнейших проблем.

Штош, в следующей статье напишем код для главного функционала калькулятора. До встречи.


Репозиторий на GitHub

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


  1. namikiri
    01.11.2021 16:34
    +1

    Какая же кайфовая КДПВ и дизайн получившегося калькулятора. Спасибо. Никогда не хотел и не собирался делать GUI для питона, но за хороший вкус в дизайне однозначно плюс.


    1. lesskop Автор
      01.11.2021 16:37
      +1

      Благодарю!


  1. SergeiMinaev
    01.11.2021 17:37
    -2

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


    1. lesskop Автор
      01.11.2021 17:48

      На Хабре любят изобретать велосипеды)


  1. technic93
    01.11.2021 19:45
    +4

    Когда вижу подобные видосики, как кто-то что то дизайнет: "Возьмем цвет например такой-то, это сделаем серым наример #444, в для заголовка выберем такой-то паддинг и размер шрифта". Витоге: получилось симпатично.

    Когда пытаюсь дизайнить я: "Полчаса выбираю цвет, меняю с #444 на #333, потом обратно. Настраиваю паддинг и размер, туда-сюда, и так что-то не то, и эдак как-то не так. Открываю сайты с генераций палитр, пробую взять цвет оттуда." Витоге: вырвиглазный ужас.


    1. lesskop Автор
      01.11.2021 20:03

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


    1. protobuf
      01.11.2021 20:34

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


  1. Nikkorot
    02.11.2021 15:26
    +1

    Спасибо. Ждем вторую часть.


  1. Snusmumrick97
    02.11.2021 23:04

    А чем обусловлен выбор PySide а не PyQt ?


    1. lesskop Автор
      02.11.2021 23:38

      Лицензией. Да, у меня open-source проект, но вдруг читатель захочет сделать свой коммерческий? Не возникнет никаких проблем. А по коду библиотеки очень похожи. Заинтересованный в вопросе читатель сам поищет отличия и сделает для себя выводы.


      1. itmind
        03.11.2021 06:00

        PySide бесплатна, а нужно ли покупать лицензию на сам Qt для коммерческих проектов?


        1. lesskop Автор
          03.11.2021 11:28

          Не нужно, насколько я понял. Но еще лучше прочитать условия лицензии LGPL.

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


          1. itmind
            03.11.2021 11:48

            Вы пишите про PyQt и PySide. Это просто прокси библиотеки для обращения к Qt. Т.е. нужна еще сама Qt, а на сайте Qt расписаны только платные лицензии.

            Я задавался этим вопросом лицензирования когда выбирал GUI для Python, но ответа так и не нашел...


            1. lesskop Автор
              03.11.2021 12:15

              Используйте Tkinter и будет вам счастье : )

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

              А может просто плохо ищу, кто знает.


            1. m1n7
              03.11.2021 23:35
              +1

              https://qna.habr.com/q/584331, https://discourse.techart.online/t/using-pyside-for-a-proprietary-product-lgpl-backward-engineering-clause/10425/3

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


  1. saw_tooth
    05.11.2021 16:12

    Не срача ради, а науки для.

    В следующий раз рассмтрите для себя вариант использования QtDarkStyle

    https://github.com/ColinDuquesnoy/QDarkStyleSheet


    1. lesskop Автор
      05.11.2021 17:34
      +1

      Не работает с PySide6 и PyQt6. Если у вас получится, обязательно напишите.

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


  1. ti_zh_vrach
    06.11.2021 16:56
    +1

    А с PySide можно использовать файл ui без конвертации? Если да, то как? С PyQt так можно.


    1. lesskop Автор
      06.11.2021 17:35
      +1

      Нужно использовать QUiLoader. Можете посетить документацию.

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


      1. ti_zh_vrach
        06.11.2021 17:44

        Спасибо! Не там искал.


  1. Panzer_Ex
    18.11.2021 13:13

    Прошу прощения за нубский вопрос, но есть ли возможность в Дизайнере применять стиль для всех QPushButton сразу? ПКМ Change styleSheet срабатывает только на одном элементе, а не всей группе. Или я что-то не так делаю?


    1. lesskop Автор
      18.11.2021 14:42

      Посмотрите внимательно часть про стилизацию. Я использовал стиль для всех кнопок, используя имя объекта кнопки QPushButton и написав этот css код в QMainWindow. Можно было написать и в QWidget, было бы то же самое, потому что это объекты-родители для кнопок.

      QPushButton {
      	background-color: transparent;
      	border: none;
      }

      Для изменения поведения при наведении курсора на кнопку:

      QPushButton:hover {
      	background-color: #666;
      }

      И при нажатии:

      QPushButton:pressed {
      	background-color: #888;
      }


      1. Panzer_Ex
        18.11.2021 15:07

        Заработало, благодарю за разъяснение! :)