Python GUI PyQt6

Python GUIであるPyQt6のウィジェットを用いてエンジニアらしい実装を展開しようと思います。因みに現在バージョンはPython 3.13、PyQt6 6.9.1である。

作成するメソッドは、プログラムが落ちないようにtry〜exceptを使いましょう。メインスレッド(main.py等)にコテコテ実装せず、機能ごとに別ソースファイル(.py)にclassを作りメイン等にimportして実装するようにしましょう。このようにすると、メインソースファイルや機能ソースファイルの流用が簡単ですし、機能の切った貼ったも簡単ですよね。しかしならが、下のサンプルソースはコテコテ実装になってます(オブジェクト指向言語初心者や、Python GUIウィジェットをググりながら作ると、こんなソースコードになると思います)。
 クラス化した実装は、当サイト「Python GUI PySide6」(PySide6特有パラメータなど未使用なので import行をPyQt6に変更すれば動作すると思います)に実装しました。また PySide6側ソースと比較すれば、クラス化改修案が見えてくると思います。

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

※補足事項、[Debug2]タブ、[設定(Setting)]タブには部品は実装してません。[設定]タブについて、当サイト「Python GUI PySide6」や「Python GUI Tkinter」に実装してます。

PyQt6サンプルアプリ起動直後の画面

---main.py---
# coding: utf-8
import sys
import os
from PyQt6.QtWidgets import QApplication,QMainWindow,QStatusBar,QWidget,QTabWidget,QFrame,QHBoxLayout,QVBoxLayout,QLabel,QPushButton,QGroupBox,QTextEdit,QLineEdit,QFileDialog,QComboBox,QSpacerItem
# 独自モジュール(パッケージ)
import config_ini

#class MainWindow(QWidget):         #statusBarを付けるためQMainWindowに変更
class MainWindow(QMainWindow):
    def __init__(self, config_ini):
        super().__init__()
        self.setWindowTitle('PyQt6 サンプル')
        self.resize(500, 540)
        tab_widget = QTabWidget(self)
        tab_widget.setFixedSize(500,520)

        self.GUI_STYLE_QSS = """
                            QPushButton {
                                background-color: #efefef;
                            }
                            QPushButton:pressed {
                                background-color: white;
                            }
                            QStatusBar {
                                border-width: 1px;
                                border-style: solid;
                                border-color: lightgray;
                            }
                        """
        self.config_ini = config_ini

        self.init_debug1_tab(tab_widget)
        self.init_debug2_tab(tab_widget)
        self.init_setting_tab(tab_widget)

        status_bar = QStatusBar()
        self.setStatusBar(status_bar)
        status_bar.setStyleSheet(self.GUI_STYLE_QSS)
        status_bar.showMessage("ここに情報を表示します")

    def init_debug1_tab(self, tab_widget):
        tab_win = QWidget(self)
        tab_widget.addTab(tab_win, "Debug1")

        tab_layout = QVBoxLayout()
        tab_win.setLayout(tab_layout)

        group_box = QGroupBox("格納先フォルダ指定")
        tab_layout.addWidget(group_box)
        group_layout = QHBoxLayout()
        group_box.setLayout(group_layout)
        self.folder_button = QPushButton("フォルダ指定")
        #print(self.folder_button.sizeHint())            # 自動調整サイズ表示
        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)
        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.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("アイテム1")      #item=0
        self.combobox.addItem("アイテム2")
        action1_inner_layout.addWidget(self.combobox)
        action1_inner_layout.addSpacing(250)

        self.action2_button = QPushButton("アクション2")
        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.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 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 folder_button_click(self):
        folder = self.config_ini.save_folder
        if folder == "":
            folder = "c:/"
        folder = QFileDialog.getExistingDirectory(self, "格納先フォルダ選択", folder)
        self.line_edit.setText(folder)
        self.config_ini.save_folder = folder

    def clear_button_click(self):
        self.text_edit.clear()

    def init_debug2_tab(self, tab_widget):
        tab_win = QWidget(self)
        tab_widget.addTab(tab_win, "Debug2")

        tab_layout = QVBoxLayout()
        tab_win.setLayout(tab_layout)
        group_box = QGroupBox("Debug 2")
        tab_layout.addWidget(group_box)

    def init_setting_tab(self, tab_widget):
        tab_win = QWidget(self)
        tab_widget.addTab(tab_win, "設定")

        tab_layout = QVBoxLayout()
        tab_win.setLayout(tab_layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    # プログラムオープニング処理
    config_ini = config_ini.ConfigIni()
    config_ini.read_config_ini()
    # GUI(表示&操作)処理
    window = MainWindow(config_ini)
    window.show()
    retval = app.exec()
    # プログラム終了処理
    config_ini.uptate_config_ini()
    sys.exit(retval)
---config_ini.py---
# coding: utf-8
import configparser
import os

CONFIG_INI_FILE = "config.ini"
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.save_folder = ""
        self.latest_ver = ""
        self.latest_date = ""
        self.latest_details = ""
        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)
            setting_section = self.config_ini[SETTING_SECTION]
            self.save_folder = setting_section.get(SAVE_FOLDER)
            latest_section = self.config_ini[LATEST_SECTION]
            self.latest_ver = latest_section.get(RELEASE_VER)
            self.latest_date = latest_section.get(RELEASE_DATE)
            self.latest_details = latest_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(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.00
date = 25/10/20
overview = ここにリリース概要を記載する

近日実装予定内容は、
●まず、タブコントロール部分を別ソースファイルに移行(機能を部品化) ・・・PySide6サンプル公開中
●その後、QThreadを使用して、ボタン押下後の時間が掛かる処理をサブスレッド化(これも別ソースファイル化)
●そして、Tcl/Tk GUIでは困難な機能のサブスレッドにてプログレス表示
以上です。

コメントを残す