「独学でイッキに学べるPython入門」 その12 おまけ4
# 「独学でイッキに学べるPython入門」 その12 おまけ4 ディレクトリ表示を追加 2022/08/21##『日経ソフトウエア』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の改良に取り組みたいと思います。# リスト11●「os.listdir」関数で、指定したフォルダー内のすべてのファイル名を取得し、# そのフォルダー内のすべてのファイルをリサイズするプログラムをGUI化する。## GUI化に当たっては、次の記事を参考にした。#『日経ソフトウエア』2021年11月号(p.52~p.63)# 「特集1 PythonでGUIアプリ開発」の基礎 PyQt/PySide編」## ステップ09 「掛け筆算練習プログラム」# リスト10●掛け筆算練習プログラム(KakeHissanPyQt.py)# GUI化のためのモジュールのインポートimport random, sys, webbrowserfrom PyQt6.QtCore import *from PyQt6.QtGui import *from PyQt6.QtWidgets import *# 本体用のモジュールのインポートimport osfrom PIL import Image# 本体用のクラスの設定# 「QMainWindow」クラスを継承した「ResizePicturePyQt」クラスを記述。# 「MainWindow」の名前を「KakeHissanPyQt」から「ResizePicturePyQt」に変更。class ResizePicturePyQt(QMainWindow): # 「ResizePicturePyQt」クラスのコンストラクタ(インスタンス生成時に # 呼び出されるメソッド)である「__init__」を記述。 def __init__(self): # 「ResizePicturePyQt」クラスの親クラスである「QMainWindow」クラスのコンストラクタを実行するコード。 # この記述がないと必要な初期処理が実行されない。 super().__init__() # ディレクトリ表示のために挿入。必要がなくなれば、削除すること。 self.textEdit = QTextEdit() # # タイトルバー文字列の設定。 # 「ResizePicturePyQt」クラスのインスタンスは、(12)で変数「w」に代入され、「w」という名前で # 参照できるが、その名前はクラスの外部のみからしか利用できない。クラス内部から自分 # 自身のインスタンスを呼び出すには、コンストラクタで指定した「self」という名前を使う。 # 「setWindowTitle」メソッドに与えた「self.__class__.__name__」は、このクラスの名前で、 # 中身は「ResizePicturePyQt」。 self.setWindowTitle(self.__class__.__name__) # (2) 同様に「self」を使って、ラベルウィジェットの位置と大きさを設定する。 # setGeometry() メソッドは、4つの整数を入力引数として受け取る。 # X 座標 # Y 座標 # フレームの幅 # フレームの高さ xOfFrame = 100 yOfFrame = 100 widthOfFrame =100 heightOfFrame =400 self.setGeometry(xOfFrame, yOfFrame, widthOfFrame, heightOfFrame) # リスト9(2) 「QMainWindow」クラスを継承した「pyqt07」クラスの「menuBar」メソッドを呼び出して、 # メニューバーのインスタンスを作り、それを「menu」という名前で参照できるようにする。 menu = self.menuBar() # リスト9(3) 「ファイル」メニューを追加し、それを「menuFile」という名前で参照できるようにする。 # 「&(アンパサンド)」の次の文字は「アクセスキー」になる。「Altキー」を押しながら # 「F」キーを押すと、「ファイル」メニューを開けるようになる。 menuFile = menu.addMenu('ファイル(&F)') ## ディレクトリ表示のため、メニューバーの「ファイル」に「開く」メニューを追加する。 # リスト?? メニュー項目に相当する「アクション」を作り、「menuFileOpen」という名前で参照できるようにする。 # 「QAction」クラスのコンストラクタに渡す最初の引数はアイコンの指定で、ここではQtが持っている # 「SP_DialogOpenButton」を指定している。 # 続く引数は、メニュー項目の文字列( '開く(&O)')と親オブジェクト(menuFile)。 menuFileOpen = QAction( QIcon(self.style().standardIcon( QStyle.StandardPixmap. SP_DialogOpenButton)), '開く(&O)', menuFile) # リスト?? 「開く」メニューが選ばれた場合の処理として、「self.showDialog」を指定。 # 「self.showDialog」は、フォルダを指定して、ファイルを表示する関数。 menuFileOpen.triggered.connect(self.showDialog) # リスト9(6) 「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) # リスト9(7) 「ヘルプ」メニューを生成して、「menuHelp」という名前で参照できるようにする。 menuHelp = menu.addMenu('ヘルプ(&H)') # リスト9(8) 「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) # リスト9(9) メニューの区切り線(セパレータ)を追加するコード。 menuHelp.addSeparator() # リスト9(10) バージョン情報を追加するコード。 # そのイベントハンドラは、(12)。 menuHelpVersion = QAction( QIcon(self.style().standardIcon( QStyle.StandardPixmap. SP_MessageBoxInformation)), 'バージョン情報(&V)', menuHelp) menuHelpVersion.triggered.connect( self.menuHelpVersionClicked) menuHelp.addAction(menuHelpVersion) # (3) 「QWidget」クラスのインスタンス「center」を作って、グリッドレイアウトの設定を # 行い、セントラルウィジェットに配置する。 # グリッドの1行目に、「label1」、「cb1」、「label2」、「cb2」、「buttonQuestion」 # (「確認」ボタン)、「buttonExec」(「実行」ボタン)を並べる。 # 「出題」ボタンを「確認」ボタンに変更。 center = QWidget(self) center.layout = QGridLayout(center) center.setLayout(center.layout) self.setCentralWidget(center) # (4) ラベル「label1」を生成。 # '1行目桁数'を'画像の幅'に変更。 label1 = QLabel('画像の幅', center) # (5) 水平方向は左寄せ、垂直方向は中央ぞろえに。 label1.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) # (6) 「label1」をグリッドの1行、1列に配置。 center.layout.addWidget(label1, 0, 0) # (7) コンボボックス「cb1」を生成。 self.cb1 = QComboBox(center) # (8) 選択肢を指定。 self.cb1.addItems(['150', '300', '1500', '3000']) # (9) 「cb1」をグリッドの1行、2列に配置。 center.layout.addWidget(self.cb1, 0, 1) # (10) ラベル「label2」を生成。 # '2行目桁数'を'画像の高さ'に変更。 label2 = QLabel('画像の高さ', center) label2.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) center.layout.addWidget(label2, 0, 2) # (11) コンボボックス「cb2」を生成。 self.cb2 = QComboBox(center) self.cb2.addItems(['150', '300', '1500', '3000']) center.layout.addWidget(self.cb2, 0, 3) # (12) 「確認」ボタンを生成。 # 「出題」ボタンを「確認」ボタンに変更。 buttonQuestion = QPushButton('確認', center) # (13) イベントハンドラとして、(21)の「buttonQuestionClicked」を指定する。 buttonQuestion.clicked.connect(self.buttonQuestionClicked) # (14) 「確認」ボタンをグリッドの1行、5列に配置。 # 「出題」ボタンを「確認」ボタンに変更。 center.layout.addWidget(buttonQuestion, 0, 4) # (15) 「実行」ボタンを生成。 # 「答え」ボタンを「実行」ボタンに変更。 # 「self.buttonAnswer」を「self.buttonExec」に変更。 self.buttonExec = QPushButton('実行', center) self.buttonExec.clicked.connect(self.buttonExecClicked) # (16) 起動時に「実行」ボタンが無効になるように設定。 # 問題が生成されていない状態で「実行」ボタンを押しても反応しないようにしている。 self.buttonExec.setEnabled(False) center.layout.addWidget(self.buttonExec, 0, 5) # (17) テキストボックス「text」を生成。 self.text = QTextEdit(center) #(18) 「text」のフォントの種類「明朝体」と大きさ「16」を設定。 self.text.setFont(QFont('MS 明朝', 16)) # (19) 書き込み禁止にするため、「ReadOnly」に設定。 self.text.setReadOnly(True) # (20) 「text」をウインドウの2行目(2番目の引数「1」)、1列目(3番目の引数「0」)に配置 # 引数の4番目は表示に何行分使うか、5番目は表示に何列分使うかの指定。 center.layout.addWidget(self.text, 1, 0, 1, 6) # 「show」メソッドを呼び出して、表示する。 self.show() # リスト9(11) 「menuHelpWeb」メニュー項目が選ばれた場合のコード。 # 「webbrowser.open」関数を使うため、(1)で「webbrowser」モジュールをインポートしている。 def menuHelpWebClicked(self): webbrowser.open( 'https://info.nikkeibp.co.jp/media/NSW/') # リスト9(12) 実行をするとこのプログラムのバージョン、作者名等のダイアログを出す。 # 「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) # (21) 「出題」ボタンのイベントハンドラ。 # 「出題」ボタンを「確認」ボタンに変更。 def buttonQuestionClicked(self): # (22) コンボボックスで選択された項目を取得し、文字列を整数に変換して、 # 変数「widthOfPicture」、「heightOfPicture」に格納する。 # 「widthOfPicture」、「heightOfPicture」は、(27)の「buttonExecClicked」メソッドで参照するので、 # 「self.」をつけて、クラスのメンバー変数にしている。 # 変数「width1」を「widthOfPicture」に、「width2」を「heightOfPicture」に変更。 self.widthOfPicture = int(str(self.cb1.currentText())) self.heightOfPicture = int(str(self.cb2.currentText())) # (23) 変数「question1」に、コンボボックス「cb1」で選んだ値を、 # 「question2」にコンボボックス「cb2」で選んだ値を格納する。 # self.question1 = self.widthOfPicture self.question2 = self.heightOfPicture # (24) 「確認」用の文字列を作って、「question」に格納する。 # 「{:>12}」は、12文字分のスペースに右詰めで文字を並べる書式指定文字列。 self.question = '画像の幅 = {}\n'.format(self.question1) + \ '画像の高さ = {}\n'.format(self.question2) print('self.question = ', self.question) # (25) 問題を「text」に表示する。 self.text.setText(self.question) # (26) 「実行」ボタンを有効(True)にする。 self.buttonExec.setEnabled(True) # (27) 「リサイズ実行」ボタンが押された時の処理。 # 「実行」ボタンを「リサイズ実行」ボタンに変更。 # 「buttonExecClicked」を「buttonExecClicked」ボタンに変更。 def buttonExecClicked(self): #photoDir = 'photo'を'self.dirName'に変更。 # (1) fnames = os.listdir(self.dirName) # (2) for fname in fnames: print('fname = ', fname) #if strOfSearch in 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) # 「QFileDialog」の「getExistingDirectory」を使って、リサイズの対象となる画像のあるフォルダ # を選択するダイアログウインドウを開き、選択したフォルダ名を「self.dirName」に格納する。 # 参考にしたURL:https://qiita.com/Nobu12/items/acd3caa625be8eebc09c def showDialog(self): # 第二引数はダイアログのタイトル、第三引数は表示するパス名。ここでは、引数を省略し、 # デフォルトの設定を使用する。 self.dirName = QFileDialog.getExistingDirectory() print('self.dirName = ', self.dirName)# プログラムを起動したときに最初に実行される。app = QApplication([])# 変数の初期化#self.widthOfPicture = 150#self.heightOfPicture = 150# 「ResizePicturePyQt」クラスを生成すると、「def __init__(self):」が実行される。w = ResizePicturePyQt()# これを実行することで、GUIの画面がユーザーに表示される。app.exec()