PySide6

Форк
0
254 строки · 17.2 Кб
1
"""
2
Пример создания пользовательского виджета.
3
Финальный код виджета, который можно использовать в других проектах.
4
"""
5
import sys
6

7
from PySide6.QtWidgets import QApplication, QVBoxLayout, QDial, QWidget, QSizePolicy
8
from PySide6.QtGui import QPaintEvent, QPainter, QBrush, QColor
9
from 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

26
class _Bar(QWidget):
27
    """
28
    Подкласс силовой шкалы от супер-класса базового виджета
29
    """
30
    clickedValue = Signal(int)  # создает экземпляр класс сигнала
31

32
    def __init__(self, steps: int or list) -> None:
33
        """
34
        Конструктор шкалы измерителя
35
        """
36
        QWidget.__init__(self)  # Явный вызов конструктора родительского класса
37
        self.setSizePolicy(QSizePolicy.MinimumExpanding,
38
                           QSizePolicy.MinimumExpanding
39
                           )
40
        if isinstance(steps, list):  # если в качестве аргумента передан список цветов
41
            self.n_steps = len(steps)  # количество делений равно количеству цветов в списке
42
            self.steps = steps  # сохранение списка цветов в аттрибуте экземпляра
43
        elif isinstance(steps, int):  # если в качестве аргумента передано количество делений
44
            self.n_steps = steps  # сохранение количества делений в атрибуте экземпляра
45
            self.steps = ['red'] * steps  # создание списка цветов
46
        else:  # в остальных случаях
47
            raise TypeError('steps must be a list or int')  # выброс исключения по типу
48
        self._bar_solid_percent = 0.8  # установка доли высоты шага для отрисовки прямоугольника деления
49
        self._background_color = QColor('black')  # установка цвета фона шкалы
50
        self._padding = 4  # установка отступа по краям прямоугольника деления в пикселях
51

52
    def sizeHint(self) -> QSize:
53
        """
54
        Метод, возвращающий минимальные размеры виджета
55
        :return: QSize - минимальный размер виджетов
56
        """
57
        return QSize(40, 120)
58

59
    def paintEvent(self, e: QPaintEvent) -> None:
60
        """
61
        Метод пользовательский обработчик события рисования
62
        :param e: QPaintEvent - событие базового обработчика рисования
63
        :return: None
64
        """
65
        painter = QPainter(self)  # создание экземпляра класса рисовальщика
66
        brush = QBrush()  # создание экземпляра класса кисти
67
        brush.setColor(self._background_color)  # установка цвета кисти
68
        brush.setStyle(Qt.SolidPattern)  # установка стиля заполнения сплошное
69
        rect = QRect(0, 0, painter.device().width(), painter.device().height())
70
        # Рисование прямоугольника. Использование метода .device() позволяют рисовать
71
        # прямоугольник согласно размеру окна виджета. Методы .width() и height() извлекают
72
        # размеры окна рисовальщика
73
        painter.fillRect(rect, brush)  # заливка прямоугольника цветом кисти
74

75
        # Отображение текущего состояния
76
        parent = self.parent()  # передача ссылки на регулятор в переменную через специальный
77
        # метод родительского класса
78
        vmin, vmax = parent.minimum(), parent.maximum()  # установка минимального и максимального значения регулятора
79
        value = parent.value()  # извлечение текущего значения регулятора
80

81
        # определение размеров поля для рисования делений шкалы
82
        d_height = painter.device().height() - (self._padding * 2)
83
        d_width = painter.device().width() - (self._padding * 2)
84

85
        step_size = d_height / self.n_steps  # вычисление размера шага для рисования деления шкалы
86
        bar_height = step_size * self._bar_solid_percent  # вычисление высоты прямоугольника деления шаклы
87

88
        pc = (value - vmin) / (vmax - vmin)  # вычисление доли шкалы, соответствующей значению регулятора
89
        n_steps_to_draw = int(pc * self.n_steps)  # вычисление количества сегментов, которые должны быть
90
        # отрисованы
91

92
        for n in range(n_steps_to_draw):  # цикл отрисовки делений шаклы
93
            brush.setColor(QColor(self.steps[n]))  # присвоение кисти цвета деления из списка цветов
94
            ypos = (1 + n) * step_size  # вычисление положения ЛВУ прямоугольника деления относительно самого деления
95
            rect = QRect(
96
                self._padding,  # отрисовка прямоугольника с вычисленными параметрами
97
                self._padding + d_height - int(ypos),  # вычисление высоты ЛВУ прямоугольника деления на шкале
98
                d_width,
99
                int(bar_height)
100
            )
101
            painter.fillRect(rect, brush)  # заливка прямоугольника деления шкалы цветом
102
        painter.end()  # метод завершения работы рисовальщика
103

104
    def _trigger_refresh(self):
105
        """
106
        Метод ресивер (слот) сигнала обновления виджета
107
        :return:
108
        """
109
        self.update()  # вызов метода обновления виджета родительского класса QWidget
110

111
    def _calculate_clicked_value(self, e) -> None:
112
        """
113
        Обработчик действий с мышью
114
        :param e: event из PySide6.QtGui.QMouseEvent содержит события с мыши
115
        :return: None
116
        """
117
        parent = self.parent()
118
        vmin, vmax = parent.minimum(), parent.maximum()
119
        d_height = self.size().height() + (self._padding * 2)
120
        step_size = d_height / self.n_steps
121
        click_y = e.y() - self._padding - step_size / 2
122
        pc = (d_height - click_y) / d_height
123
        value = int(vmin + pc * (vmax - vmin))
124
        self.clickedValue.emit(value)
125

126
    def mouseMoveEvent(self, e) -> None:
127
        """
128
        Обработчик сигнала на перемещение мышью
129
        """
