Кастомизация календаря на PyQt5

от автора

Сегодня столкнулся с простой на первый взгляд задачей: в виджете календаря на PyQt5 сделать так, чтобы сегодняшняя дата обводилась зеленой рамкой. Но оказалось, что на русском языке материалов на эту тему вообще нет, а на английском — только один вопрос на stackoverflow. Решил облегчить жизнь другим разработчикам, которые только знакомятся с этой библиотекой и описать, как я решал эту задачу.

Создаем календарь

Чтобы создать окно с календарем, открываем Qt Creator или Qt Designer. В боковом меню во вкладке Display Widgets выбираем Calendar и перетаскиваем в редактор:

После этого сохраняем получившийся .ui-файл. Чтобы его конвертировать в Python-файл, запускаем в терминале команду:

pyuic5 path/to/design.ui -o output/path/to/design.py

Если вы видите сообщение, что такой команды нет, то, скорее всего, что-то не так с PyQt. Проверьте переменную PATH или попробуйте переустановить PyQt.

После выполнения этой команды появляется файл design.py, в который мы и будем вносить изменения.

Кастомизация календаря

В стандартом виджете подсвечивается только выбранная дата. Чтобы сегодняшняя дата была подсвечена постоянно, нужно сделать свой виджет на основе встроенного. Создаем файл CustomCalendar.py, в котором расширяем класс QtWidgets.QCalendarWidget:

from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import Qt  class MyCalendar(QtWidgets.QCalendarWidget):     def __init__(self, parent=None):         QtWidgets.QCalendarWidget.__init__(self, parent)

Теперь нужно перегрузить метод отрисовки ячейки в календаре:

from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import Qt  class MyCalendar(QtWidgets.QCalendarWidget):     def __init__(self, parent=None):         QtWidgets.QCalendarWidget.__init__(self, parent)      def paintCell(self, painter, rect, date):         QtWidgets.QCalendarWidget.paintCell(self, painter, rect, date)         if date == date.currentDate():             painter.setBrush(QtGui.QColor(0, 200, 200, 50))             painter.drawRect(rect)

Метод printCell принимает три аргумента:

  • painter — часть библиотеки Qt, которая отвечает за отрисовку

  • rect — объект, в котором содержится информация о позиции и размерах ячейки, которая отрисовывается

  • date — дата, которую отражает эта ячейка

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

Теперь в файле design.py подключаем модуль CustomCalendar и заменяем QtWidgets.QCalendarWidget на MyCalendar:

from PyQt5 import QtCore, QtGui, QtWidgets from CustomCalendar import MyCalendar   class Ui_MainWindow(object):     def setupUi(self, MainWindow):         MainWindow.setObjectName("MainWindow")         MainWindow.resize(600, 502)         MainWindow.setStyleSheet("")         self.centralWidget = QtWidgets.QWidget(MainWindow)         self.centralWidget.setObjectName("centralWidget")         self.calendarWidget = MyCalendar(self.centralWidget)         self.calendarWidget.setGeometry(QtCore.QRect(60, 50, 471, 341))         self.calendarWidget.setObjectName("calendarWidget")         MainWindow.setCentralWidget(self.centralWidget)         self.menuBar = QtWidgets.QMenuBar(MainWindow)         self.menuBar.setGeometry(QtCore.QRect(0, 0, 600, 22))         self.menuBar.setObjectName("menuBar")         MainWindow.setMenuBar(self.menuBar)         self.mainToolBar = QtWidgets.QToolBar(MainWindow)         self.mainToolBar.setObjectName("mainToolBar")         MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar)         self.statusBar = QtWidgets.QStatusBar(MainWindow)         self.statusBar.setObjectName("statusBar")         MainWindow.setStatusBar(self.statusBar)          self.retranslateUi(MainWindow)         QtCore.QMetaObject.connectSlotsByName(MainWindow)      def retranslateUi(self, MainWindow):         _translate = QtCore.QCoreApplication.translate         MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))  

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

from PyQt5 import QtWidgets import sys import design   class CalendarApp(QtWidgets.QMainWindow, design.Ui_MainWindow):     def __init__(self):         super().__init__()         self.setupUi(self)   def main():     app = QtWidgets.QApplication(sys.argv)     window = CalendarApp()     window.show()     app.exec_()   if __name__ == '__main__':     main()

Запускаем приложение и видим, что всё получилось:

Чтобы убрать черную рамку, в функции paintRect нужно изменить painter так, чтобы обводка была прозрачной:

def paintCell(self, painter, rect, date):     QtWidgets.QCalendarWidget.paintCell(self, painter, rect, date)     if date == date.currentDate():         painter.setBrush(QtGui.QColor(0, 200, 200, 50))         painter.setPen(QtGui.QColor(0, 0, 0, 0))         painter.drawRect(rect)

Обводим ячейку

Первоначальная задача была сделать для сегодняшней даты зеленую рамку. Кажется, что достаточно нарисовать прямоугольник с зеленой обводкой. Для этого нужно убрать строчку с painter.setBrush и установить нужный цвет в painter.setPen. Но если сделать так, то получается не очень красиво:

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

def paintCell(self, painter, rect, date):     QtWidgets.QCalendarWidget.paintCell(self, painter, rect, date)     if date == date.currentDate():         painter.setPen(QtGui.QPen(QtGui.QColor(0, 200, 200),  2, Qt.SolidLine, Qt.RoundCap))         painter.drawLine(rect.topRight(), rect.topLeft())         painter.drawLine(rect.topRight(), rect.bottomRight())         painter.drawLine(rect.bottomLeft(), rect.bottomRight())         painter.drawLine(rect.topLeft(), rect.bottomLeft())

И финальный результат:

Что ещё можно сделать

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

ссылка на оригинал статьи https://habr.com/ru/post/535486/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *