wxPythonでGUIアプリを作成しよう【Python】

公開日:  

python wxpython


wxPythonを使ってPythonでGUIアプリケーションを作ってみましょう。


環境

  • Windows10
  • Python 3.6.2
  • wxPython 4.0.7.post2
  • wxGlade 0.9.3

準備

必要なものをインストールしましょう。

・wxPythonのインストール

まずはwxPythonをインストールします。これがなければ始まりません。

pip install wxpython

・wxGladeのダウンロード

続いて、wxGladeというツールをダウンロードします。
これはwxPythonのためのGUIビルダーで、フォームやボタン配置などのレイアウトを作成することができ、さらに作成したレイアウトからPythonのソースを自動生成してくれます。
ソースコードから(パラメーターを細かくいじって)レイアウトを調整するより、視覚的にレイアウトを決められるため便利です。

以下からダウンロードします。

ダウンロードしたフォルダの中にwxglade.pyというファイルがあるはずです。このファイルを実行するとwxGladeが起動します(wxPythonがインストール済みでないと起動できません)。

python wxglade.py
wxGladeの起動

wxGladeの起動

以上で準備完了です。
次項からGUIプログラム作成に入ります。


wxPythonとwxGladeによるGUIプログラム作成

いよいよプログラム作成に入るのですが、そもそも何を作るのか決めていませんでしたね。
できるだけシンプルなものがよいので、「フォルダ圧縮プログラム」でどうでしょうか? フォルダを指定して「圧縮」ボタンを押すと、(zip形式などで)圧縮されたファイルを出力するプログラムです。

今回作成するフォルダ圧縮プログラム

今回作成するフォルダ圧縮プログラム

レイアウトの作成

wxGladeを使用してレイアウトを作成しましょう。
今回作成する「フォルダ圧縮プログラム」では以下の要素が必要そうですね。

  • 圧縮したいフォルダや出力先を指定するためのテキスト入力欄フォルダ参照ボタン
  • 圧縮フォーマットを指定するためのセレクトボックス
  • 圧縮処理を実行するための実行ボタン

これらをwxGladeで配置していきます。

レイアウトを作成する基本的な流れとしては、

  1. 部品を配置する
  2. 適宜、部品のプロパティを編集する(大きさや色など見た目、イベントハンドラなど)
  3. レイアウトが完成したら、Pythonファイルとして出力する

となります。

wxGladeの基本

wxGladeの基本

メインフレームを配置

まずはメインフレーム(Frame)を配置します。

  • 左上の部品エリアから「Windows」>「Add a Frame」をクリック
「Add a Frame」をクリック

「Add a Frame」をクリック

  • 「Select frame class」というダイアログが開くのでそのまま「OK」をクリック
「OK」をクリック

「OK」をクリック

  • 右側のツリーにFrameが追加されます。
Frameが追加された

Frameが追加された

配置したFrameのプロパティを編集します(別このタイミングである必要はありませんが、説明の都合上このタイミングで行っています)。
ツリーから要素を選択すると、左下にプロパティが表示され編集することができます。
今回は下記のプロパティとしました。

  • Common
    • Size 640, 480
    • Background #ffffff
  • Widget
    • Title 圧縮プログラム

フォルダ選択のための入力欄を配置

フォルダを選択するためのテキスト入力欄とファイル参照ボタン(押すとフォルダ選択ダイアログが開く)を配置します。
個別の要素を並べる前に、ラベル、テキスト入力欄、ボタンを水平方向に並べるためにBoxSizerを追加します。

  • 左上の部品エリアから「Sizers」>「Add a BoxSizer」をクリックし、ツリー上の「SLOT 1」をクリック
BoxSizerを追加

BoxSizerを追加

  • 「Select sizer type」というダイアログが開くので、OrientationにHorizontal(水平)を選択し、さらにSlotsを3にします。そして「OK」をクリックします。
Slotsを3にして「OK」をクリック

Slotsを3にして「OK」をクリック

  • 右側のツリーにBoxSizerが追加されます。
BoxSizerが追加された

BoxSizerが追加された

Slotが3つありますが、それぞれに部品を割り当てていきます。
まずはラベルを配置します。

  • 左上の部品エリアから「Static」>「Add a StaticText」をクリックし、ツリー上の「SLOT 1」をクリック
StaticTextが追加された

StaticTextが追加された

追加したStaticTextのプロパティをいじります。

  • Layout
    • Border 5
    • Alignment wxALIGN_CENTER
  • Widget
    • Label 圧縮したいフォルダ:
StaticTextのプロパティを変更

StaticTextのプロパティを変更

続いて、テキストボックスを配置します。

  • 左上の部品エリアから「Inputs」>「Add a TextCtrl」をクリックし、ツリー上の「SLOT 2」をクリック
TextCtrlを追加

TextCtrlを追加

追加したTextCtrlのプロパティを以下に変更します。

  • Layout
    • Proportion 2
    • Border 5
    • Alignment wxALIGN_CENTER

3つ目のSlotにはボタンを配置します。

  • 左上の部品エリアから「Buttons」>「Add a Button」をクリックし、ツリー上の「SLOT 3」をクリック
Buttonを追加

Buttonを追加

追加したButtonのプロパティは以下です。

  • Layout
    • Border 5
    • Alignment wxALIGN_CENTER
  • Widget
    • Label
  • Events
    • EVT_BUTTON OnChooseTargetDir(ボタンがクリックされたときに実行される関数名を指定します)

ボタンがクリックされたときの処理(フォルダ選択画面を開く処理)を記述するため、イベントハンドラを登録しています。実際の処理はソースコード内に記述します。

ここまででどのような画面ができているか確認してみましょう。
ツリー上のFrameをクリックするとプロパティに「Show Preview」というボタンがあるので、それをクリックしてください。プレビューが表示されます。

「Show Preview」をクリック

「Show Preview」をクリック

以下のような画面になっているでしょうか?

ここまでのプレビュー画面

ここまでのプレビュー画面

圧縮フォーマット選択のためのセレクトボックスを配置

圧縮フォーマット(zip、tar)などを選択するためのセレクトボックスを配置しましょう。

  • ツリー上のFrame直下のSizerを右クリックし、「Add slot」をクリックします。
Slotを追加

Slotを追加

  • 作成されたSlotにBoxSizer(Horizontal、slotsは2)を割り当てます。
BoxSizerを追加

BoxSizerを追加

  • 1つ目のSlotにラベル(StaticText)を配置します。前項と同じように追加しプロパティ設定も行います。
StaticTextを追加

StaticTextを追加

  • 続いてセレクトボックス(ComboBox)を配置します。
ComboBoxを追加

ComboBoxを追加

ComboBoxのプロパティを以下のように編集します。

  • Layout
    • Border 5
    • Alignment wxALIGN_CENTER
  • Widget
    • Style wxCB_DROPDOWN | wxCB_READONLY
    • SELECTION 0
    • CHOICES zip, tar, gztar, bztar, xztar(これが選択肢となります)

ここまでで以下のような画面ができました。

ここまでのプレビュー画面

ここまでのプレビュー画面

出力先を指定するための入力欄を配置

出力先のフォルダを選択するための入力欄を配置します。
先ほどもフォルダ選択の入力欄(ラベル、テキストボックス、ボタン)を配置しましたね。手順はそれと同じなので詳細は省略します。

以下のような配置となります。

StaticText、TextCtrl、Buttonを追加

StaticText、TextCtrl、Buttonを追加

プロパティも先ほどのものとほぼ同じでよいですが、Buttonのイベントハンドラの設定は下記としておきましょう。

  • Events
    • EVT_BUTTON OnChooseOutputDir

ここまでで以下の画面ができました。

ここまでのプレビュー画面

ここまでのプレビュー画面

実行ボタン、キャンセルボタンを配置

処理を実行するための実行ボタンとプログラムを終了するためのキャンセルボタンを配置します。
BoxSizerを追加しその中にボタンを配置していきます。

  • BoxSizerを追加します(Horizontal、slotsは3)。
  • 1番目のSlotにSpacerを配置します。Spacerとはその名の通りただスペースを作ってくれるだけのもので、レイアウトの調整に使います。
Spacerを追加

Spacerを追加

Spacerのプロパティを下記に変更します。

  • Layout
    • Proportion 2
    • Border 5
    • Alignment wxALIGN_CENTER

続いて、ボタンを配置していきます。何度もやってるので分かりますよね?

ボタンを追加

ボタンを追加

プロパティでイベントハンドラを設定するのを忘れないでください。

  • 実行ボタンのプロパティ
    • Events
      • EVT_BUTTON OnExec
  • キャンセルボタンのプロパティ
    • Events
      • EVT_BUTTON OnCancel

これで以下のような画面が作れたと思います。

ここまでのプレビュー画面

ここまでのプレビュー画面

必要な要素は配置できたので、これでレイアウト作成は終わりとしましょう。

保存

