PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。配布ライセンスは LGPL で公開されています。最新のバージョンは Qt6 に対応した PySide6(記事執筆時点で 6.4.2)です。
PySide6 で利用できる GUI のウィジェット(部品)は機能が豊富に備わっているので使っていて楽しいのですが、極めて大量かつ同じウィジェット(例えばチェックボックスだけのウィジェット)を表示したいときに、機能が豊富が故にインスタンスあたりのサイズが大きいのか、メモリがたくさん消費されて困ります。
そんな時、特定の目的に機能を絞った軽いウィジェットを作ることができれば、と考えました。チェックボックスのように抽象クラスの QAbstractButton [1] を継承、実装しているボタン系ウィジェットであれば、必要な機能だけを実装したウィジェットを作れば、メモリの消費を抑えられるかもしれません。
そう考えて、まず QAbstractButton から作ったウィジェットのサンプルが公開されていないか探しました。なかなか都合の良い例がありませんでしたが、Toggle ウィジェット(クラス)[2] というのを見つけましたので、PySide6 用に少し直して紹介します。なお、ここでは Swicth と、ウィジェット(クラス)の名前を変えました。
下記の OS 環境で動作確認をしました。
![]() |
Fedora Linux 37 | (Server Edition) | x86_64 |
python3.11 | python3-3.11.1-1.fc37.x86_64 | ||
PySide6 | 6.4.2 |
抽象クラス QAbstractButton を直接継承して機能を実装した Switch クラスを下記に示しました。
import sys | |
from PySide6.QtCore import ( | |
QPropertyAnimation, | |
QRect, | |
QSize, | |
Qt, | |
Property, | |
Signal, | |
) | |
from PySide6.QtGui import ( | |
QBrush, | |
QColor, | |
QPainter, | |
) | |
from PySide6.QtWidgets import ( | |
QAbstractButton, | |
QApplication, | |
QVBoxLayout, | |
QWidget, | |
) | |
# Reference: | |
# https://www.programcreek.com/python/?code=decred%2Ftinydecred%2Ftinydecred-master%2Ftinywallet%2Ftinywallet%2Fqutilities.py | |
class Switch(QAbstractButton): | |
"""Implementation of a clean looking toggle switch translated from | |
https://stackoverflow.com/a/38102598/1124661 | |
QAbstractButton::setDisabled to disable | |
""" | |
statusChanged = Signal(bool) | |
def __init__(self): | |
super().__init__() | |
self.onBrush = QBrush(QColor("#569167")) | |
self.slotBrush = QBrush(QColor("#999999")) | |
self.switchBrush = self.slotBrush | |
self.disabledBrush = QBrush(QColor("#666666")) | |
self.on = False | |
self.fullHeight = 18 | |
self.halfHeight = self.xPos = int(self.fullHeight / 2) | |
self.fullWidth = 34 | |
self.setFixedWidth(self.fullWidth) | |
self.slotMargin = 3 | |
self.slotHeight = self.fullHeight - 2 * self.slotMargin | |
self.travel = self.fullWidth - self.fullHeight | |
self.slotRect = QRect( | |
self.slotMargin, | |
self.slotMargin, | |
self.fullWidth - 2 * self.slotMargin, | |
self.slotHeight, | |
) | |
self.animation = QPropertyAnimation(self, b'pqProp', self) | |
self.animation.setDuration(120) | |
self.setCursor(Qt.PointingHandCursor) | |
def paintEvent(self, e): | |
"""QAbstractButton method. Paint the button. | |
""" | |
painter = QPainter(self) | |
painter.setPen(Qt.NoPen) | |
painter.setBrush(self.switchBrush if self.on else self.disabledBrush) | |
painter.setOpacity(0.6) | |
painter.drawRoundedRect( | |
self.slotRect, self.slotHeight / 2, self.slotHeight / 2, | |
) | |
painter.setOpacity(1.0) | |
painter.drawEllipse( | |
QRect(self.xPos, 0, self.fullHeight, self.fullHeight, ) | |
) | |
def mouseReleaseEvent(self, e): | |
"""Switch the button. | |
""" | |
if e.button() == Qt.LeftButton: | |
self.on = not self.on | |
self.switchBrush = self.onBrush if self.on else self.slotBrush | |
self.animation.setStartValue(self.xPos) | |
self.animation.setEndValue(self.travel if self.on else 0) | |
self.animation.start() | |
self.statusChanged.emit(self.on) | |
super().mouseReleaseEvent(e) | |
def sizeHint(self): | |
"""Required to be implemented and return the size of the widget. | |
""" | |
return QSize(self.fullWidth, self.fullHeight) | |
def setOffset(self, o): | |
"""Setter for QPropertyAnimation. | |
""" | |
self.xPos = o | |
self.update() | |
def getOffset(self): | |
"""Getter for QPropertyAnimation. | |
""" | |
return self.xPos | |
pqProp = Property(int, fget=getOffset, fset=setOffset) | |
def set(self, on): | |
"""Set state to on, and trigger repaint. | |
""" | |
self.on = on | |
self.switchBrush = self.onBrush if on else self.slotBrush | |
self.xPos = self.travel if on else 0 | |
self.update() | |
class Example(QWidget): | |
"""Example widget for demonstration | |
""" | |
def __init__(self): | |
super().__init__() | |
self.setWindowTitle('Example for Switch') | |
layout = QVBoxLayout() | |
self.setLayout(layout) | |
switch = Switch() | |
switch.statusChanged.connect(self.switch_changed) | |
layout.addWidget(switch) | |
def switch_changed(self, status: bool): | |
"""for statusChanged signal | |
""" | |
print('Switch is', status) | |
def main(): | |
"""main event loop | |
""" | |
app: QApplication = QApplication(sys.argv) | |
ex = Example() | |
ex.show() | |
sys.exit(app.exec()) | |
if __name__ == "__main__": | |
main() |
Switch は小さなウィジェットです。実行例を示しました。
Switch is True
このサンプルで判ったことは、QAbstractButton を利用するには、ウィジェットを描画して表示する paintEvent メソッドと、表示サイズの推奨値を保持している sizeHint メソッドを、最低限オーバーライドして機能を実装する必要があるということでした。😁
さて、くだんのチェックボックスだけのウィジェットですが、QAbstractButtton を継承して、チェックボックスのチェック/アンチェックのイメージを切り替えて表示するようにしたものを作成したところ、これでメモリ使用量をざっくり3割程度に削減できました。これは 100 × 1000 以上の大きなチェックボックスのマトリックスを、スクロールバー付きで表示するような特殊な用途です。
参考サイト

にほんブログ村
#オープンソース

0 件のコメント:
コメントを投稿