130
        self._calculate_clicked_value(e)
131

132
    def mousePressEvent(self, e) -> None:
133
        """
134
        Обработчик сигнала на нажатие кнопок мыши
135
        """
136
        self._calculate_clicked_value(e)
137

138

139
class PowerBar(QWidget):
140
    """
141
    Подкласс пользовательского виджета от супер-класса базового виджета
142
    """
143

144
    def __init__(self, parent=None, steps: int or list = 5) -> None:
145
        """
146
        Конструктор пользовательского виджета
147
        """
148
        QWidget.__init__(self, parent)  # явный вызов конструктора родительского класса
149
        layout = QVBoxLayout()  # создание экземпляра класса слоев для виджетов
150
        self._bar = _Bar(steps)  # создание экземпляра класса силовой шкалы
151
        layout.addWidget(self._bar)  # размещение силовой объекта силовой шкалы в слое
152
        self._dial = QDial()  # создание экземпляра класса виджета вращающегося регулятора
153
        self._dial.setNotchesVisible(True)  # данный метод включает и отключает отображение насечек регулятора
154
        self._dial.setWrapping(False)  # включение и отключение мертвой зоны межу макс и мин внизу регулятора
155
        self._dial.valueChanged.connect(self._bar._trigger_refresh)  # создание сигнала на изменение
156
        # положения регулятора
157
        layout.addWidget(self._dial)  # размещение виджета регулятора на слое
158
        self.setLayout(layout)  # размещение слоя с виджетами в окне пользовательского виджета
159
        self._bar.clickedValue.connect(self._dial.setValue)  # создание сигнала на клик по шкале
160
        # и подключение к методу обработчика
161

162
    def __getattr__(self, item) -> None:
163
        """
164
        Магический метод для перехвата имени атрибутов экземпляра
165
        :param item: имя аттрибута экземпляра
166
        :return: None
167
        """
168
        if item in self.__dict__:  # если аттрибут существует в словаре аттрибутов экземпляра
169
            return self[item]  # вызов метода согласно имени аттрибута
170
        try:
171
            return getattr(self._dial, item)  # в противном случае пытается извлечь и вернуть значение
172
            # аттрибута из экземпляра вращающегося регулятора
173
        except AttributeError:  # если аттрибут не обнаружен, выбрасывается исключение с ошибкой аттрибута
174
            raise AttributeError(f"'{self.__class__.__name__}' объект не имеет аттрибута '{item}'")
175

176
    def setColor(self, color: str) -> None:
177
        """
178
        Метода для приема настройки одного цвета для всех делений
179
        :param color: str - название цвета или его шестнадцатеричный код
180
        :return: None
181
        """
182
        self._bar.steps = [color] * self._bar.n_steps  # создание списка цветов делений
183
        self._bar.update()  # вызов метода обновления виджета из родительского класса
184

185
    def setColors(self, colors: list) -> None:
186
        """
187
        Метода для приема настройки цветов для делений шкалы
188
        :param colors: list - список названий цветов или их шестнадцатеричных кодов
189
        :return: None
190
        """
191
        self._bar.n_steps = len(colors)  # определение количества делений шкалы
192
        self._bar.steps = colors  # сохранение списка цветов делений шкалы в аттрибут экземпляра
193
        self._bar.update()  # вызов метода обновления виджета из родительского класса
194

195
    def setBarPadding(self, i: int) -> None:
196
        """
197
        Метод для приема настройки величины отступа по краям делений
198
        :param i: int - величина отступа в пикселях
199
        :return: None
200
        """
201
        self._bar._padding = int(i)  # сохранение количества делений шкалы в аттрибут экземпляра
202
        self._bar.update()  # вызов метода обновления виджета из родительского класса
203

204
    def setBarSolidPercent(self, f: float) -> None:
205
        """
206
        Метод для приема настройки доли высоты прямоугольника деления
207
        :param f: float - доля высоты прямоугольника деления от высоты шага делений
208
        :return: None
209
        """
210
        self._bar._bar_solid_percent = float(f)  # сохранение доли высоты в аттрибуте экземпляра
211
        self._bar.update()  # вызов метода обновления виджета из родительского класса
212

213
    def setBackgraundColor(self, color: str) -> None:
214
        """
215
        Метод для приема настройки цвета заднего фона шкалы
216
        :param color: str - название цвета или его шестнадцатеричный код
217
        :return: None
218
        """
219
        self._bar._background_color = QColor(color)  # создание и сохранение объекта цвета в аттрибуте экземпляра
220
        self._bar.update()  # вызов метода обновления виджета из родительского класса
221

222
    def setNSteps(self, n_steps: int) -> None:
223
        """
224
        Метода для приема настройки количества делений шкалы
225
        :param n_steps: int - количество делений шкалы
226
        :return: None
227
        """
228
        self._bar.n_steps = n_steps  # сохранение количества делений в аттрибуте экземпляра
229
        self._bar.steps = [self._bar.steps[0]] * self._bar.n_steps  # создание списка цветов делений
230
        self._bar.update()  # вызов метода обновления виджета из родительского класса
231

232

233
def main() -> None:
234
    """
235
    Функция запуска кода верхнего уроня
236
    :return: None
237
    """
238
    app = QApplication(sys.argv)  # создание экземпляра основного цикла главного окна приложения
239
    volume = PowerBar()
240
    volume.setColor('green')
241
    volume.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)
248
    volume.show()  # вызов метода вывода виджета (по умолчанию виджет спрятан)
249
    app.exec()  # запуска основного цикла пользовательского виджета
250

251

252
if __name__ == '__main__':  # данное условие нужно для предотвращения запуска кода верхнего уровня при
253
    # импортировании данного файла как модуля
254
    main()  # вызов функции запуска кода верхнего
255

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

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.