Python GUI PySide6

Python GUIであるPySide6のウィジェットを用いてエンジニアらしい実装を展開しようと思います。因みに現在バージョンはPython 3.13、PyQt6 6.9.3である。当サイト「Python GUI PyQt6」のGUIサンプルアプリソースを機能ごとにモジュール化(クラス化)した実装ソースを下部に展開する。

作成するメソッドは、(全て対応できてないが)プログラムが落ちないようにtry〜exceptを使いましょう。メインスレッド(main.py等)にコテコテ実装せず、機能ごとに別ソースファイル(.py)にclassを作りメイン等にimportして実装するようにしましょう。このようにすると、メインソースファイルや機能ソースファイルの流用が簡単ですし、機能の切った貼ったも簡単ですよね。

使用した PySide6 部品は<PyQt6サンプルアプリ>同様、メインウィンドウ(QMainWindow)、ステータスバー(QStatusBar)、タブコントロール(QTabWidget)、グループボックス(QGroupBox)、1行のエディトボックス(QLineEdit)、複数行のエディトボックス(QTextEdit)、プッシュボタン(QPushButton)、コンボボックス(QComboBox)、ディレクトリ選択ダイアログ(QFileDialog.getExistingDirectory)、ボタンウィジェットにスタイル設定(setStyleSheet)、Windows風INIファイルです(configparser.ConfigParser)。

PySide6 サンプルアプリ起動直後の画面と[設定]タブ画面

---main.py---
# coding: utf-8
# PySide6 6.9.3
import sys
from PySide6.QtWidgets import QApplication,QMainWindow,QTabWidget,QWidget,QLabel,QVBoxLayout, QGroupBox,QHBoxLayout,QPushButton,QTextEdit,QLineEdit,QFileDialog,QComboBox,QSpacerItem
# 独自モジュール
import config_ini
import debug1_tab, setting_tab

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PySide6 タブ クラス化")
        self.resize(500, 540)

        self.config_ini = config_ini.ConfigIni()
        self.config_ini.read_config_ini()
        # QTabWidgetを作成
        self.tab_widget = QTabWidget()

        # 各タブのコンテンツクラスを作成し、QTabWidgetに追加
        self.tab_widget.addTab(debug1_tab.Debug1Tab(self.config_ini), "Debug1")
        self.tab_widget.addTab(setting_tab.SettingsTab(self.config_ini), "設定")

        self.setCentralWidget(self.tab_widget)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    retval = app.exec()
    window.config_ini.uptate_config_ini()
    sys.exit(retval)
---debug1_tab.py---
# coding: utf-8
# PySide6 6.9.3
import sys
from PySide6.QtWidgets import QApplication,QMainWindow,QTabWidget,QWidget,QLabel,QVBoxLayout, QGroupBox,QHBoxLayout,QPushButton,QTextEdit,QLineEdit,QFileDialog,QComboBox,QSpacerItem

