Pyside 【第2回 | デザイン編】ダークテーマを実現!QSSでライト/ダーク切替機能付きアプリ

\ 迷ったらまずTechAcademyの無料カウンセリング! /

現代のデスクトップアプリではダークテーマの需要が高く、目の疲れ軽減やモダンな印象づけに有効です。

PySide(Qt)の QSS は CSS に似た書き方で、アプリ全体の配色・ボタン・入力欄・フォント等を一括で整えられます。
この記事では GitHub風のダークテーマ をベースに、ライトテーマとの切替機能まで含めた実践的な実装と、QSSでよく使う主要プロパティを丁寧に解説します。

目次

完成コードと概要

ダーク / ライト切替付きのサンプルアプリです。ぜひそのまま実行してみてください。

完成コード

  • dark_qss / light_qss を用意して app.setStyleSheet() で一括適用。
  • 切替は QPushButton(チェックトグル)と toggled シグナルでコントロール。
  • サンプルとして QLabel, QPushButton, QLineEdit, QTextEdit を置き、テーマ差を確認できる。
# theme_toggle_demo.py
import sys
from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QLineEdit, QLabel, QTextEdit
)
from PySide6.QtCore import Qt


class ThemeDemoApp(QWidget):
    def __init__(self, app):
        super().__init__()
        self.app = app
        self.setWindowTitle("ダーク/ライト テーマ切替デモ")
        self.resize(640, 360)

        # UI
        main = QVBoxLayout()
        control_row = QHBoxLayout()

        self.toggle_btn = QPushButton("ライトに切替")
        self.toggle_btn.setCheckable(True)  # 押すと ON (ライト)
        self.toggle_btn.toggled.connect(self.on_toggle)

        # Add some sample widgets
        self.title = QLabel("ダークテーマサンプル")
        self.title.setObjectName("title")
        info = QLabel("以下はテーマによる見た目の違いを示すサンプルウィジェットです。")
        button = QPushButton("サンプルボタン")
        input_line = QLineEdit()
        input_line.setPlaceholderText("テキスト入力欄")
        text = QTextEdit()
        text.setPlainText("QTextEdit のサンプルテキスト。テーマ切替で色や枠が変わります。")

        control_row.addWidget(self.toggle_btn)
        control_row.addStretch()

        main.addLayout(control_row)
        main.addWidget(self.title)
        main.addWidget(info)
        main.addWidget(button)
        main.addWidget(input_line)
        main.addWidget(text)

        self.setLayout(main)

        # 初期はダーク
        self.apply_dark()

    def on_toggle(self, checked: bool):
        # checked == True -> ライトに切替
        if checked:
            self.apply_light()
        else:
            self.apply_dark()

    def apply_dark(self):
        theme = 'dark'
        with open(fr"python-tool\PySide\{theme}.qss", "r", encoding="utf-8") as f:
            self.app.setStyleSheet(f.read())
            print(f.read())
        self.toggle_btn.setText("ライトに切替")
        self.toggle_btn.setChecked(False)
        self.title.setText("ダークテーマサンプル")

    def apply_light(self):
        theme = 'light'
        with open(fr"python-tool\PySide\{theme}.qss", "r", encoding="utf-8") as f:
            self.app.setStyleSheet(f.read())
        self.toggle_btn.setText("ダークに切替")
        self.toggle_btn.setChecked(True)
        self.title.setText("ライトテーマサンプル")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    demo = ThemeDemoApp(app)
    demo.show()
    sys.exit(app.exec())

ダークテーマQSS

/* --- ダークテーマ QSS --- */
QWidget {
    background-color: #0d1117;
    color: #c9d1d9;
    font-family: "Yu Gothic UI", "Segoe UI", sans-serif;
    font-size: 14px;
}

