Qt アプリ開発:スタイルシートによるUIテーマの作成と適用

2025-05-27

QWidgetクラスにおけるstyleSheetとは、Qtのスタイルシート機能を利用して、そのウィジェットの外観(背景色、文字色、フォント、ボーダーなど)をカスタマイズするためのプロパティです。CSS(Cascading Style Sheets)と非常によく似た構文を持ち、Qtアプリケーションのルック&フィールを柔軟に制御できます。

主なポイント

  • アプリケーション全体への適用
    QApplicationオブジェクトのsetStyleSheet()メソッドを使用すると、アプリケーション全体のデフォルトスタイルを設定できます。これにより、アプリケーション全体で一貫したルック&フィールを実現できます。

  • 動的な変更
    アプリケーションの実行中にsetStyleSheet()メソッドを呼び出すことで、ウィジェットのスタイルを動的に変更できます。これにより、ユーザーの操作やアプリケーションの状態に応じて外観を変化させることが可能です。

  • 継承と上書き
    スタイルシートは、親ウィジェットから子ウィジェットへと継承されます。ただし、子ウィジェット自身にスタイルシートが設定されている場合、親のスタイルは上書きされます。

  • ウィジェット単位での設定
    styleSheetは個々のQWidgetオブジェクトに対して設定できます。これにより、アプリケーション内の特定のウィジェットだけ異なるスタイルにすることができます。

  • CSSライクな構文
    styleSheetに設定する文字列は、ウェブ開発で用いられるCSSとほぼ同じ形式で記述します。セレクタ(どのウィジェットにスタイルを適用するかを指定)、プロパティ(変更したい外観の属性)、値(プロパティに設定する具体的な値)を組み合わせて記述します。

    /* 例:QPushButtonというクラスの全てのウィジェットの背景色を青にする */
    QPushButton {
        background-color: blue;
        color: white; /* 文字色を白にする */
    }
    
    /* 例:オブジェクト名が"myButton"のQPushButtonのボーダーを設定する */
    QPushButton#myButton {
        border: 2px solid green;
        border-radius: 5px; /* 角を丸くする */
    }
    
    /* 例:特定の状態(マウスオーバー時)のスタイル */
    QPushButton:hover {
        background-color: darkblue;
    }
    

styleSheetの利用例

  • 複雑なカスタムウィジェットの外観を定義する
  • リストビューのアイテムのスタイルをカスタマイズする
  • ラベルのフォントや背景色を設定する
  • ボタンの色や形を変更する

注意点

  • プラットフォーム固有のネイティブなスタイルを完全に上書きする可能性があるため、注意が必要です。
  • Qtのスタイルシートは、CSSのすべての機能をサポートしているわけではありません。
  • 複雑すぎるスタイルシートは、アプリケーションのパフォーマンスに影響を与える可能性があります。


シンタックスエラー (構文エラー)

  • トラブルシューティング
    • スタイルシートの記述を注意深く見直し、スペルミスや記号の抜け漏れがないか確認してください。
    • Qt CreatorなどのIDEを使用している場合、シンタックスハイライトやエラーチェック機能を利用すると便利です。
    • コンソールに出力されているエラーメッセージを確認し、問題のある箇所を特定します。
  • 原因
    CSSの構文ルールに違反している。コロン (:), セミコロン (;), 波括弧 ({}) の閉じ忘れ、スペルミスなどが考えられます。
  • 症状
    スタイルシートが全く適用されない、または部分的にしか適用されない。コンソールにエラーメッセージが出力される場合もあります。