class Debug1Tab(QWidget):
    def __init__(self, config_ini):
        self.GUI_STYLE_QSS = """
                            QPushButton {
                                background-color: #f4f4f4;
                            }
                            QPushButton:pressed {
                                background-color: white;
                            }
                            QStatusBar {
                                border-width: 1px;
                                border-style: solid;
                                border-color: #efefef;
                            }
                        """
        self.config_ini = config_ini

        super().__init__()
        tab_layout = QVBoxLayout()
        tab_layout.addWidget(QLabel("Debug 1 GUI"))     # [QLabel見本]デザイン的には無しまたは中央表示
        self.setLayout(tab_layout)

        #self.save_folder = ""
        group_box = QGroupBox("格納先フォルダ指定")
        tab_layout.addWidget(group_box)
        group_layout = QHBoxLayout()
        group_box.setLayout(group_layout)
        self.folder_button = QPushButton("フォルダ指定")
        self.folder_button.setStyleSheet(self.GUI_STYLE_QSS)
        self.folder_button.setFixedSize(100, 24)
        self.folder_button.clicked.connect(self.folder_button_click)
        group_layout.addWidget(self.folder_button)
        self.line_edit = QLineEdit()
        self.line_edit.setText(self.config_ini.save_folder)
        #self.line_edit.setText(self.save_folder)
        group_layout.addWidget(self.line_edit)

        group_box = QGroupBox("アクション")
        tab_layout.addWidget(group_box)
        group_layout = QVBoxLayout()
        group_box.setLayout(group_layout)

        action1_inner_layout = QHBoxLayout()
        group_layout.addLayout(action1_inner_layout)
        self.action1_button = QPushButton("アクション1")
        self.action1_button.setStyleSheet(self.GUI_STYLE_QSS)
        self.action1_button.setFixedSize(100, 24)
        self.action1_button.clicked.connect(self.action1_button_click)
        action1_inner_layout.addWidget(self.action1_button)
        self.combobox = QComboBox()
        self.combobox.setFixedSize(100, 24)
        self.combobox.addItem("アイテム0")      #item=0
        self.combobox.addItem("アイテム1")
        action1_inner_layout.addWidget(self.combobox)
        action1_inner_layout.addSpacing(250)

        self.action2_button = QPushButton("アクション2")
        self.action2_button.setStyleSheet(self.GUI_STYLE_QSS)
        self.action2_button.setFixedSize(100, 24)
        self.action2_button.clicked.connect(self.action2_button_click)
        group_layout.addWidget(self.action2_button)
        self.action3_button = QPushButton("アクション3")
        self.action3_button.setStyleSheet(self.GUI_STYLE_QSS)
        self.action3_button.setFixedSize(100, 24)
        self.action3_button.clicked.connect(self.action3_button_click)
        group_layout.addWidget(self.action3_button)

        group_box = QGroupBox("実行情報")
        tab_layout.addWidget(group_box)
        group_layout = QVBoxLayout()
        group_box.setLayout(group_layout)
        self.clear_button = QPushButton("表示クリア")
        self.clear_button.setStyleSheet(self.GUI_STYLE_QSS)
        self.clear_button.setFixedSize(100, 24)
        self.clear_button.clicked.connect(self.clear_button_click)
        group_layout.addWidget(self.clear_button)
        self.text_edit = QTextEdit()
        group_layout.addWidget(self.text_edit)

    def folder_button_click(self):
        folder = self.line_edit.text()
        #folder = self.save_folder
        if folder == "":
            folder = "c:/"
        folder = QFileDialog.getExistingDirectory(self, "格納先フォルダ選択", folder)
        self.line_edit.setText(folder)
        self.config_ini.save_folder = folder

    def action1_button_click(self):
        self.item_index = self.combobox.currentIndex()
        self.text_edit.append("action1_button clicked  item={}".format(self.item_index))

    def action2_button_click(self):
        self.text_edit.append("action2_button clicked")

    def action3_button_click(self):
        self.text_edit.append("action3_button clicked")

    def clear_button_click(self):
        self.text_edit.clear()
---setting_tab.py---
# coding: utf-8
# PySide6 6.9.3
import sys
from PySide6.QtWidgets import QApplication,QMainWindow,QTabWidget,QWidget,QLabel,QVBoxLayout, QGroupBox,QHBoxLayout,QPushButton,QTextEdit,QLineEdit,QFileDialog,QComboBox,QSpacerItem

