本記事は、
- Pythonのスキル、初心者以上の方
を対象にした内容になっております。
本記事を含む、一連の記事を読むことで、(全7回)
- かっこよくて
- おしゃれな
「簡単なGUIアプリ」を作成できるようになります。
例えば、wxPythonで、画像表示アプリを作ってみました。
上記みたいな、見た目(Macの場合)となります。
本記事は、下記の続編となります。
前回までの記事を読んでいなくても、内容が理解できるよう、構成しています。
本記事のゴール設定
wxPythonの
- 標準的な、GUI構造の理解と、
- GUI部分のコードの書き方
を理解する事になっています。
wxPythonにおける、GUI構造
以下のGUI構造が、標準です。
この段階で、意味が分からなくても、大丈夫です。
順番に、解説していきます。
<GUI 構造>
- 「Frame」というトップウインドウを、まず作る
- その子供に、「Panel」と呼ばれる部品を配置する。
- その「Panel」のレイアウト部品として、「Sizer」と呼ばれる部品を設定。(そのSizerの中に、別のSizerの設置も可能)
- 「Sizer」に、ボタン等のウィジェット(コントロール部品)を追加する。
という構造をとります。
厳密な親子関係は以下の通りです。
(よく分からない場合、本記事を最後まで読んだ後、ご覧になると理解できると思います。)
「Panel」の概念把握と、コード紹介
Panelとは
Panelは、その子供に設定された「コントロール部品」間をタブで移動できる、つまり部品をグループにまとめる役割があります。
次回以降の解説となりますが、ボタンを押した時の挙動等(イベント処理)にも、影響があります。
Panelの解説(英語版ですいません。日本語の解説が見つかりませんでした。)
wx.Panel widgets enable tabbing between Widgets on Windows. So if you want to be able to tab through the widgets in a form you have created, you are required to have a panel as their parent.
解説本「Creating GUI Applications with wxPython」 P10
また、Frameの子供に設定されたGUI部品は、ウインドウ全体に広げられます。
前回の記事では、Frame直下にウィジェットを設置すると、画面いっぱいに広がっていました。
例えば、コンボボックスを設置して、「▼をクリック」した状態が、下記画面になります。
ここに、「Frame」の子供に「Panel」を設置し、その「Panel」の子供に「コンボボックス」を設置した例を見てみます。
コンボボックスの直下に選択肢が現れ、期待した通りの見た目になっています。
(参考)前回紹介した「ラジオボタン」の場合、以下のようになります。
Panel のコード
以下のコードが、wxPythonの、基本的なコード(GUI部分)になります。
- wx.Panelを継承した、Panelクラスを作成
- wx.Frameを継承した、Frameクラスを作成
- Frameクラスから、Panelクラスをインスタンス化。
(下記コード中の、クラス名は、自由に変更して下さい。 また、見やすさ優先で、PEP8を無視した記載になっています。 ご了承ください。)
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
super().__init__(self, parent)
######################################
# ここにSizer, ボタン等のウィジェットを記載
# (本記事にて、後ほど、解説)
######################################
######################################################
# ここにボタンを押下等のイベント処理を、メソッドで記載することが多い
# (別記事にて、解説します)
######################################################
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, id=-1, title='wxPython')
panel = MyPanel(self)
########################################################
# 必要ならば、メニューバー、ステータスバー、ツールバーを作成する
# (別記事にて、解説します)
########################################################
self.Show()
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()
「Sizer」の概念把握
「Sizer」とは
前回の記事で紹介した、下記問題が解決できる、レイアウト部品です。
- 各ウィジェットが重なって表示される
- ウインドウサイズを大きくした場合(マウスのドラッグ等にて)、画面が再レイアウトしてくれない
Sizerを使うと、
- 各ウィジェットが、重ならないようになる
- ウインドウサイズを変更した時、再レイアウトしてくれる
ようになります。
Sizerにも、さまざまな種類があります。
次の章で、ご紹介します。
「Sizer」の種類
Sizerには、下記の種類があります。
- BoxSizer
- StaticBoxSizer
- GridSizer
- FlexGridSizer
- WrapSizer
- GridBugSizer(本記事では、省略。)
順番に、各特徴を見ていきます。
BoxSizer
ウインドウの中に、
- ウィジェットを、長方形状に、並べていく
レイアウト部品です。
<イメージ絵>
下絵は、BoxSizerの中に、ウィジェットを3つ設置し、縦方向に重ねたものです。
ウインドウ(Frame)が大きくなると、
- 「ウィジェット」も比例して、大きくなっています。
<上記イメージ図の、GUI構造>
BoxSizerの特徴:
- ウインドウ全体に渡って、ウィジェットを縦に並べる。
- ウィジェットを横に並べる事も可能。
- 下絵の通り、BoxSizerの中に設置したウィジェットは、ウインドウの大きさに合わせて、サイズ比を保ったまま、大きさが変わる。(大きさを変えない設定も可能)
StaticBoxSizer
既に紹介した、BoxSizserに
- 外枠をつけて、タイトル名を表示する事ができる
レイアウト部品です。
下記の、イメージ図を見て頂いた方が、理解が早いと思います。
<イメージ絵>
<上記イメージ図の、GUI構造>
StaticBoxSizerの特徴:
- ウイジェットを視覚的に、グループ化する時に使用。
- 他の特徴は、BoxSizerと同じです。
GridSizer
ウインドウの中に、
- ウィジェットを、格子状(グリッド状)に並べていく
レイアウト部品です。
<イメージ絵>
下絵は、
- 縦2 ✖️ 横2で設定したGridSizerを使用して、
- ウィジェットを4つ、設置したイメージになります。
<上記イメージ図の、GUI構造>
GridSizerの特徴:
- 縦、横の分割数を、任意で設定できる
- ウインドウ拡大・縮小時の挙動は、BoxSizerの解説と同じ
FlexGridSizer
既に紹介したGridSizerは
- 全グリッドのサイズが同じ
であるのに対し
- 設定した行、もしくは列のみ(もしくは両方)、サイズが変更できる
レイアウト部品です。
<イメージ図>
2列目のみ、変更可能に設定した場合の、挙動です。
<上記イメージ図の、GUI構造>
FlexGridSizerの特徴:
- ウインドウサイズ変更時に、サイズを変更したい行、列が設定できる。(正確には、「○行目、△列目を可変にする」と設定する。)
- 他は、GridSizerの特徴と同じです。
WrapSizer
ウインドウに
- スペースがある限り、ウィジェットを一列に並べる
- スペースがなくなったら、ウィジェットの並べ替えをする
という、レイアウト部品です。
下記イメージ図を見て頂いた方が、理解が早いと思います。
<イメージ図>
<上記イメージ図の、GUI構造>
WrapSizerの特徴:
- HTML5でいう、「レスポンシブWEBデザイン」みたいな挙動をする。(ウィンドウサイズにより、再レイアウトされる。)
- 他の機能は、他Sizerと同じです。
各「Sizer」のコード紹介
紹介する順番は以下の通りです。(前章と同じ順番です。)
- BoxSizer
- StaticBoxSizer
- GridSizer
- FlexGridSizer
- WrapSizer
Sizerを使ったコード手順(全Sizer共通)
- 「ウィジェット」を作る(ボタン、テキスト等)
- 「Sizer」を作る
- 作った「Sizer」に、作った「ウィジェット」をAddしていく
- Panelの規定Sizerに、作った「Sizer」を指定する
上記の「作る」という表現は、「インスタンス化」を意味しています。
以下の章では、
- 既に紹介した下記コードの、7行目〜10行目に該当するコードを
順番に、ご紹介します。
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
super().__init__(self, parent)
######################################
# ここにSizer, ボタン等のウィジェットを記載
# (本記事にて、解説)
######################################
######################################################
# ここにボタンを押下等のイベント処理を、メソッドで記載することが多い
# (別途、解説します)
######################################################
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, id=-1, title='wxPython')
panel = MyPanel(self)
########################################################
# 必要ならば、メニューバー、ステータスバー、ツールバーを作成する
# (別途、解説します)
########################################################
self.Show()
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()
BoxSizer のコード
下記構造の、コードをご紹介します。
BoxSizerは、wx.BoxSizer( )にて指定します。
引数として
- wx.VERTICAL: ウィジェットを縦に並べる
- wx.HORIZONTAL: ウィジェットを横に並べる
を指定します。
コードは、わかりやすいように、パラメータ引数を使って記載しています。
このBoxSizerに、
- Add( )でウィジェットを追加していき、
- 最後に、Panelの規定Sizerに、作ったSizerを指定します。
# ウィジェットを作る
text1 = wx.TextCtrl(self, id=-1, value='aa')
# 分かり易さのため、背景を青に設定
text1.SetBackgroundColour('blue')
# ウィジェットを作る
text2 = wx.TextCtrl(self, id=-1, value='aa')
# 色の指定は、下記方法でもOK
text2.SetBackgroundColour('#ff00ff')
# Sizerを作る
sizer = wx.BoxSizer(orient=wx.VERTICAL) # 縦に並べる
# 各ウィジェットをSizerにAdd していく
sizer.Add(text1, 1)
sizer.Add(text2, 1)
# Panleの規定Sizerに指定
self.SetSizer(sizer)
<実行結果>
◆ wx.BoxSizer(wx.HORIZONTAL) を指定した場合
各ウィジェットの、サイズ比を変えたい
上記のコードは、ウィジェットのサイズ比が1:1でしたが、2:1に変えたコードがこちらです。
13行目と14行目の
- sizer.Add(text, 1) の
第2引数の「1」がサイズを表しています。
text1 = wx.TextCtrl(self, id=-1, value='aa')
text1.SetBackgroundColour('blue')
text2 = wx.TextCtrl(self, id=-1, value='aa')
text2.SetBackgroundColour('#ff00ff')
sizer = wx.BoxSizer(orient=wx.VERTICAL)
# 大きさを 2 にする
sizer.Add(text1, 2)
# 大きさを 1 にする
sizer.Add(text2, 1)
self.SetSizer(sizer)
これで、サイズ比が 2 : 1 で保たれます。
<実行結果>
StaticBoxSizer のコード
下記構造の、コードをご紹介します。
StaticBoxSizerは、wx.StaticBoxSizer( )にて指定します。
引数として
- 第1引数: wx.VERTICAL or wx.HORIZONTAL
- 第2引数: 親を指定
- 第3引数: グループのタイトル
を指定します。
コードは、わかりやすいように、パラメータ引数を使って記載しています。
button_1 = wx.Button(self, -1, 'ボタン1')
button_2 = wx.Button(self, -1, 'ボタン2')
button_3 = wx.Button(self, -1, 'ボタン3')
sizer = wx.StaticBoxSizer(orient=wx.HORIZONTAL, parent=self, title='タイトル')
# 上記1行は、下記、2行でもOK
# nm = wx.StaticBox(self, -1, 'タイトル')
# sizer = wx.StaticBoxSizer(box=nm, orient=wx.HORIZONTAL)
sizer.Add(button_1)
sizer.Add(button_2)
sizer.Add(button_3)
self.SetSizer(sizer)
<実行結果>
GridSizer のコード
下記構造の、コードをご紹介します。
GridSizerは、wx.GridSizer( )にて指定します。
引数として
- 第1引数: 縦を何分割するか
- 第2引数: 横を何分割するか
- 第3引数: 各ウィジェットの隙間(タプルにて、ピクセル指定)
を指定します。
コードは、わかりやすいように、パラメータ引数を使って記載しています。
text1 = wx.TextCtrl(self, id=-1, value='aa')
text1.SetBackgroundColour('blue')
text2 = wx.TextCtrl(self, id=-1, value='bb')
text2.SetBackgroundColour('#ff00ff')
text3 = wx.TextCtrl(self, id=-1, value='cc')
text3.SetBackgroundColour('red')
text4 = wx.TextCtrl(self, id=-1, value='dd')
text4.SetBackgroundColour('pink')
sizer = wx.GridSizer(rows=2, cols=2, gap=(0, 0))
sizer.Add(text1)
sizer.Add(text2)
sizer.Add(text3)
sizer.Add(text4)
self.SetSizer(sizer)
<実行結果>
FlexGridSizer のコード
下記構造の、コードをご紹介します。
FlexGridSizerは、wx.FlexGridSizer( )にて指定します。
引数にの指定は、GridSizerと同じです。
- 第1引数: 縦を何分割するか
- 第2引数: 横を何分割するか
- 第3引数: 各ウィジェットの隙間(タプルにて、ピクセル指定)
コードは、わかりやすいように、パラメータ引数を使って記載しています。
見た目優先で、本コード(Sizer.Add( ) のメソッド)の引数に、wx.EXPAND を指定しています。
詳細は、後ほど、解説しています。
text1 = wx.TextCtrl(self, id=-1, value='aa')
text2 = wx.TextCtrl(self, id=-1, value='bb')
text3 = wx.TextCtrl(self, id=-1, value='cc')
text4 = wx.TextCtrl(self, id=-1, value='dd')
text5 = wx.TextCtrl(self, id=-1, value='ee')
text6 = wx.TextCtrl(self, id=-1, value='rr')
sizer = wx.FlexGridSizer(rows=3, cols=2, gap=(10, 10))
sizer.Add(text1, 1, wx.EXPAND)
sizer.Add(text2, 1, wx.EXPAND)
sizer.Add(text3, 1, wx.EXPAND)
sizer.Add(text4, 1, wx.EXPAND)
sizer.Add(text5, 1, wx.EXPAND)
sizer.Add(text6, 1, wx.EXPAND)
# サイズ変更できる行を指定 (0から開始)
sizer.AddGrowableRow(0) # 1行目
# サイズ変更できる列を指定 (0から開始)
sizer.AddGrowableCol(1) # 2列目
self.SetSizer(sizer)
<実行結果>
WrapGridSizer のコード
下記構造の、コードをご紹介します。
WrapSizerは、wx.WrapSizer( )にて指定します。
引数として
- 第1引数: wx.VERTICAL → ウィジェットを縦に並べるのを優先。wx.HORIZONTAL → ウィジェットを横に並べるのを優先。
- 第2引数: ウィジェット配置時に、スペースを残すかどうか
wx.WRAPSIZER_DEFAULT_FLAGS → 残す
wx.REMOVE_LEADING_SPACES → 残さない
を指定します。
第2引数の挙動ですが、文章だけでは、よく分からないと思うので、実行結果をご参照下さい
コードは、わかりやすいように、パラメータ引数を使って記載しています。
見た目優先で、本コード(Sizer.Add( ) のメソッド)の引数に、wx.EXPAND を指定しています。
詳細は、後ほど、解説しています。
text1 = wx.TextCtrl(self, id=-1, value='aa')
text2 = wx.TextCtrl(self, id=-1, value='bb')
text3 = wx.TextCtrl(self, id=-1, value='cc')
text4 = wx.TextCtrl(self, id=-1, value='dd')
text5 = wx.TextCtrl(self, id=-1, value='ee')
text6 = wx.TextCtrl(self, id=-1, value='rr')
sizer = wx.WrapSizer(orient=wx.HORIZONTAL, flags=wx.WRAPSIZER_DEFAULT_FLAGS)
sizer.Add(text1, 1, wx.EXPAND)
sizer.Add(text2, 1, wx.EXPAND)
sizer.Add(text3, 1, wx.EXPAND)
sizer.Add(text4, 1, wx.EXPAND)
sizer.Add(text5, 1, wx.EXPAND)
sizer.Add(text6, 1, wx.EXPAND)
self.SetSizer(sizer)
<実行結果>
◆ wx.WrapSizerのflags引数:wx.WRAPSIZER_DEFAULT_FLAGS の場合
テキストコントロールとウインドウ枠との間に、空白ができています。
◆ wx.WrapSizerのflags引数:wx.REMOVE_LEADING_SPACES の場合
テキストコントロールとウインドウ枠との間の、空白が埋まっています。
各Sizerの詳細設定
ここでは、BoxSizer を例として、ご紹介します。
ただし、全てのSizerに、応用できる内容になっています。
sizer.Add( )の引数 詳細解説
sizer.Add( )の引数、詳細に見てみます。
- 第1引数:Sizerに追加するウィジェット(コントロール)を指定
- 第2引数:proportionというキーワード引数名になります。コントロールの大きさを指定。0 の場合、最小サイズが設定されます。
- 第3引数:flagというキーワード引数名になります。詳細は、この後の記事を参考にして下さい。
- 第4引数:borderというキーワード引数名になります。詳細は、この後の記事を参考にして下さい。
キーワード引数を省略せずに記載すると
- sizer.Add(<ウィジェット名>, proportion=<XXX>, flag=<XXX>, border=<XXX>) になります。 (※ 例 sizer.Add(text, proportion=1, flag=wx.LEFT, border=1)
になります。
各ウィジェットを左寄せ、中央寄せ、右寄せにしたい
sizer.Add( )に第3引数を追加します。
wx.BoxSizer(VERTICAL)の場合、指定できるもの:
- 左寄せ → sizer.Add(text, 1, wx.ALIGN_LEFT)
- 中央寄せ → sizer.Add(text, 1, wx.ALIGN_CENTER)
- 右寄せ → sizer.Add(text, 1, wx.ALIGN_LEFT)
wx.BoxSizer(HORIZONTAL)の場合、指定できるもの:
- 上寄せ → sizer.Add(text, 1, wx.ALIGN_TOP)
- 中央寄せ → sizer.Add(text, 1, wx.ALIGN_CENTER)
- 下寄せ → sizer.Add(text, 1, wx.ALIGN_BOTTOM)
(上記メソッドの、第1、第2引数は、仮で表記しています。)
text1 = wx.TextCtrl(self, id=-1, value='aa')
text1.SetBackgroundColour('blue')
text2 = wx.TextCtrl(self, id=-1, value='aa')
text2.SetBackgroundColour('#ff00ff')
sizer = wx.BoxSizer(wx.VERTICAL)
# 中央寄せ
sizer.Add(text1, 2, wx.ALIGN_CENTER)
# 右寄せ
sizer.Add(text2, 1, wx.ALIGN_RIGHT)
self.SetSizer(sizer)
<実行結果>
各ウィジェットで、ウインドウとの空白を埋めたい
sizer.Add( )に第3引数を追加します。
- sizer.Add(text, 1, wx.EXPAND)
(上記メソッドの、第1、第2引数は、仮で表記しています。)
text1 = wx.TextCtrl(self, id=-1, value='aa')
text1.SetBackgroundColour('blue')
text2 = wx.TextCtrl(self, id=-1, value='aa')
text2.SetBackgroundColour('#ff00ff')
sizer = wx.BoxSizer(wx.VERTICAL)
# wx.EXPAND を追加
sizer.Add(text1, 2, wx.EXPAND)
sizer.Add(text2, 1, wx.EXPAND)
self.SetSizer(sizer)
<実行結果>
◆ (参考)GridSizerの場合
指定したサイズで、マージンを作りたい
ウィジェットの境界に、指定したサイズで、余白を作りたい場合です。
sizer.Add( )の、第3、第4引数で設定します。
下記メソッドの場合、上下左右に、10pxの余白が作られます。
- sizer.Add(text, 1, wx.ALL, 10)
上下左右、単独で設定したい場合は、以下の通りです。
- 上に余白 → sizer.Add(text, 1, wx.TOP, 10)
- 下に余白 → sizer.Add(text, 1, wx.BOTTOM, 10)
- 左に余白 → sizer.Add(text, 1, wx.LEFT, 10)
- 右に余白 → sizer.Add(text, 1, wx.RIGHT, 10)
上記を組み合わせる事もできます。( | を使用します。複数使用OK)
- 上と左と右のみ → sizer.Add(text, 1, wx.TOP | wx.LEFT | wx.RIGHT, 10)
(上記メソッドの、第1、第2引数は、仮で表記しています。)
text1 = wx.TextCtrl(self, id=-1, value='aa')
text1.SetBackgroundColour('blue')
text2 = wx.TextCtrl(self, id=-1, value='aa')
text2.SetBackgroundColour('#ff00ff')
sizer = wx.BoxSizer(wx.VERTICAL)
# wx.EXPAND と 同時に使用
# 上下左右に10px、余白
sizer.Add(text1, 2, wx.EXPAND | wx.ALL, 10)
# 上、左右に10px、余白
sizer.Add(text2, 1, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 10)
self.SetSizer(sizer)
<実行結果>
ウィジェットとウインドウとの間に、10pxの空白ができています。
(text1とtext2の間は、合計20pxの空白が、発生。)
番外編 Sizerの中にSizerを入れる
Sizerの中に、Sizerを入れる事も可能です。
例えば、GridSizerの中に、BoxSizerを設置する事が可能です。
<例えば、下記構造とか・・>
上記構造の場合、コードは以下の通りです。
text_box_sizer = wx.TextCtrl(self, id=-1, value='BoxSizer下')
text_grid1 = wx.TextCtrl(self, id=-1, value='GridSizer下_1')
text_grid2 = wx.TextCtrl(self, id=-1, value='GridSizer下_2')
text_grid3 = wx.TextCtrl(self, id=-1, value='GridSizer下_3')
text_grid4 = wx.TextCtrl(self, id=-1, value='GridSizer下_4')
# Sizerをインスタンス化
sizer = wx.BoxSizer(wx.VERTICAL)
grid_sizer = wx.GridSizer(rows=2, cols=2, gap=(0, 0))
# GridSizerにAddしていく
grid_sizer.Add(text_grid1, 1, wx.EXPAND)
grid_sizer.Add(text_grid2, 1, wx.EXPAND)
grid_sizer.Add(text_grid3, 1, wx.EXPAND)
grid_sizer.Add(text_grid4, 1, wx.EXPAND)
# BoxSizerにAddしていく
sizer.Add(text_box_sizer, 1, wx.EXPAND)
sizer.Add(grid_sizer, 1, wx.EXPAND)
self.SetSizer(sizer)
<実行結果>
次回予告
第2回と3回(本記事)で、画面の見た目を、ご紹介してきました。
次回記事では、ボタン押下等の処理、つまりイベント発生時の処理方法をご紹介します。
次回記事は、ステータスバー、メニューバーの紹介をしました。
画面の「見た目」を、まとめて紹介した方が、わかりやすいと思ったからです。
イベント処理のご紹介は、次回以降にしました。
リンクを貼り付けます。
本記事は、少々長くなってしまいました。
次回の記事は、できるだけ短くなるよう、努めます。
最後まで読んで頂き、ありがとうございました。
また、お会いしましょう。