今まで編集してきた内容を保存しましょう。[Ctrl]+[s]で拡張子がwxgのファイルとして保存できます。 このwxgファイルはwxGladeで使用するファイル形式です。次にまた同じwxgファイルを編集したければwxGladeで開けば編集できます。

Pythonのソースコードを生成

作成したレイアウトを元にPythonのソースコードを生成しましょう。
まず、ツリー上のApplicationをクリックします。プロパティに「Generate Source」というボタンがあるのでそれをクリックすると、ソースコードを生成できます。

ソースコード生成の際の設定は以下の画像を参考にしてください。

ソースコードの生成

ソースコードの生成

以下が今回生成されたソースコードです。
イベントハンドラとして設定した関数(OnChooseTargetDirやOnExecなど)もちゃんと定義されています。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# generated by wxGlade 0.9.3 on Mon Dec 30 09:00:28 2019
#

import wx

# begin wxGlade: dependencies
# end wxGlade

# begin wxGlade: extracode
# end wxGlade


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((640, 480))
        self.text_ctrl_1 = wx.TextCtrl(self, wx.ID_ANY, "")
        self.button_1 = wx.Button(self, wx.ID_ANY, "...")
        self.combo_box_1 = wx.ComboBox(self, wx.ID_ANY, choices=["zip", "tar", "gztar", "bztar", "xztar"], style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.text_ctrl_2 = wx.TextCtrl(self, wx.ID_ANY, "")
        self.button_2 = wx.Button(self, wx.ID_ANY, "...")
        self.button_3 = wx.Button(self, wx.ID_ANY, u"圧縮")
        self.button_4 = wx.Button(self, wx.ID_ANY, u"キャンセル")

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_BUTTON, self.OnChooseTargetDir, self.button_1)
        self.Bind(wx.EVT_BUTTON, self.OnChooseOutputDir, self.button_2)
        self.Bind(wx.EVT_BUTTON, self.OnExec, self.button_3)
        self.Bind(wx.EVT_BUTTON, self.OnCancel, self.button_4)
        # end wxGlade

    def __set_properties(self):
        # begin wxGlade: MyFrame.__set_properties
        self.SetTitle(u"圧縮プログラム")
        self.SetBackgroundColour(wx.Colour(255, 255, 255))
        self.combo_box_1.SetSelection(0)
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MyFrame.__do_layout
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_5 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_4 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
        label_1 = wx.StaticText(self, wx.ID_ANY, u"圧縮したいフォルダ:")
        sizer_2.Add(label_1, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_2.Add(self.text_ctrl_1, 2, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_2.Add(self.button_1, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_1.Add(sizer_2, 1, wx.EXPAND, 0)
        label_2 = wx.StaticText(self, wx.ID_ANY, u"圧縮フォーマット:")
        sizer_3.Add(label_2, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_3.Add(self.combo_box_1, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_1.Add(sizer_3, 1, wx.EXPAND, 0)
        label_3 = wx.StaticText(self, wx.ID_ANY, u"出力先フォルダ:")
        sizer_4.Add(label_3, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_4.Add(self.text_ctrl_2, 2, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_4.Add(self.button_2, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_1.Add(sizer_4, 1, wx.EXPAND, 0)
        sizer_5.Add((20, 20), 2, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_5.Add(self.button_3, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_5.Add(self.button_4, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        sizer_1.Add(sizer_5, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_1)
        self.Layout()
        # end wxGlade

    def OnChooseTargetDir(self, event):  # wxGlade: MyFrame.<event_handler>
        print("Event handler 'OnChooseTargetDir' not implemented!")
        event.Skip()
    def OnChooseOutputDir(self, event):  # wxGlade: MyFrame.<event_handler>
        print("Event handler 'OnChooseOutputDir' not implemented!")
        event.Skip()
    def OnExec(self, event):  # wxGlade: MyFrame.<event_handler>
        print("Event handler 'OnExec' not implemented!")
        event.Skip()
    def OnCancel(self, event):  # wxGlade: MyFrame.<event_handler>
        print("Event handler 'OnCancel' not implemented!")
        event.Skip()
# end of class MyFrame

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

# end of class MyApp

if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

生成されたPythonファイルを実行すると以下のようにGUIが表示されます。

生成されたPythonファイルを実行

生成されたPythonファイルを実行

試しにGUI上のボタンを押してみてください。 ターミナルに以下のようなメッセージが出力されるはずです。

Event handler 'OnChooseTargetDir' not implemented!
Event handler 'OnChooseOutputDir' not implemented!
Event handler 'OnExec' not implemented!
Event handler 'OnCancel' not implemented!

イベントハンドラを実装していない旨のメッセージが出力されています。
次項からこれらのイベントハンドラを実装していきましょう。

ビジネスロジック部分の作成

「圧縮」ボタンを押したら実際に圧縮処理を行いたいですよね。
そこで、ボタンをクリックしたときに実行すべき処理をイベントハンドラに記述しましょう。
前項に載せたソースコードを元に説明しますので、適宜参照してください。また、できるだけシンプルにするために細かいところの処理は省略しており、ユーザビリティに乏しくなっていますがご了承くださいね。

フォルダ選択ボタン

フォルダ選択ボタンをクリックしたらフォルダ選択ダイアログが開くようにしましょう。
フォルダ選択ボタンにはOnChooseTargetDir(と出力先フォルダ用にOnChooseOutputDir)というイベントハンドラを設定しましたね。この関数に処理を実装していきます。

ありがたいことにwxPythonではフォルダ選択ダイアログを表示するクラス(wx.DirDialog)があります。これを使用して実装したものが以下です。

    def OnChooseTargetDir(self, event):  # wxGlade: MyFrame.<event_handler>
            pathname = self.showDirDialog()
            self.text_ctrl_1.SetValue(pathname)

    def OnChooseOutputDir(self, event):  # wxGlade: MyFrame.<event_handler>
            pathname = self.showDirDialog()
            self.text_ctrl_2.SetValue(pathname)

    def showDirDialog(self):
        with wx.DirDialog(self, 'フォルダを選択してください',
                          style=wx.DD_DEFAULT_STYLE
                                | wx.DD_DIR_MUST_EXIST
                                | wx.DD_CHANGE_DIR
                          ) as dialog:
            if dialog.ShowModal() == wx.ID_CANCEL:
                return
            return dialog.GetPath()

showDirDialogというフォルダ選択のための関数を作り、それを使ってフォルダパスを取得します。取得したパスはself.text_ctrl_1.SetValue(pathname)でテキストボックスに入力しています。
上記コードを実装したら、再度プログラムを実行してフォルダ選択ボタンをクリックしてみてください。フォルダ選択ダイアログが開くはずです。フォルダを選択すると隣のテキストボックスにフォルダのパスが入力されます。

フォルダ選択ダイアログ

フォルダ選択ダイアログ

これでOnChooseTargetDirOnChooseOutputDirが実装できました。

キャンセルボタン

キャンセルボタンを押したらアプリケーションが終了するようにしましょう。

    def OnCancel(self, event):  # wxGlade: MyFrame.<event_handler>
        self.Destroy()

self.Destroy()でウィンドウが閉じます。

実行ボタン

実行ボタンを押すと、選択したフォルダを圧縮し出力先フォルダに出力するようにしましょう。

import os
from shutil import make_archive

    def OnExec(self, event):  # wxGlade: MyFrame.<event_handler>
        target_dir = self.text_ctrl_1.GetValue()
        fmt = self.combo_box_1.GetValue()
        output_dir = self.text_ctrl_2.GetValue()

        try:
            filepath = os.path.join(output_dir, os.path.basename(target_dir))
            make_archive(filepath, fmt, root_dir=target_dir)
        except Exception:
            dlg = wx.MessageDialog(self, 'エラーが発生しました。入力内容を確認してください。',
                                   'エラー',
                                   wx.OK | wx.ICON_ERROR
                                   )
            dlg.ShowModal()
            dlg.Destroy()
        else:
            dlg = wx.MessageDialog(self, '圧縮処理が正常に終了しました。',
                                   '完了',
                                   wx.OK | wx.ICON_INFORMATION
                                   )
            dlg.ShowModal()
            dlg.Destroy()

画面から入力された値をGetValue()で取得し、それらをmake_archive関数に渡すことで圧縮処理を行います。
圧縮処理については以下のページも参考にしてください。

処理が完了したらwx.MessageDialogを使ってメッセージボックスを表示します。

完了メッセージ

完了メッセージ

これで全てのイベントハンドラの実装が終わりました。つまり私たちのGUIアプリケーションが完成したということです。粗い作りではありますが、GUIアプリケーションとして十分成立しているのではないでしょうか。

終わりに

以上でこの記事は終わりとなります。いかがでしたでしょうか。wxGladeを使うことでレイアウトは比較的簡単に作成できますし、あとはそれにビジネスロジックをくっつけるだけでちょっとしたアプリケーションが作れてしまいます。
wxPythonは他にも色々な機能があるので、気になる方は公式のドキュメントやデモプログラムもチェックしてみてください。



関連記事