# 各タブのコンテンツクラス化
class SettingsTab(QWidget):
    def __init__(self, config_ini):
        self.GUI_STYLE_QSS = """
                            QPushButton {
                                background-color: #f4f4f4;
                            }
                            QPushButton:pressed {
                                background-color: white;
                            }
                            QStatusBar {
                                border-width: 1px;
                                border-style: solid;
                                border-color: #efefef;
                            }
                        """
        self.config_ini = config_ini

        super().__init__()
        tab_layout = QVBoxLayout()
        tab_layout.addWidget(QLabel("Settings GUI"))
        self.setLayout(tab_layout)

        group_box = QGroupBox("デフォルト - 格納先フォルダ")
        tab_layout.addWidget(group_box)
        group_layout = QHBoxLayout()
        group_box.setLayout(group_layout)
        self.folder_button = QPushButton("フォルダ指定")
        self.folder_button.setStyleSheet(self.GUI_STYLE_QSS)
        self.folder_button.setFixedSize(100, 24)
        self.folder_button.clicked.connect(self.def_folder_button_click)
        group_layout.addWidget(self.folder_button)
        self.line1_edit = QLineEdit()
        self.line1_edit.setText(self.config_ini.default_folder)
        group_layout.addWidget(self.line1_edit)

        group_box = QGroupBox("格納先フォルダ指定")
        tab_layout.addWidget(group_box)
        group_layout = QHBoxLayout()
        group_box.setLayout(group_layout)
        self.folder_button = QPushButton("フォルダ指定")
        self.folder_button.setStyleSheet(self.GUI_STYLE_QSS)
        self.folder_button.setFixedSize(100, 24)
        self.folder_button.clicked.connect(self.save_folder_button_click)
        group_layout.addWidget(self.folder_button)
        self.line2_edit = QLineEdit()
        self.line2_edit.setText(self.config_ini.save_folder)
        group_layout.addWidget(self.line2_edit)
        group_box = QGroupBox("プログラム情報")
        tab_layout.addWidget(group_box)
        group_layout = QHBoxLayout()
        group_box.setLayout(group_layout)

        self.text_edit = QTextEdit()
        self.text_edit.append("Version : {}".format(self.config_ini.latest_ver))
        self.text_edit.append("Date    : {}".format(self.config_ini.latest_date))
        self.text_edit.append("{}".format(self.config_ini.latest_overview))
        self.text_edit.setReadOnly(True)
        group_layout.addWidget(self.text_edit)

    def def_folder_button_click(self):
        folder = self.line1_edit.text()
        if folder == "":
            folder = "c:/"
        folder = QFileDialog.getExistingDirectory(self, "格納先フォルダ選択", folder)
        self.line1_edit.setText(folder)
        self.config_ini.default_folder = folder

    def save_folder_button_click(self):
        folder = self.line2_edit.text()
        if folder == "":
            folder = "c:/"
        folder = QFileDialog.getExistingDirectory(self, "格納先フォルダ選択", folder)
        self.line2_edit.setText(folder)
        self.config_ini.save_folder = folder
---config_ini.py---
# coding: utf-8
import configparser
import os

CONFIG_INI_FILE = "config.ini"
DEFAULT_SECTION = "DEFAULT"
SETTING_SECTION = "SETTING"
SAVE_FOLDER = "save_folder"
LATEST_SECTION = "LATEST"
RELEASE_VER = "version"
RELEASE_DATE = "date"
RELEASE_OVERVIEW = "overview"

class ConfigIni():
    def __init__(self):
        # .iniファイルが存在しない場合、キーの設定値に文字列をセットしてプログラム実行を続行する
        # 尚、キーの設定値にNoneをセットして(プログラム)処理にてExcetsion発生させ中断することも可能
        self.default_folder = ""
        self.save_folder = ""
        self.latest_ver = ""
        self.latest_date = ""
        self.overview = ""
        try:
            self.config_ini = configparser.ConfigParser()
            if not os.path.exists(CONFIG_INI_FILE):
                raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), CONFIG_INI_FILE)
        except Exception as err:
            print(err)

    def read_config_ini(self):
        retval = False
        try:
            #self.config_ini.read(CONFIG_INI_FILE, encoding='utf-8')
            # Python 3.2以降のencoding指定(推奨)ぽい…AIによると
            with open(CONFIG_INI_FILE, 'r', encoding='utf-8') as fp:
                self.config_ini.read_file(fp)
            section = self.config_ini[DEFAULT_SECTION]
            self.default_folder = section.get(SAVE_FOLDER)
            section = self.config_ini[SETTING_SECTION]
            self.save_folder = section.get(SAVE_FOLDER)
            section = self.config_ini[LATEST_SECTION]
            self.latest_ver = section.get(RELEASE_VER)
            self.latest_date = section.get(RELEASE_DATE)
            self.overview = section.get(RELEASE_OVERVIEW)
            retval = True
        except Exception as err:
            print(err)
        return retval

    def uptate_config_ini(self):
        retval = False
        try:
            self.config_ini.set(DEFAULT_SECTION, SAVE_FOLDER, self.default_folder)
            self.config_ini.set(SETTING_SECTION, SAVE_FOLDER, self.save_folder)
            # Python 3.2以降のencoding指定(推奨)方法
            with open(CONFIG_INI_FILE, 'w', encoding='utf-8') as fp:
                self.config_ini.write(fp)
            retval = True
        except Exception as err:
            print(err)
        return retval
---config.ini---
[DEFAULT]
save_folder = C:/

[SETTING]
save_folder = C:/Python

[LATEST]
version = V1.0.0
date = 25/10/20
overview = ここにリリース概要を記載する

コメントを残す