PySide6
254 строки · 17.2 Кб
1"""
2Пример создания пользовательского виджета.
3Финальный код виджета, который можно использовать в других проектах.
4"""
5import sys
6
7from PySide6.QtWidgets import QApplication, QVBoxLayout, QDial, QWidget, QSizePolicy
8from PySide6.QtGui import QPaintEvent, QPainter, QBrush, QColor
9from PySide6.QtCore import Qt, QRect, QSize, Signal
10
11"""
12Модуль sys нужен для доступа к аргументам командной строки. Если использование аргументов
13командной строки не предполагается, то импорт можно не выполнять. При этом, при создании
14приложения в класс QApplication([]) в качестве аргумента передается пустой список.
15Импорт из модуля PySide6.QtWidgets класса для управления приложением QApplication и класса слоев
16для виджетов с вертикальной организацией QVBoxLayout, класса виджета вращающегося регулятора QDial,
17класса базового виджета QWidget, класса политики изменения размера QSizePolicy.
18Импорт из модуля PySide6.QtCore класса Qt,содержащего различные идентификаторы, используемые
19в библиотеке Qt, класса примитива прямоугольника QRect, класс сигнала Signal.
20Импорт из модуля PySide6.QtGui класса обработчика событий рисования QPaintEvent, класса виджета
21для рисования QPainter, класса кисти QBrush, класса объекта цветов QColor, класса размеров QSize.
22Другие виджеты можно найти по ссылке https://doc.qt.io/qt-5/widget-classes.html#basic-widget-classes
23"""
24
25
26class _Bar(QWidget):
27"""
28Подкласс силовой шкалы от супер-класса базового виджета
29"""
30clickedValue = Signal(int) # создает экземпляр класс сигнала
31
32def __init__(self, steps: int or list) -> None:
33"""
34Конструктор шкалы измерителя
35"""
36QWidget.__init__(self) # Явный вызов конструктора родительского класса
37self.setSizePolicy(QSizePolicy.MinimumExpanding,
38QSizePolicy.MinimumExpanding
39)
40if isinstance(steps, list): # если в качестве аргумента передан список цветов
41self.n_steps = len(steps) # количество делений равно количеству цветов в списке
42self.steps = steps # сохранение списка цветов в аттрибуте экземпляра
43elif isinstance(steps, int): # если в качестве аргумента передано количество делений
44self.n_steps = steps # сохранение количества делений в атрибуте экземпляра
45self.steps = ['red'] * steps # создание списка цветов
46else: # в остальных случаях
47raise TypeError('steps must be a list or int') # выброс исключения по типу
48self._bar_solid_percent = 0.8 # установка доли высоты шага для отрисовки прямоугольника деления
49self._background_color = QColor('black') # установка цвета фона шкалы
50self._padding = 4 # установка отступа по краям прямоугольника деления в пикселях
51
52def sizeHint(self) -> QSize:
53"""
54Метод, возвращающий минимальные размеры виджета
55:return: QSize - минимальный размер виджетов
56"""
57return QSize(40, 120)
58
59def paintEvent(self, e: QPaintEvent) -> None:
60"""
61Метод пользовательский обработчик события рисования
62:param e: QPaintEvent - событие базового обработчика рисования
63:return: None
64"""
65painter = QPainter(self) # создание экземпляра класса рисовальщика
66brush = QBrush() # создание экземпляра класса кисти
67brush.setColor(self._background_color) # установка цвета кисти
68brush.setStyle(Qt.SolidPattern) # установка стиля заполнения сплошное
69rect = QRect(0, 0, painter.device().width(), painter.device().height())
70# Рисование прямоугольника. Использование метода .device() позволяют рисовать
71# прямоугольник согласно размеру окна виджета. Методы .width() и height() извлекают
72# размеры окна рисовальщика
73painter.fillRect(rect, brush) # заливка прямоугольника цветом кисти
74
75# Отображение текущего состояния
76parent = self.parent() # передача ссылки на регулятор в переменную через специальный
77# метод родительского класса
78vmin, vmax = parent.minimum(), parent.maximum() # установка минимального и максимального значения регулятора
79value = parent.value() # извлечение текущего значения регулятора
80
81# определение размеров поля для рисования делений шкалы
82d_height = painter.device().height() - (self._padding * 2)
83d_width = painter.device().width() - (self._padding * 2)
84
85step_size = d_height / self.n_steps # вычисление размера шага для рисования деления шкалы
86bar_height = step_size * self._bar_solid_percent # вычисление высоты прямоугольника деления шаклы
87
88pc = (value - vmin) / (vmax - vmin) # вычисление доли шкалы, соответствующей значению регулятора
89n_steps_to_draw = int(pc * self.n_steps) # вычисление количества сегментов, которые должны быть
90# отрисованы
91
92for n in range(n_steps_to_draw): # цикл отрисовки делений шаклы
93brush.setColor(QColor(self.steps[n])) # присвоение кисти цвета деления из списка цветов
94ypos = (1 + n) * step_size # вычисление положения ЛВУ прямоугольника деления относительно самого деления
95rect = QRect(
96self._padding, # отрисовка прямоугольника с вычисленными параметрами
97self._padding + d_height - int(ypos), # вычисление высоты ЛВУ прямоугольника деления на шкале
98d_width,
99int(bar_height)
100)
101painter.fillRect(rect, brush) # заливка прямоугольника деления шкалы цветом
102painter.end() # метод завершения работы рисовальщика
103
104def _trigger_refresh(self):
105"""
106Метод ресивер (слот) сигнала обновления виджета
107:return:
108"""
109self.update() # вызов метода обновления виджета родительского класса QWidget
110
111def _calculate_clicked_value(self, e) -> None:
112"""
113Обработчик действий с мышью
114:param e: event из PySide6.QtGui.QMouseEvent содержит события с мыши
115:return: None
116"""
117parent = self.parent()
118vmin, vmax = parent.minimum(), parent.maximum()
119d_height = self.size().height() + (self._padding * 2)
120step_size = d_height / self.n_steps
121click_y = e.y() - self._padding - step_size / 2
122pc = (d_height - click_y) / d_height
123value = int(vmin + pc * (vmax - vmin))
124self.clickedValue.emit(value)
125
126def mouseMoveEvent(self, e) -> None:
127"""
128Обработчик сигнала на перемещение мышью
129"""
130self._calculate_clicked_value(e)
131
132def mousePressEvent(self, e) -> None:
133"""
134Обработчик сигнала на нажатие кнопок мыши
135"""
136self._calculate_clicked_value(e)
137
138
139class PowerBar(QWidget):
140"""
141Подкласс пользовательского виджета от супер-класса базового виджета
142"""
143
144def __init__(self, parent=None, steps: int or list = 5) -> None:
145"""
146Конструктор пользовательского виджета
147"""
148QWidget.__init__(self, parent) # явный вызов конструктора родительского класса
149layout = QVBoxLayout() # создание экземпляра класса слоев для виджетов
150self._bar = _Bar(steps) # создание экземпляра класса силовой шкалы
151layout.addWidget(self._bar) # размещение силовой объекта силовой шкалы в слое
152self._dial = QDial() # создание экземпляра класса виджета вращающегося регулятора
153self._dial.setNotchesVisible(True) # данный метод включает и отключает отображение насечек регулятора
154self._dial.setWrapping(False) # включение и отключение мертвой зоны межу макс и мин внизу регулятора
155self._dial.valueChanged.connect(self._bar._trigger_refresh) # создание сигнала на изменение
156# положения регулятора
157layout.addWidget(self._dial) # размещение виджета регулятора на слое
158self.setLayout(layout) # размещение слоя с виджетами в окне пользовательского виджета
159self._bar.clickedValue.connect(self._dial.setValue) # создание сигнала на клик по шкале
160# и подключение к методу обработчика
161
162def __getattr__(self, item) -> None:
163"""
164Магический метод для перехвата имени атрибутов экземпляра
165:param item: имя аттрибута экземпляра
166:return: None
167"""
168if item in self.__dict__: # если аттрибут существует в словаре аттрибутов экземпляра
169return self[item] # вызов метода согласно имени аттрибута
170try:
171return getattr(self._dial, item) # в противном случае пытается извлечь и вернуть значение
172# аттрибута из экземпляра вращающегося регулятора
173except AttributeError: # если аттрибут не обнаружен, выбрасывается исключение с ошибкой аттрибута
174raise AttributeError(f"'{self.__class__.__name__}' объект не имеет аттрибута '{item}'")
175
176def setColor(self, color: str) -> None:
177"""
178Метода для приема настройки одного цвета для всех делений
179:param color: str - название цвета или его шестнадцатеричный код
180:return: None
181"""
182self._bar.steps = [color] * self._bar.n_steps # создание списка цветов делений
183self._bar.update() # вызов метода обновления виджета из родительского класса
184
185def setColors(self, colors: list) -> None:
186"""
187Метода для приема настройки цветов для делений шкалы
188:param colors: list - список названий цветов или их шестнадцатеричных кодов
189:return: None
190"""
191self._bar.n_steps = len(colors) # определение количества делений шкалы
192self._bar.steps = colors # сохранение списка цветов делений шкалы в аттрибут экземпляра
193self._bar.update() # вызов метода обновления виджета из родительского класса
194
195def setBarPadding(self, i: int) -> None:
196"""
197Метод для приема настройки величины отступа по краям делений
198:param i: int - величина отступа в пикселях
199:return: None
200"""
201self._bar._padding = int(i) # сохранение количества делений шкалы в аттрибут экземпляра
202self._bar.update() # вызов метода обновления виджета из родительского класса
203
204def setBarSolidPercent(self, f: float) -> None:
205"""
206Метод для приема настройки доли высоты прямоугольника деления
207:param f: float - доля высоты прямоугольника деления от высоты шага делений
208:return: None
209"""
210self._bar._bar_solid_percent = float(f) # сохранение доли высоты в аттрибуте экземпляра
211self._bar.update() # вызов метода обновления виджета из родительского класса
212
213def setBackgraundColor(self, color: str) -> None:
214"""
215Метод для приема настройки цвета заднего фона шкалы
216:param color: str - название цвета или его шестнадцатеричный код
217:return: None
218"""
219self._bar._background_color = QColor(color) # создание и сохранение объекта цвета в аттрибуте экземпляра
220self._bar.update() # вызов метода обновления виджета из родительского класса
221
222def setNSteps(self, n_steps: int) -> None:
223"""
224Метода для приема настройки количества делений шкалы
225:param n_steps: int - количество делений шкалы
226:return: None
227"""
228self._bar.n_steps = n_steps # сохранение количества делений в аттрибуте экземпляра
229self._bar.steps = [self._bar.steps[0]] * self._bar.n_steps # создание списка цветов делений
230self._bar.update() # вызов метода обновления виджета из родительского класса
231
232
233def main() -> None:
234"""
235Функция запуска кода верхнего уроня
236:return: None
237"""
238app = QApplication(sys.argv) # создание экземпляра основного цикла главного окна приложения
239volume = PowerBar()
240volume.setColor('green')
241volume.setNSteps(10)
242# volume = PowerBar(steps=["#5e4fa2", "#3288bd", "#66c2a5", "#abdda4", "#e6f598",
243# "#ffffbf", "#fee08b", "#fdae61", "#f46d43", "#d53e4f",
244# "#9e0142"]) # создание экземпляра пользовательского виджета
245# volume = PowerBar(steps=["#a63603", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2",
246# "#feedde"])
247# volume = PowerBar(steps=10)
248volume.show() # вызов метода вывода виджета (по умолчанию виджет спрятан)
249app.exec() # запуска основного цикла пользовательского виджета
250
251
252if __name__ == '__main__': # данное условие нужно для предотвращения запуска кода верхнего уровня при
253# импортировании данного файла как модуля
254main() # вызов функции запуска кода верхнего
255