/* Buttons */
QPushButton {
    background-color: #21262d;
    color: #c9d1d9;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 6px 12px;
}
QPushButton:hover { background-color: #30363d; }
QPushButton:pressed { background-color: #161b22; }

/* Inputs */
QLineEdit, QTextEdit {
    background-color: #161b22;
    color: #c9d1d9;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 6px;
}

/* Labels (headings) */
QLabel#title {
    font-size: 18px;
    font-weight: bold;
    color: #adbac7;
}

ライトテーマQSS

/* --- ライトテーマ QSS --- */
QWidget {
    background-color: #ffffff;
    color: #222222;
    font-family: "Yu Gothic UI", "Segoe UI", sans-serif;
    font-size: 14px;
}

/* Buttons */
QPushButton {
    background-color: #f3f4f6;
    color: #222222;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    padding: 6px 12px;
}
QPushButton:hover { background-color: #e5e7eb; }
QPushButton:pressed { background-color: #d1d5db; }

/* Inputs */
QLineEdit, QTextEdit {
    background-color: #ffffff;
    color: #222222;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    padding: 6px;
}

/* Labels (headings) */
QLabel#title {
    font-size: 18px;
    font-weight: bold;
    color: #111827;
}

プロパティ・メソッドの全まとめ

Noプロパティ / メソッド用途実例
1QWidget { background-color }アプリ全体の背景色background-color: #0d1117;
2colorテキストの文字色color: #c9d1d9;
3font-familyフォントの種類font-family: "Yu Gothic UI", "Segoe UI";
4font-size基本文字サイズfont-size: 14px;
5border枠線の色や太さ(ボタン/入力欄)border: 1px solid #30363d;
6border-radius角丸の指定border-radius: 6px;
7padding内側余白(ボタン/入力)padding: 6px 12px;
8QPushButton:hoverボタンにカーソルを乗せた時の色QPushButton:hover { background-color: #30363d; }
9QPushButton:pressedボタンが押されている時の色QPushButton:pressed { background-color: #161b22; }
10QLineEdit, QTextEdit入力欄の背景・文字色・枠QLineEdit { background-color: #161b22; }

詳細解説

以下の見出しは先ほどの表の No + 用途 と一致しています。各項目は 概要 / メソッド / コード例 の順で説明します。例は 2〜3 個ずつ載せ、実務ですぐ使える形にしています。

1 . アプリ全体の背景色(QWidget { background-color }

概要
アプリ全体の背景色を決める最も基本的な設定です。QWidget セレクタで指定すれば、全ウィジェットのデフォルト背景に影響します(ただし個別ウィジェットで上書き可能)。

メソッド

  • app.setStyleSheet(qss) で全体に適用。
  • window.setStyleSheet(...) で特定ウィンドウ配下に限定適用可能。

コード例

  1. アプリ全体(推奨)
# ダークテーマ
app.setStyleSheet("QWidget { background-color: #0d1117; }")
# ライトテーマ
app.setStyleSheet("QWidget { background-color: #ffffff; }")
  1. 特定ウィンドウだけ
window.setStyleSheet("QWidget { background-color: #f8fafc; }")  # そのウィンドウ配下のみライト
  1. 個別ウィジェット上書き
some_widget.setStyleSheet("background-color: #1f2937;")  # QWidget セレクタより優先

2 . テキストの文字色(color

概要
背景とのコントラストを取るために重要。真っ白は疲れやすいので、ダークテーマではやや灰色寄りの色(例: #c9d1d9)がよく使われます。

メソッド

  • QSS 内の color: <hex>
  • 個別に label.setStyleSheet("color: #adbac7;") でも可。

コード例

  1. 全体で設定
# ダークテーマ
QWidget { color: #c9d1d9; }
# ライトテーマ
QWidget { color: #222222; }
  1. ラベルだけ
# ダークテーマ
QLabel#title { color: #adbac7; }
# ライトテーマ
QLabel#title { color: #111827; }
  1. 直接 Python で
label.setStyleSheet("color: #333333;")<br>

3. フォントの種類(font-family

概要
フォントを揃えることで UI の印象が安定します。日本語対応を考えて "Yu Gothic UI"Meiryo を優先指定し、英語系は "Segoe UI" などをフォールバックします。

メソッド

  • QSS:font-family を使う。
  • 代替:app.setFont(QFont(...)) でアプリ全体のフォントを設定する方法もある。

コード例

  1. QSSで一括
QWidget { font-family: "Yu Gothic UI", "Segoe UI", sans-serif; }
  1. アプリ全体で QFont を使う
from PySide6.QtGui import QFont
app.setFont(QFont("Meiryo", 12))
  1. 特定ウィジェットだけ
label.setStyleSheet("font-family: 'Courier New';")

4. 基本文字サイズ(font-size

概要
可読性と情報密度のバランスを取るために基本サイズを決めます。14px前後がデスクトップでは標準的です。高DPI対応にはポイント指定や app.setFont() と併用を検討。

メソッド

  • QSS:font-size: 14px;
  • QFont:QFont("Meiryo", 12)(ポイント指定)。

コード例

  1. QSS で設定
QWidget { font-size: 14px; }
  1. QFont でアプリ全体
app.setFont(QFont("Meiryo", 12))  # point-size を指定<br>
  1. 部分的に大きく
QLabel#title { font-size: 18px; }

5. 枠線(border:ボタン/入力欄)

概要
入力フィールドやボタンの「境界線」を決める設定。ダーク背景では暗めのグレーを使うと落ち着きます。

メソッド

  • QSS:border: 1px solid #30363d; など。
  • 複数ウィジェットをカンマ区切りで指定可能:QLineEdit, QTextEdit { ... }

コード例

  1. ボタンの枠線
QPushButton { border: 1px solid #30363d; }
  1. 入力欄の枠線
# ダークテーマ
QLineEdit { border: 1px solid #30363d; border-radius:6px; }
# ライトテーマ
QLineEdit { border: 1px solid #d1d5db; border-radius:6px; }
  1. 個別 Python 上書き
btn.setStyleSheet("border: 2px dashed #4b5563;")

6. 角丸(border-radius

概要
border-radius で丸めるとモダンな印象になります。数値はアプリのトーンに合わせて 4〜10px を目安に。

メソッド

  • QSS:border-radius: 6px;
  • 注意:角丸を使うとボタンの高さやpaddingとのバランスに注意。

コード例

  1. ボタン角丸
QPushButton { border-radius: 6px; }
  1. 入力欄角丸
QLineEdit { border-radius: 8px; padding:6px; }
  1. より丸く
QPushButton { border-radius: 999px; }  /* pill style */

7. 内側余白(padding

概要
padding はボタンや入力欄の「押しやすさ」「読みやすさ」に影響します。上下左右のバランスを整えて UX を向上させます。

メソッド

  • QSS:padding: 6px 12px;(縦 横)。
  • 固定サイズを厳密にするときは setFixedHeight() などと併用。

コード例

  1. 標準ボタン
QPushButton { padding: 6px 12px; }
  1. 大きめのボタン
QPushButton.big { font-size:16px; padding:10px 18px; }
  1. 入力欄の内側余白
QLineEdit { padding: 8px; }

8. ホバー時(QPushButton:hover

概要
マウスが乗ったときのフィードバックを与えるためのスタイル。色を少し明るくするのが定石です。

メソッド

  • QSSの疑似状態:QPushButton:hover { ... }
  • hover を使うと「ボタンが押せる」ことが視覚的に伝わる。

コード例

  1. 基本 hover
# ダークテーマ
QPushButton:hover { background-color: #30363d; }
# ライトテーマ
QPushButton:hover { background-color: #e5e7eb; }
  1. アウトラインボタンで塗りつぶす
QPushButton#outline:hover { background-color: #0b1220; color: white; }
  1. 影を演出(QSSでは box-shadow 非対応 → QGraphicsEffect を使う)
# QSS では不可。QGraphicsDropShadowEffect を使う実装例が必要

9. 押下時(QPushButton:pressed

概要
クリック時のスタイル。押下感(色を暗くする等)を出して操作の確信を与えます。

メソッド

  • QSS:QPushButton:pressed { background-color: #161b22; }

コード例

  1. 基本
# ダークテーマ
QPushButton:pressed { background-color: #161b22; }
# ライトテーマ
QPushButton:pressed { background-color: #d1d5db; }
  1. 押下で少し縮小感(アニメーション必要)
# QSS にトランジションは無い。QPropertyAnimation で geometry を一時的に縮める処理を行う
  1. 色の変化 + テキスト色保持
QPushButton:pressed { background-color: #111417; color: #c9d1d9; }

10. 入力欄(QLineEdit, QTextEdit

概要
入力欄は視認性とフォーカス時の指標が重要。背景色・枠線・フォーカス時の色を設計します。

メソッド

  • QSS:QLineEdit { background-color: #161b22; border:1px solid #30363d; }
  • フォーカス時スタイル:QLineEdit:focus { border-color: #58a6ff; }(フォーカス色を用意すると良い)

コード例

  1. 基本
# ダークテーマ
QLineEdit, QTextEdit {
  background-color: #161b22;
  color: #c9d1d9;
  border: 1px solid #30363d;
  border-radius: 6px;
  padding: 6px;
}

# ライトテーマ
QLineEdit, QTextEdit {
    background-color: #ffffff;
    color: #222222;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    padding: 6px;
}
  1. フォーカス時にアクセントを出す
QLineEdit:focus { border: 1px solid #58a6ff; }
  1. プレースホルダ色を変えたい(Qt のバージョンに依存)
# Qt 側で placeholderText の色を QPalette で指定する方が確実:
palette = line.palette()
palette.setColor(QPalette.PlaceholderText, QColor("#9aa5b1"))
line.setPalette(palette)

テーマ切替(実装の注意点と拡張例)

概要

  • 切替は単に app.setStyleSheet(dark_qss) / app.setStyleSheet(light_qss) を呼べばOK。
  • 切替タイミングで「一時的にUIがチラつく」ケースがあるため、必要なら QApplication::processEvents() を呼ぶか、切替を非同期で滑らかに行う工夫を検討。

実装上のポイント

  • 状態保存:ユーザーが選択したテーマを次回起動時に復元するなら、QSettings 等に “theme=dark” を保存・読み込みする。
  • 部分的上書き:特定ウィジェットの見た目を常に固定したいなら、個別ウィジェットで setStyleSheet() を当てる(全体の切替でも上書きされないように注意)。
  • フォント / DPI:テーマ切替時もフォントサイズが整合するよう app.setFont() を併用すると安定。

コード例(状態保存の簡易例)

from PySide6.QtCore import QSettings

# 保存
settings = QSettings("YourOrg", "YourApp")
settings.setValue("theme", "dark")  # or "light"

# 読み込み
theme = settings.value("theme", "dark")
if theme == "light":
    app.setStyleSheet(light_qss)
else:
    app.setStyleSheet(dark_qss)

よくある質問 Q&A

app.setStyleSheet()widget.setStyleSheet() の違いは?

app.setStyleSheet() はアプリ全体に適用(推奨)。widget.setStyleSheet() はそのウィジェット配下にだけ適用(部分的な上書き)が可能。

テーマ切替時に一部ウィジェットの色が変わらない(上書きされない)

そのウィジェットに個別 setStyleSheet() がされていると、アプリ全体のQSS に上書きされる場合があります。優先順位を整理して、必要なら個別セレクタ(#objectName)を使う。

システム(OS)ダークモードを検知して自動切替できる?

Qt側で直接の簡単なAPIは限られます。プラットフォーム固有APIや Qt 6 の OS情報機能を使って検知し、app.setStyleSheet() を切り替える実装を行います。

QSS だけで全ての UI を同じ見た目にできる?

多くは可能ですが、Qt内部でネイティブ描画される部分(特定のネイティブウィジェットやプラットフォーム固有の要素)は QSS で完全にコントロールできない場合があります。

ダークテーマで色選びのコツは?

完全な白(#ffffff)ではなく、やや灰色寄りの文字色を使う(例: #c9d1d9)。アクセントカラーは 1〜2 色に限定して統一感を持たせるとモダンに見えます。

まとめ

QSS は PySide で アプリ全体を一括してテーマ化する強力な手段です。

基本は「背景 (QWidget), 文字色 (color), 枠線 (border), 角丸 (border-radius), padding」 を整えるだけでモダンなダークテーマが作れます。

ライト/ダーク切替app.setStyleSheet() を切り替えるだけで実装可能で,実務では状態保存(QSettings)やフォント/DPIの整合も考慮しましょう。

本記事の完成コードをベースに、アクセントカラーの差し替え、フォントの最適化、フォーカス時アニメーション などを追加していくとさらに完成度が上がります。

シェアしてくださると嬉しいです!
  • URLをコピーしました!

コメント

コメントする

目次