「PythonでGUIアプリ開発」の基礎 PyQt/PySide編 その12
「PythonでGUIアプリ開発」の基礎 PyQt/PySide編 その122022/09/29 『「独学でイッキに学べるPython入門」 その15 おまけ7』の記事を作成中、間違いに気づき、一部訂正①リスト9()の終了処理が、間違っていたので、修正。 (なぜ、前の処理でエラーが出なかったのかは、不明。)②それに伴い、(32)の関数を追加。#----------ここから# 「特集1 PythonでGUIアプリ開発」の基礎 PyQt/PySide編 その2## ステップ09 「掛け筆算練習プログラム」##『日経ソフトウエア』2021年11月号(p.63~p.65)## リスト10●掛け筆算練習プログラム(KakeHissanPyQt.py)## このプログラムでは、「QMainWindow」クラスを継承した「KakeHissanPyQt」クラスを使い、# そのセントラルウィジェットとして、「QWidget」クラスのインスタンス「center」を指定している。## (1)import random, sys, webbrowserfrom PyQt6.QtCore import *from PyQt6.QtGui import *from PyQt6.QtWidgets import *# 「QMainWindow」クラスを継承した「KakeHissanPyQt」クラスを記述。class KakeHissanPyQt(QMainWindow): # 「KakeHissanPyQt」クラスのコンストラクタ(インスタンス生成時に # 呼び出されるメソッド)である「__init__」を記述。 def __init__(self): # 「KakeHissanPyQt」クラスの親クラスである「QMainWindow」クラスのコンストラクタを実行するコード。 # この記述がないと必要な初期処理が実行されない。 super().__init__() # タイトルバー文字列の設定。 # 「KakeHissanPyQt」クラスのインスタンスは、(12)で変数「w」に代入され、「w」という名前で # 参照できるが、その名前はクラスの外部のみからしか利用できない。クラス内部から自分 # 自身のインスタンスを呼び出すには、コンストラクタで指定した「self」という名前を使う。 # 「setWindowTitle」メソッドに与えた「self.__class__.__name__」は、このクラスの名前で、 # 中身は「KakeHissanPyQt」。 self.setWindowTitle(self.__class__.__name__) # (2) 同様に「self」を使って、ラベルウィジェットの位置と大きさを設定する。 self.setGeometry(100, 100, 100, 400) # リスト9(2) QMainWindow」クラスを継承した「pyqt07」クラスの「menuBar」メソッドを呼び出して、 # メニューバーのインスタンスを作り、それを「menu」という名前で参照できるようにする。 menu = self.menuBar() # リスト9(3) 「ファイル」メニューを追加し、それを「menuFile」という名前で参照できるようにする。 # 「&(アンパサンド)」の次の文字は「アクセスキー」になる。「Altキー」を押しながら # 「F」キーを押すと、「ファイル」メニューを開けるようになる。 menuFile = menu.addMenu('ファイル(&F)') # リスト9(4) メニュー項目に相当する「アクション」を作り、「menuFileExit」という名前で参照できるようにする。 # 「QAction」クラスのコンストラクタに渡す最初の引数はアイコンの指定で、ここではQtが持っている # 「SP_TitleBarCloseButton」を指定している。 # 続く引数は、メニュー項目の文字列( '終了(&X)')と親オブジェクト。 menuFileExit = QAction( QIcon(self.style().standardIcon( QStyle.StandardPixmap. SP_TitleBarCloseButton)), '終了(&X)', menuFile) # リスト9(5) 「終了」メニューが選ばれた場合の処理として、「quitself.winClose」を指定。 # 「quit」は、「QApplication」静的メソッドで、呼び出すとプログラムが終了する。 menuFileExit.triggered.connect(quit) menuFileExit.triggered.connect(self.winClose) # リスト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」 # (「出題」ボタン)、「buttonAnswer」(「答え」ボタン)を並べる。 center = QWidget(self) center.layout = QGridLayout(center) center.setLayout(center.layout) self.setCentralWidget(center) # (4) ラベル「label1」を生成。 label1 = QLabel('1行目桁数', 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(['2', '3', '4', '5']) # (9) 「cb1」をグリッドの1行、2列に配置。 center.layout.addWidget(self.cb1, 0, 1) # (10) ラベル「label2」を生成。 label2 = QLabel('2行目桁数', center) label2.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) center.layout.addWidget(label2, 0, 2) # (11) コンボボックス「cb2」を生成。 self.cb2 = QComboBox(center) self.cb2.addItems(['2', '3', '4', '5']) 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 = QPushButton('答え', center) self.buttonAnswer.clicked.connect(self.buttonAnswerClicked) # (16) 起動時に「答え」ボタンが無効になるように設定。 # 問題が生成されていない状態で「答え」ボタンを押しても反応しないようにしている。 self.buttonAnswer.setEnabled(False) center.layout.addWidget(self.buttonAnswer, 0, 5) # (17) テキストボックス「text」を生成。 self.text = QTextEdit(center) #(18) 「text」のフォントの種類と大きさを設定。 self.text.setFont(QFont('MS 明朝', 32)) # (19) 書き込み禁止にするため、「ReadOnly」に設定。 self.text.setReadOnly(True) # (20) 「text」を2行、1列に配置 # 引数の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」モジュールをインポートしている。 def menuHelpVersionClicked(self): s = self.__class__.__name__ s += ' Version 0.01(2021/08/25)\n' s += '@2021 Hideo Harada\n' s += 'with Python ' + sys.version QMessageBox.information( self, self.__class__.__name__, s) # (21) 「出題」ボタンのイベントハンドラ。 def buttonQuestionClicked(self): # (22) コンボボックスで選択された項目を取得し、文字列を整数に変換して、 # 変数「width1」、「width2」に格納する。 # 「width2」は、(27)の「buttonAnswerClicked」メソッドで参照するので、 # 「self.」をつけて、クラスのメンバー変数にしている。 width1 = int(str(self.cb1.currentText())) self.width2 = int(str(self.cb2.currentText())) # (23) 乱数を使って、必要な桁数の整数を生成し、変数「question1」、 # 「question2」に格納する。 # 乱数を得る「random」モジュールは、(1)でインポートしている。 self.question1 = random.randrange( 10**(width1-1), 10**width1-1) self.question2 = random.randrange( 10**(self.width2-1), 10**self.width2-1) # (24) 「出題」用の問題の文字列を作って、「question」に格納する。 # 「{:>12}」は、12文字分のスペースに右詰めで文字を並べる書式指定文字列。 self.question = '{:>12}\n'.format(self.question1) + \ ' ×{:>5}\n -------------\n'.format(self.question2) # (25) 問題を「text」に表示する。 self.text.setText(self.question) # (26) 「答え」ボタンを有効(True)にする。 self.buttonAnswer.setEnabled(True) # (27) 「答え」ボタンが押された時の処理。 def buttonAnswerClicked(self): # (28) 問題の文字列を「答え」の文字列である「answerString」に入れる。 answerString = self.question # (29) 「for」ループで、問題1行目の数値と、問題2行目の1桁を掛け合わせ、 # 「partialAnswer」に格納する。 # それがゼロでなかった場合は、「answerString」に追加で書き込む。 # 桁が上がっていくにつれ、表示位置を左にずらす。 for i in range(1, self.width2+1): partialAnswer = self.question1 * \ int(str(self.question2)[-i]) if(partialAnswer != 0): answerString += ('{:>' + str(13-i) + '}\n' ).format(partialAnswer) # (30) 2番目の横線と掛け算の答えを追加する。 answerString += ' -------------\n' + '{:>12}'.format( self.question1 * self.question2) # (31) 「answerString」を「text」に表示する。 self.text.setText(answerString) # (32) 「終了」ボタンが押された時の処理。 def winClose(self): self.close() # プログラムを起動したときに最初に実行される。app = QApplication([])# 「KakeHissanPyQt」クラスを生成すると、「def __init__(self):」が実行される。w = KakeHissanPyQt()# これを実行することで、GUIの画面がユーザーに表示される。app.exec()#-----------ここまでリスト10の実行結果「出題」ボタンを押したところ。「答え」ボタンをおしたところ<おまけ>リスト10をJupiterNotebookで実行した結果。「出題」ボタンを押したところ。「答え」ボタンを押したところ。