この間Qtを初めてちゃんと触ったんですが、その際に学んだTips等をまとめておきます。なお、今回はQMLではなくQtWidgetを対象としています。
外観
テーマの設定
QMLだとMaterialやUniveral等素敵なデザインが用意されているんですが、QtWidgetの場合はおそらく「windowsvista」「Windows」「Fusion」の3つ。 下記のコードで有効なスタイルリストを取得することも可能。
print(QStyleFactory.keys()) # ['windowsvista', 'Windows', 'Fusion'] app = QApplication() app.setStyle("Fusion")
ダークテーマ(標準)
上で挙げたスタイルのうち、「Fusion」にはダークテーマが存在します。 Qtのバージョンが5.15以降6.5未満の場合はコマランドライン引数から設定できたようです。
アプリケーションの起動時にコマンドラインオプションで設定することができます。
> gallery.exe -platform windows:darkmode=1
この場合は、QApplication
の引数にwindows:darkmode=2
等を渡してあげれば強制的にダークモードを適用できるようです。
python - How do I use QT6 Dark Theme with PySide6? - Stack Overflow
問題は6.5以降の場合なんですが、このコマンドライン指定はできなくなったようで、Windows側のライト/ダークモード設定に従うようになったようです。 詳しくは公式ページを確認してください。
ということで、強制的に暗い感じのデザインにするにはスタイルシート等で設定するしか無いようです。Windowsの設定に合わせる方がユーザのためになるだろ!というQt側の公式見解ですが、デフォルトで暗くしたいのです....
ダークテーマ(qdarktheme)
有志がダークテーマかつMaterialデザインなQSSを設定するライブラリを公開しています。前回はこれにお世話になりました。
QApplication
インスタンスを生成後に、メソッドを呼び出せば暗めでいい感じのQSSが適用されます。
app = QApplication() qdarktheme.setup_theme()
また、Primaryカラー等を変更すれば自分好みの色を適用できます。加えて、追加のQSSも設定可能です。
qdarktheme.setup_theme(custom_colors={"primary": "#32a852"}, additional_qss="")
なお、qdarkthemeがどんなQSSを出力するかは下記で確認可能です。
print(qdarktheme.load_stylesheet())
QToolTipが見えづらい
Issueとしても挙がってましたが、qtdarkthemeのQToolTipの視認性が絶望的です。改善したい場合は、addtional_qss
引数でQToolTip
の設定を上書きしましょう。
QSSを別ファイルにまとめる
QSSはQWidgetのメソッドsetStyleSheet
で設定するわけですが、つまるところただのテキストファイルなので、Webページのようにstyle.qss
などとして別途ファイルにまとめることも可能です。
下記はqrcファイル(Resource Collection Files)でqssをロードした場合の読み込み例です。
stylefile = QFile(":/styles/main.qss") if not stylefile.open(QIODevice.ReadOnly | QIODevice.Text): logger.critical("Failed to apply qss styles") qsstext = QTextStream(stylefile).readAll()
QSSセレクタで特定のQWidgetを選ぶ
QSSはCSSとほぼフォーマットを同じくしています。公式リファレンスにも詳しく説明がありますが、「ここのQLabelだけ色変えたいなぁ」といった場合の設定方法をメモっときます。
lb = QLabel("少し大きめな見出し") lb.setObjectName("h2")
QLabel#h2 { font-size: 24px; }
Font family
使用できるフォントファミリーの一覧は下記で確認できます。QFontDatabaseはQGuiApplicationインスタンスを作らないと使えないので注意です。
from PySide6.QtGui import QFontDatabase, QGuiApplication tmp = QGuiApplication() print("\n".join(QFontDatabase.families()))
スレッディング
QThreadの理解につながる記事
コードはC++ですが、QThread概念、特にイベントループやシグナル周りの理解に大変おすすめです。
Qtでスレッドを使う前に知っておこう #C++ - Qiita
上を踏まえたマルチスレッド実装についてはこちらの記事が参考になりました。
[PySide] QThread を使って時間のかかる処理をスレッド化する - へっぽこプログラマーの備忘録
動作が保証されないQPixmapまわり
UIをセカンダリスレッドから変更するのは動作保証の観点から避ける必要がありますが、QPixmap
が一番レイヤーの低い画像クラスだと勘違いしていてハマりました。
こちらの記事にきれいにまとまっていますが、QPixmap
はあくまで画像GUI上で扱うためのクラスなので、ワーカースレッドから扱ってはいけません。代わりにQImageであれば、裏側でロードしていても問題ありません。
それに合わせてQPixmapCache
もメインスレッド以外からは使えません。setCacheLimit
メソッドで無理やりできるかと半ば実験的にやってみましたが、セカンダリスレッドからの利用は完全に無効化されています(C++のソースも確認してみました)。
QAbstractTableModel dataメソッドのロール
テーブルの各セルの中身をrowやcolumnに合わせて返すメソッドですが、色んなロールがあるのでリファレンスページをメモっときます。 Qt名前空間のリファレンスなので若干見つけづらい。
例えばですが、Qt.TextAlignmentRole
の評価タイミングでQt.AlignmentFlag.AlignCenter
を渡してあげれば、特定のrowやcolumnのセルを中央揃えなんてことも可能です。
フレームレスウィンドウのDrag&Drop移動
ウィジェットのmouseイベント周りをオーバーライドすれば意外と簡単に実装できます。
# 左クリックによるウィンドウの移動制御 def mousePressEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.MouseButton.LeftButton: self._press_pos = event.position() self._is_moving = True def mouseMoveEvent(self, event: QMouseEvent) -> None: if self._is_moving: diff = event.position() - self._press_pos self.move(self.pos() + diff.toPoint()) def mouseReleaseEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.MouseButton.LeftButton: self._is_moving = False
PySideで動かしているQtバージョンの確認
print(PySide6.__version__) # 6.5.1.1 print(PySide6.QtCore.qVersion()) # 6.5.1