「独学でイッキに学べるPython入門」 その13 おまけ5
# 「独学でイッキに学べるPython入門」 その13 おまけ5 ヘルプに使い方を追加 2022/08/30## 今回の変更点は、# 1 「使い方」を「メニューバー」の「ヘルプ」メニューに追加した。# 2 リスト内の説明のための番号をつけ直した。# 3 冗長な説明を削除した。# 4 実行後にリサイズを。実施したフォルダ内のファイルを表示するようにした。##『日経ソフトウエア』2022年05月号(p.006~p.032)## 目次## Part1 開発準備編(p.006~p.009)・・・省略# Part2 データ構造編(p.009~p.015)・・・省略# Part3 処理の仕組み編(p.015~p.009)・・・省略# Part4 開発実践編(p.026~p.009)# ■画像一括リサイズアプリを作ろう(p.026)# ①1つの画像のみリサイズ(p.026~p.029)# [STEP1]1枚の画像のみを無条件にリサイズ(p.027~p.029)→「その3」# [STEP2]1枚の画像のみ、ファイル名に文字列「cat」を含むならリサイズ(p.029~p.031)# [STEP3]すべての画像で、ファイル名に文字列「cat」を含むならリサイズ(p.031~p.032) #〇おまけ:記事の機能は出来上がったのですが、次の項目に挑戦してみたいと思います。# ① GUI化してとりあえず動くようにする。# ② リサイズしたいファイルが入っているフォルダを選択できるようにする。# ③ リサイズしたいファイルを選択できるようにする。# ④ 各種のエラー処理を付け加える。# ⑤ 完成したら、EXE化する。# ① GUI化してとりあえず動くようにする。# ①ー2 「出題」ボタンを「確認」ボタンに変更し、コンボボックスで選択した「画像の幅」、「高さ」# を表示する。# ①ー3 元の仕様では、「cat」をフィル名に含むファイルのみをリサイズすることになっているが、# フォルダ内のファイルすべてをリサイズするように仕様を変更する。# ② リサイズしたいファイルが入っているフォルダを選択できるようにする。# Qtの「QFileDialog」の「getExistingDirectory」を使うことで実現できた。# ただ、このままでは、UI(ユーザーインターフェイス)があまり良くないので、# 次回は、UIの改良に取り組みたいと思います。# ②ー2 「ヘルプ」に「使い方」を追加する。# リスト12●「os.listdir」関数で、指定したフォルダー内のすべてのファイル名を取得し、# そのフォルダー内のすべてのファイルをリサイズするプログラムをGUI化する。# (「ヘルプ」に「使い方」を追加した。)# GUI化に当たっては、次の記事を参考にした。#『日経ソフトウエア』2021年11月号(p.52~p.63)# 「特集1 PythonでGUIアプリ開発」の基礎 PyQt/PySide編」## ステップ09 「掛け筆算練習プログラム」# リスト10●掛け筆算練習プログラム(KakeHissanPyQt.py)################################################################################################### ここから今回のプログラムリスト############################ リスト13 画像ファイル一括リサイズプログラム「ResizePicturePyQt.py」##################################################################################################### (1)GUI化のためのモジュールのインポートimport random, sys, webbrowserfrom PyQt6.QtCore import *from PyQt6.QtGui import *from PyQt6.QtWidgets import *# (2)本体用のモジュールのインポートimport osfrom PIL import Image# (3)本体用のクラスの設定# 「QMainWindow」クラスを継承した「ResizePicturePyQt」クラスを記述。# 「MainWindow」の名前を「KakeHissanPyQt」から「ResizePicturePyQt」に変更。class ResizePicturePyQt(QMainWindow): # 「ResizePicturePyQt」クラスのコンストラクタ(インスタンス生成時に # 呼び出されるメソッド)である「__init__」を記述。 def __init__(self): # (4)「ResizePicturePyQt」クラスの親クラスである「QMainWindow」クラスのコンストラクタを実行するコード。 # この記述がないと必要な初期処理が実行されない。 super().__init__() # (5)ディレクトリ表示のために挿入。必要がなくなれば、削除すること。 self.textEdit = QTextEdit() # # (6)タイトルバー文字列の設定。 # 「ResizePicturePyQt」クラスのインスタンスは、(12)で変数「w」に代入され、「w」という名前で # 参照できるが、その名前はクラスの外部のみからしか利用できない。クラス内部から自分 # 自身のインスタンスを呼び出すには、コンストラクタで指定した「self」という名前を使う。 # 「setWindowTitle」メソッドに与えた「self.__class__.__name__」は、このクラスの名前で、 # 中身は「ResizePicturePyQt」。 self.setWindowTitle(self.__class__.__name__) # (7) 同様に「self」を使って、「ラベルウィジェット」の位置と大きさを設定する。 # 「setGeometry」メソッドは、4つの整数を入力引数として受け取る。 # X 座標 # Y 座標 # フレームの幅 # フレームの高さ xOfFrame = 100 yOfFrame = 100 widthOfFrame =100 heightOfFrame =400 self.setGeometry(xOfFrame, yOfFrame, widthOfFrame, heightOfFrame) # (8) 「QMainWindow」クラスを継承した「pyqt07」クラスの「menuBar」メソッドを呼び出して、 # メニューバーのインスタンスを作り、それを「menu」という名前で参照できるようにする。 menu = self.menuBar() ### ここから「メニューバー」の「ファイル」メニューの記述 #### # (9) 「メニューバー」に「ファイル」メニューを追加し、それを「menuFile」という名前で参照できるようにする。 # 「&(アンパサンド)」の次の文字は「アクセスキー」になる。「Altキー」を押しながら # 「F」キーを押すと、「ファイル」メニューを開けるようになる。 menuFile = menu.addMenu('ファイル(&F)') # (9)-1ディレクトリ表示のため、メニューバーの「ファイル」に「開く」メニューを追加する。 # メニュー項目に相当する「アクション」を作り、「menuFileOpen」という名前で参照できるようにする。 # 「QAction」クラスのコンストラクタに渡す最初の引数はアイコンの指定で、ここではQtが持っている # 「SP_DialogOpenButton」を指定している。 # 続く引数は、メニュー項目の文字列( '開く(&O)')と親オブジェクト(menuFile)。 menuFileOpen = QAction( QIcon(self.style().standardIcon( QStyle.StandardPixmap. SP_DialogOpenButton)), '開く(&O)', menuFile) # (9)-2「開く」メニューが選ばれた場合の処理として、「self.showDialog」を指定。 # 「self.showDialog」は、フォルダを指定して、ファイルを表示する関数。 menuFileOpen.triggered.connect(self.selectDialog) # (9)-3 「menuFile」に「menuFileOpen」アクションを追加する。 menuFile.addAction(menuFileOpen) # (9)-4 メニュー項目に相当する「アクション」を作り、「menuFileExit」という名前で参照できるようにする。 # 「QAction」クラスのコンストラクタに渡す最初の引数はアイコンの指定で、ここではQtが持っている # 「SP_TitleBarCloseButton」を指定している。 # 続く引数は、メニュー項目の文字列( '終了(&X)')と親オブジェクト(menuFile)。 menuFileExit = QAction( QIcon(self.style().standardIcon( QStyle.StandardPixmap. SP_TitleBarCloseButton)), '終了(&X)', menuFile) # (9)-5 「終了」メニューが選ばれた場合の処理として、「quit」を指定。 # 「quit」は、「QApplication」静的メソッドで、呼び出すとプログラムが終了する。 menuFileExit.triggered.connect(quit) # (9)-6 「menuFile」に「menuFileExit」アクションを追加する。 menuFile.addAction(menuFileExit) ### ここから「メニューバー」の「ヘルプ」メニューの記述 #### # (10) 「メニューバー」に「ヘルプ」メニューを生成して、「menuHelp」という名前で参照できるようにする。 menuHelp = menu.addMenu('ヘルプ(&H)') # (10)-1 「使い方(Usage)」を表示するコード。 # そのイベントハンドラは、「menuHelpUsage」。 menuHelpUsage = QAction( QIcon(self.style().standardIcon( QStyle.StandardPixmap. SP_MessageBoxInformation)), '使い方(&U)', menuHelp) menuHelpUsage.triggered.connect( self.menuHelpUsageClicked) menuHelp.addAction(menuHelpUsage) # (10)-2 「menuHelpWeb」メニューを生成して、「menuHelpWeb」という名前で参照できるようにする。 # このメニュー項目が選ばれた場合、(11)の「menuHelpWebClicked」メソッドを呼び出す。 menuHelpWeb = QAction( QIcon(self.style().standardIcon( QStyle.StandardPixmap. SP_DialogHelpButton)), 'Webサイトを開く(&W)', menuHelp) menuHelpWeb.triggered.connect( self.menuHelpWebClicked) menuHelp.addAction(menuHelpWeb) # (10)-3 メニューの区切り線(セパレータ)を追加するコード。 menuHelp.addSeparator() # (10)-4 バージョン情報を追加するコード。 # そのイベントハンドラは、「menuHelpVersion」。 menuHelpVersion = QAction( QIcon(self.style().standardIcon( QStyle.StandardPixmap. SP_MessageBoxInformation)), 'バージョン情報(&V)', menuHelp) menuHelpVersion.triggered.connect( self.menuHelpVersionClicked) menuHelp.addAction(menuHelpVersion) ### ### ここから「セントラルウィジェット」の設定 ### # (11) 「QWidget」クラスのインスタンス「center」を作って、グリッドレイアウトの設定を # 行い、セントラルウィジェットに配置する。 # グリッドの1行目に、「label1」、「cb1」、「label2」、「cb2」、「buttonConfirmation」 # (「確認」ボタン)、「buttonExec」(「実行」ボタン)を並べる。 # 「出題」ボタンを「確認」ボタンに変更。 center = QWidget(self) center.layout = QGridLayout(center) center.setLayout(center.layout) self.setCentralWidget(center) # (11)-1 ラベル「label1」を生成。 # '1行目桁数'を'画像の幅'に変更。 label1 = QLabel('画像の幅', center) # (11)-1-2 水平方向は左寄せ、垂直方向は中央ぞろえに。 label1.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) # (11)-1-3「label1」をグリッドの1行、1列に配置。 center.layout.addWidget(label1, 0, 0) # (11)-2 コンボボックス「cb1」を生成。 self.cb1 = QComboBox(center) # (11)-2-1 選択肢を指定。 self.cb1.addItems(['150', '300', '1500', '3000']) # (11)-2-2 「cb1」をグリッドの1行、2列に配置。 center.layout.addWidget(self.cb1, 0, 1) # (11)-3 ラベル「label2」を生成。 # '2行目桁数'を'画像の高さ'に変更。 label2 = QLabel('画像の高さ', center) label2.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) center.layout.addWidget(label2, 0, 2) # (11)-4 コンボボックス「cb2」を生成。 self.cb2 = QComboBox(center) self.cb2.addItems(['150', '300', '1500', '3000']) center.layout.addWidget(self.cb2, 0, 3) # (12) 「確認」ボタンを生成。 buttonConfirmation = QPushButton('確認', center) # (12)-1 イベントハンドラとして、(21)の「buttonConfirmationClicked」を指定する。 buttonConfirmation.clicked.connect(self.buttonConfirmationClicked) # (12)-2 「確認」ボタンをグリッドの1行、5列に配置。 center.layout.addWidget(buttonConfirmation, 0, 4) # (13) 「実行」ボタンを生成。 self.buttonExec = QPushButton('実行', center) self.buttonExec.clicked.connect(self.buttonExecClicked) # (14) 起動時に「実行」ボタンが無効になるように設定。 # 問題が生成されていない状態で「実行」ボタンを押しても反応しないようにしている。 self.buttonExec.setEnabled(False) center.layout.addWidget(self.buttonExec, 0, 5) # (15) テキストボックス「text」を生成。 self.text = QTextEdit(center) #(15)-1 「text」のフォントの種類「明朝体」と大きさ「16」を設定。 self.text.setFont(QFont('MS 明朝', 16)) # (15)-2 書き込み禁止にするため、「ReadOnly」に設定。 self.text.setReadOnly(True) # (15)-3 「text」をウインドウの2行目(2番目の引数「1」)、1列目(3番目の引数「0」)に配置 # 引数の4番目は表示に何行分使うか、5番目は表示に何列分使うかの指定。 center.layout.addWidget(self.text, 1, 0, 1, 6) # (16)「show」メソッドを呼び出して、表示する。 self.show() # (17) 「menuHelpUsage」メニュー項目が選ばれた場合のコード。 # このプログラムの「使い方(Usage)」のダイアログを出す。 # def menuHelpUsageClicked(self): s = self.__class__.__name__ s += '\n このアプリの使い方\n' s += '①フォルダの選択\n' s += ' 「ファイル」メニューの「開く」をクリックして、\n' s += ' リサイズしたいファイル(JPEG画像)の入っている\n' s += ' フォルダを指定する。\n' s += '②画像サイズの上限を設定\n' s += '③確認(フォルダ、画像サイズの上限)\n' s += '④「実行」ボタンを押す。\n' QMessageBox.information( self, self.__class__.__name__, s) # (18) 「menuHelpWeb」メニュー項目が選ばれた場合のコード。 # 「webbrowser.open」関数を使うため、(1)で「webbrowser」モジュールをインポートしている。 # def menuHelpWebClicked(self): webbrowser.open( 'https://info.nikkeibp.co.jp/media/NSW/') # (19) 「menuHelpVersion」メニュー項目が選ばれた場合のコード。 # このプログラムのバージョン、作者名等のダイアログを出す。 # 「sys.version」、を使うために(1)で「sys」モジュールをインポートしている。 # バージョンを「0.02」とし、今回の記事の作者である「立山英利」氏、筆者の名前を追加した。 def menuHelpVersionClicked(self): s = self.__class__.__name__ s += '\n Version 0.02(2022/07/12)\n' s += ' Version 0.01(2021/08/25)\n' s += '@2021 Hideo Harada\n' s += '@2022 Hidetoshi Tateyama\n' s += '@2022 Hiroshi Kato\n' s += 'with Python ' + sys.version QMessageBox.information( self, self.__class__.__name__, s) # (20) 「確認」ボタンのイベントハンドラ。 def buttonConfirmationClicked(self): # (20)-1 コンボボックスで選択された項目を取得し、文字列を整数に変換して、 # 変数「widthOfPicture」、「heightOfPicture」に格納する。 # 「widthOfPicture」、「heightOfPicture」は、(21)の「buttonExecClicked」メソッドで参照するので、 # 「self.」をつけて、クラスのメンバー変数にしている。 # 変数「width1」を「widthOfPicture」に、「width2」を「heightOfPicture」に変更。 self.widthOfPicture = int(str(self.cb1.currentText())) self.heightOfPicture = int(str(self.cb2.currentText())) # (20)-2 変数「Confirmation1」に、コンボボックス「cb1」で選んだ値を、 # 「Confirmation2」にコンボボックス「cb2」で選んだ値を格納する。 # self.Confirmation1 = self.widthOfPicture self.Confirmation2 = self.heightOfPicture # (20)-3 「確認」用の文字列を作って、「Confirmation」に格納する。 self.Confirmation = 'フォルダ名 = {}\n'.format(self.dirName) + \ '画像の幅 = {}\n'.format(self.Confirmation1) + \ '画像の高さ = {}\n'.format(self.Confirmation2) print('self.Confirmation = ', self.Confirmation) # (20)-4 結果を「text」に表示する。 self.text.setText(self.Confirmation) # (20)-5 「実行」ボタンを有効(True)にする。 self.buttonExec.setEnabled(True) # (21) リサイズ「実行」ボタンが押された時の処理。 def buttonExecClicked(self): # (21)-1 「os」モジュールの「listdir」関数で「dirName」フォルダ内のファイル名を入手し、 # 「fnames」に格納する。 fnames = os.listdir(self.dirName) # (21)-2 dirName」フォルダ内の画像ファイル「fname」を、「img」モジュールの「thumbnail」関数で、 # 幅(「widthOfPicture」)、高さ(「heightOfPicture」) for fname in fnames: print('fname = ', fname) fpath = os.path.join(self.dirName + '/' + fname) print('fpath = ', fpath) img = Image.open(fpath) print('self.widthOfPicture = ', self.widthOfPicture) print('self.heithtOfPicture = ', self.heightOfPicture) img.thumbnail((self.widthOfPicture, self.heightOfPicture)) img.save(fpath) #(21)-3 「dirName」フォルダ内のファイルを表示する。 fileName = QFileDialog.getOpenFileName(self, 'Open file', self.dirName) # (22)フォルダの選択 # 「QFileDialog」の「getExistingDirectory」を使って、リサイズの対象となる画像ファイルのあるフォルダ # を選択するダイアログウインドウを開き、選択したフォルダ名を「self.dirName」に格納する。 # 参考にしたURL:https://qiita.com/Nobu12/items/acd3caa625be8eebc09c def selectDialog(self): # (22)-1 第二引数はダイアログのタイトル、第三引数は表示するパス名。 # ここでは、引数を省略し、デフォルトの設定を使用する。 self.dirName = QFileDialog.getExistingDirectory() print('self.dirName = ', self.dirName)# (23)プログラムを起動したときに最初に実行される。app = QApplication([])# (24)「ResizePicturePyQt」クラスを生成すると、「def __init__(self):」が実行される。w = ResizePicturePyQt()# (25)これを実行することで、GUIの画面がユーザーに表示される。app.exec()