セレクタの間違い

  • トラブルシューティング
    • クラスセレクタ (QWidgetなど)
      大文字・小文字が正確か確認してください。カスタムウィジェットの場合は、そのクラス名を指定します。
    • オブジェクト名セレクタ (#objectName)
      setObjectName()で設定した名前と一致しているか確認してください。#を忘れないようにしましょう。
    • 子孫セレクタ (QWidget QLineEdit)
      親と子の関係が正しいか確認してください。
    • 直接の子セレクタ (QWidget > QLineEdit)
      直接の親子関係にあるか確認してください。
    • 兄弟セレクタ (QLineEdit + QPushButton, QLineEdit ~ QPushButton)
      ウィジェットの階層構造と兄弟関係が正しいか確認してください。
    • 属性セレクタ ([readOnly="true"])
      属性名と値が正しいか確認してください。
    • 疑似クラス (:hover, :checked)
      適用したい状態に対応する疑似クラス名が正しいか確認してください。
  • 原因
    セレクタが対象のウィジェットを正しく指定できていない。クラス名、オブジェクト名、プロパティ、疑似クラスなどの指定ミスが考えられます。
  • 症状
    特定のウィジェットにスタイルが適用されない。

プロパティと値の間違い

  • トラブルシューティング
    • Qtのドキュメントで、使用したいウィジェットやクラスがサポートしているスタイルシートプロパティを確認してください。
    • 値の形式(色、数値、単位など)が正しいか確認してください。例えば、色の指定には #RRGGBB, rgb(r, g, b), namedcolor などの形式があります。数値には単位 (px, pt, % など) が必要な場合があります。
  • 原因
    存在しないプロパティ名を使用している、プロパティに無効な値を設定している。
  • 症状
    スタイルは適用されるが、意図した見た目にならない。

スタイルの競合と優先順位

  • トラブルシューティング
    • スタイルシートの特異性を理解し、意図したスタイルがより高い優先順位を持つようにセレクタを調整します。
    • 不要なスタイルシートの設定がないか確認します。
    • 親ウィジェットと子ウィジェットの両方にスタイルが設定されている場合、どちらのスタイルが適用されているか確認します。
  • 原因
    スタイルシートの特異性(specificity)の理解不足。より具体的なセレクタを持つスタイルが優先されます。また、後から設定されたスタイルが優先されることもあります。
  • 症状
    複数のスタイルが同じウィジェットに適用され、意図しないスタイルが優先される。

継承の問題

  • トラブルシューティング
    • Qtのドキュメントで、各スタイルシートプロパティが継承されるかどうかを確認してください。
    • 子ウィジェットに意図せずスタイルシートが設定されていないか確認してください。
  • 原因
    スタイルシートプロパティによっては継承されないものがあります。また、子ウィジェット自身にスタイルシートが設定されている場合、親のスタイルは上書きされます。
  • 症状
    親ウィジェットに設定したスタイルが子ウィジェットに継承されない、または意図しない継承が起こる。

カスタムウィジェットの問題

  • トラブルシューティング
    • カスタムウィジェットの paintEvent 内で、スタイルシートで設定された背景色やボーダーなどを描画するように実装する必要があります。QStyle クラスを利用して、現在のスタイルに基づいて要素を描画する方法を検討してください。
  • 原因
    カスタムウィジェットのペイント処理 (paintEvent) が、スタイルシートの設定を考慮していない可能性があります。
  • 症状
    カスタムウィジェットにスタイルシートが期待通りに適用されない。

パフォーマンスの問題

  • トラブルシューティング
    • できるだけ共通のスタイルをアプリケーション全体または親ウィジェットに設定し、個々のウィジェットへの設定を減らします。
    • 複雑なグラデーションや画像の使用は控えめにします。
    • スタイルシートの記述を最適化します。
  • 原因
    複雑すぎるスタイルシートや、多数のウィジェットに個別のスタイルを設定している場合など。
  • 症状
    スタイルシートを多用すると、アプリケーションの描画パフォーマンスが低下する。
  • Qt Inspector
    Qt Creatorに付属している Qt Inspector を使用すると、実行中のアプリケーションのウィジェットのスタイル情報を確認できます。どのスタイルが適用されているか、特異性はどうかなどを視覚的に確認できるため、非常に役立ちます。
  • コメントアウト
    スタイルシートの一部をコメントアウトして、どの部分が問題を引き起こしているか特定します。
  • 段階的な適用
    まずは簡単なスタイルから適用し、徐々に複雑なスタイルを追加していくことで、問題箇所を特定しやすくなります。


基本的な例:QPushButtonの背景色と文字色を変更する

import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout

if __name__ == '__main__':
    app = QApplication(sys.argv)

    window = QWidget()
    window.setWindowTitle("スタイルシートの例")
    layout = QVBoxLayout()

    button = QPushButton("青いボタン")
    # スタイルシートを文字列で設定
    button.setStyleSheet("background-color: blue; color: white;")
    layout.addWidget(button)

    window.setLayout(layout)
    window.show()

    sys.exit(app.exec_())

説明

  • 複数のスタイルをセミコロン ; で区切って指定できます。
  • setStyleSheet() メソッドに、CSSの構文に似た文字列 "background-color: blue; color: white;" を渡すことで、ボタンの背景色を青、文字色を白に設定しています。
  • QPushButton のインスタンス button を作成しています。

オブジェクト名を指定してスタイルを適用する例:特定のボタンだけスタイルを変更する

import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout

if __name__ == '__main__':
    app = QApplication(sys.argv)

    window = QWidget()
    window.setWindowTitle("オブジェクト名指定の例")
    layout = QVBoxLayout()

    button1 = QPushButton("デフォルトのボタン")
    layout.addWidget(button1)

    button2 = QPushButton("特別なボタン")
    button2.setObjectName("specialButton") # オブジェクト名を設定
    # オブジェクト名セレクタを使ってスタイルを設定
    button2.setStyleSheet("#specialButton { background-color: green; color: yellow; font-weight: bold; }")
    layout.addWidget(button2)

    window.setLayout(layout)
    window.show()

    sys.exit(app.exec_())

説明

  • setStyleSheet() では、# に続けてオブジェクト名を記述することで、特定のオブジェクトのみにスタイルを適用しています (#specialButton { ... })。
  • button2 に対して setObjectName("specialButton") を呼び出し、オブジェクト名を "specialButton" に設定しています。

クラスと疑似クラスを使った例:マウスオーバーでスタイルを変える

import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout

if __name__ == '__main__':
    app = QApplication(sys.argv)

    window = QWidget()
    window.setWindowTitle("疑似クラスの例")
    layout = QVBoxLayout()

    button = QPushButton("マウスオーバーで変化")
    # クラスセレクタと疑似クラス (:hover) を使用
    button.setStyleSheet("""
        QPushButton {
            background-color: lightgray;
            color: black;
            padding: 10px;
            border: 1px solid gray;
            border-radius: 5px;
        }
        QPushButton:hover {
            background-color: yellow;
            color: blue;
        }
    """)
    layout.addWidget(button)

    window.setLayout(layout)
    window.show()

    sys.exit(app.exec_())

説明

  • :hover という疑似クラスセレクタを使って、マウスカーソルがボタンの上に乗った時のスタイル (background-color: yellow; color: blue;) を定義しています。
  • QPushButton クラス全体に基本的なスタイルを設定しています。

複数のウィジェットに共通のスタイルを適用する例:親ウィジェットにスタイルを設定する

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout

if __name__ == '__main__':
    app = QApplication(sys.argv)

    window = QWidget()
    window.setWindowTitle("親のスタイルを継承する例")
    layout = QVBoxLayout()

    # 親ウィジェットに共通の背景色を設定
    window.setStyleSheet("background-color: lightblue;")

    button = QPushButton("ボタン")
    label = QLabel("ラベル")

    layout.addWidget(button)
    layout.addWidget(label)
    window.setLayout(layout)
    window.show()

    sys.exit(app.exec_())

説明

  • 子ウィジェットである QPushButtonQLabel は、特にスタイルを指定していないため、親の背景色を継承します。ただし、子ウィジェット自身にスタイルシートが設定された場合は、そのスタイルが優先されます。
  • 親ウィジェットである window"background-color: lightblue;" というスタイルシートを設定しています。

外部のスタイルシートファイルを読み込む例 (Pythonでは直接的な機能はありませんが、文字列として読み込んで設定できます)

import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout

def load_stylesheet(filepath):
    """外部のスタイルシートファイルを読み込む関数"""
    try:
        with open(filepath, 'r') as f:
            return f.read()
    except FileNotFoundError:
        print(f"エラー: ファイル '{filepath}' が見つかりません。")
        return ""

if __name__ == '__main__':
    app = QApplication(sys.argv)

    window = QWidget()
    window.setWindowTitle("外部スタイルシートの例")
    layout = QVBoxLayout()

    button = QPushButton("スタイル適用ボタン")
    stylesheet = load_stylesheet("style.css") # style.css というファイルから読み込む
    button.setStyleSheet(stylesheet)
    layout.addWidget(button)

    window.setLayout(layout)
    window.show()

    sys.exit(app.exec_())

style.css ファイルの例

QPushButton {
    background-color: orange;
    color: black;
    border: 2px solid darkorange;
    padding: 8px 15px;
    border-radius: 3px;
}

QPushButton:pressed {
    background-color: #FFA500; /* より濃いオレンジ */
}
  • 読み込んだスタイルシート文字列を button.setStyleSheet() に渡すことで、外部ファイルで定義されたスタイルを適用できます。
  • load_stylesheet() 関数は、指定されたパスのCSSファイルを読み込み、その内容を文字列として返します。


パレット (QPalette) の使用

  • 使用例

    import sys
    from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout
    from PyQt5.QtGui import QPalette, QColor
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
    
        window = QWidget()
        window.setWindowTitle("パレットの例")
        layout = QVBoxLayout()
    
        button = QPushButton("色の変わるボタン")
    
        # パレットを取得
        palette = button.palette()
    
        # ボタンの背景色(Buttonロール)を設定
        palette.setColor(QPalette.Button, QColor(200, 100, 100)) # 赤っぽい色
    
        # ボタンの文字色(ButtonTextロール)を設定
        palette.setColor(QPalette.ButtonText, QColor(255, 255, 255)) # 白
    
        # パレットをボタンに設定
        button.setPalette(palette)
    
        layout.addWidget(button)
        window.setLayout(layout)
        window.show()
    
        sys.exit(app.exec_())
    
    • システムのテーマやスタイルに比較的簡単に統合できます。
    • ウィジェットの役割(ボタン、テキストエディタなど)に基づいて色を設定できます。
    • スタイルシートよりも抽象的なレベルで外観を制御します。

スタイル (QStyle) のサブクラス化と使用

  • 使用例 (概念的なもの)

    from PyQt5.QtWidgets import QApplication, QPushButton
    from PyQt5.QtGui import QStyle, QStyleOptionButton, QPainter, QRect
    
    class MyCustomStyle(QStyle):
        def drawControl(self, element, option, painter, widget=None):
            if element == QStyle.CE_PushButton:
                # カスタムのボタンの描画処理を実装
                painter.save()
                painter.fillRect(option.rect, Qt.darkGreen)
                painter.setPen(Qt.white)
                painter.drawText(option.rect, Qt.AlignCenter, option.text)
                painter.restore()
            else:
                super().drawControl(element, option, painter, widget)
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        app.setStyle(MyCustomStyle()) # カスタムスタイルをアプリケーションに設定
    
        button = QPushButton("カスタムボタン")
        button.show()
    
        sys.exit(app.exec_())
    
  • 特徴

    • 最も強力で柔軟なカスタマイズ方法です。
    • ウィジェットのあらゆる側面(サイズ、形状、描画の詳細など)を制御できます。
    • 複雑な描画処理が必要となるため、高度な知識が必要です。
    • アプリケーション全体のルック&フィールを根本的に変更できます。

ペイントイベント (paintEvent) のオーバーライド

  • 使用例

    import sys
    from PyQt5.QtWidgets import QApplication, QWidget
    from PyQt5.QtGui import QPainter, QColor, QPen
    from PyQt5.QtCore import Qt
    
    class MyPaintWidget(QWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setWindowTitle("ペイントイベントの例")
    
        def paintEvent(self, event):
            painter = QPainter(self)
            painter.setBrush(QColor(150, 150, 255)) # 薄い青
            pen = QPen(Qt.black, 3, Qt.SolidLine)
            painter.setPen(pen)
            painter.drawRect(50, 50, 100, 100)
            painter.drawText(70, 120, "カスタム描画")
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        window = MyPaintWidget()
        window.show()
        sys.exit(app.exec_())
    
  • 特徴

    • 個々のカスタムウィジェットの外観を細かく制御できます。
    • QPainter を使用して、線、図形、テキスト、画像などを自由に描画できます。
    • スタイルシートやパレットの設定を反映させることも可能です。
    • カスタムウィジェットの外観を独自に作り込む場合に非常に有効です。

グラフィックビュー (Graphics View) フレームワーク

  • 使用例 (非常に基本的な概念)

    import sys
    from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsRectItem
    from PyQt5.QtGui import QColor
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        scene = QGraphicsScene()
        rect = QGraphicsRectItem(0, 0, 100, 100)
        rect.setBrush(QColor(255, 0, 0)) # 赤
        scene.addItem(rect)
    
        view = QGraphicsView(scene)
        view.setWindowTitle("グラフィックビューの例")
        view.show()
    
        sys.exit(app.exec_())
    
  • 特徴

    • カスタムな形状やアニメーションを容易に実現できます。
    • 大量のカスタムアイテムを効率的に管理できます。
    • マウスインタラクションや衝突検出などの高度な機能を提供します。
    • ウィジェットの基本的な外観カスタマイズとは少し異なりますが、全く新しいUI要素や視覚表現を作成する際に強力です。

それぞれの方法の使い分け

  • 複雑な2Dグラフィックスやインタラクティブな要素を作成したい場合
    グラフィックビュー (Graphics View) フレームワークを検討します。
  • 特定のカスタムウィジェットの外観を細かく制御したい場合
    ペイントイベント (paintEvent) のオーバーライドが有効です。
  • アプリケーション全体のウィジェットの描画を根本的にカスタマイズしたい場合
    スタイル (QStyle) のサブクラス化が必要になります。
  • プラットフォームのルック&フィールに合わせた調整や、基本的な色の設定を行いたい場合
    パレット (QPalette) が適しています。
  • 簡単なスタイルの変更や、アプリケーション全体で一貫したテーマを適用したい場合
    スタイルシート (setStyleSheet) が手軽で強力です。