wxPythonで文字やボタンがぼやける時の対処法(高DPI対応)
wxPythonでGUIを作ったらなぜか下の画像のように表示がぼやけてしまったので、その時の対処法をメモしときます。
wxPythonを使ってPythonでGUIアプリケーションを作ってみましょう。
必要なものをインストールしましょう。
まずはwxPythonをインストールします。これがなければ始まりません。
pip install wxpython
続いて、wxGladeというツールをダウンロードします。
これはwxPythonのためのGUIビルダーで、フォームやボタン配置などのレイアウトを作成することができ、さらに作成したレイアウトからPythonのソースを自動生成してくれます。
ソースコードから(パラメーターを細かくいじって)レイアウトを調整するより、視覚的にレイアウトを決められるため便利です。
以下からダウンロードします。
ダウンロードしたフォルダの中にwxglade.pyというファイルがあるはずです。このファイルを実行するとwxGladeが起動します(wxPythonがインストール済みでないと起動できません)。
python wxglade.py
以上で準備完了です。
次項からGUIプログラム作成に入ります。
いよいよプログラム作成に入るのですが、そもそも何を作るのか決めていませんでしたね。
できるだけシンプルなものがよいので、「フォルダ圧縮プログラム」でどうでしょうか? フォルダを指定して「圧縮」ボタンを押すと、(zip形式などで)圧縮されたファイルを出力するプログラムです。
wxGladeを使用してレイアウトを作成しましょう。
今回作成する「フォルダ圧縮プログラム」では以下の要素が必要そうですね。
これらをwxGladeで配置していきます。
レイアウトを作成する基本的な流れとしては、
となります。
まずはメインフレーム(Frame)を配置します。
配置したFrameのプロパティを編集します(別このタイミングである必要はありませんが、説明の都合上このタイミングで行っています)。
ツリーから要素を選択すると、左下にプロパティが表示され編集することができます。
今回は下記のプロパティとしました。
フォルダを選択するためのテキスト入力欄とファイル参照ボタン(押すとフォルダ選択ダイアログが開く)を配置します。
個別の要素を並べる前に、ラベル、テキスト入力欄、ボタンを水平方向に並べるためにBoxSizerを追加します。
Slotが3つありますが、それぞれに部品を割り当てていきます。
まずはラベルを配置します。
追加したStaticTextのプロパティをいじります。
続いて、テキストボックスを配置します。
追加したTextCtrlのプロパティを以下に変更します。
3つ目のSlotにはボタンを配置します。
追加したButtonのプロパティは以下です。
ボタンがクリックされたときの処理(フォルダ選択画面を開く処理)を記述するため、イベントハンドラを登録しています。実際の処理はソースコード内に記述します。
ここまででどのような画面ができているか確認してみましょう。
ツリー上のFrameをクリックするとプロパティに「Show Preview」というボタンがあるので、それをクリックしてください。プレビューが表示されます。
以下のような画面になっているでしょうか?
圧縮フォーマット(zip、tar)などを選択するためのセレクトボックスを配置しましょう。
ComboBoxのプロパティを以下のように編集します。
ここまでで以下のような画面ができました。
出力先のフォルダを選択するための入力欄を配置します。
先ほどもフォルダ選択の入力欄(ラベル、テキストボックス、ボタン)を配置しましたね。手順はそれと同じなので詳細は省略します。
以下のような配置となります。
プロパティも先ほどのものとほぼ同じでよいですが、Buttonのイベントハンドラの設定は下記としておきましょう。
ここまでで以下の画面ができました。
処理を実行するための実行ボタンとプログラムを終了するためのキャンセルボタンを配置します。
BoxSizerを追加しその中にボタンを配置していきます。
Spacerのプロパティを下記に変更します。
続いて、ボタンを配置していきます。何度もやってるので分かりますよね?
プロパティでイベントハンドラを設定するのを忘れないでください。
これで以下のような画面が作れたと思います。
必要な要素は配置できたので、これでレイアウト作成は終わりとしましょう。
今まで編集してきた内容を保存しましょう。[Ctrl]+[s]で拡張子がwxgのファイルとして保存できます。 このwxgファイルはwxGladeで使用するファイル形式です。次にまた同じwxgファイルを編集したければwxGladeで開けば編集できます。
作成したレイアウトを元に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が表示されます。 試しに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)
でテキストボックスに入力しています。
上記コードを実装したら、再度プログラムを実行してフォルダ選択ボタンをクリックしてみてください。フォルダ選択ダイアログが開くはずです。フォルダを選択すると隣のテキストボックスにフォルダのパスが入力されます。
これでOnChooseTargetDirとOnChooseOutputDirが実装できました。
キャンセルボタンを押したらアプリケーションが終了するようにしましょう。
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は他にも色々な機能があるので、気になる方は公式のドキュメントやデモプログラムもチェックしてみてください。
wxPythonでGUIを作ったらなぜか下の画像のように表示がぼやけてしまったので、その時の対処法をメモしときます。
Python Selenium を実行すると ChromeDriver のコンソールウィンドウが表示されてしまいます。 …
Python で Selenium を使ったプログラムを PyInstaller で実行ファイル化する自分用まとめです。 …
Pythonでフォントをサブセット化(使う文字だけを抽出して軽量化)する方法を紹介します。
PythonでフォルダをZIPファイル(.zip)に圧縮する方法です。 また、ZIPファイルを解凍する方法についても記し …
Google ColaboratoryでStable Diffusion web UIを使って画像を生成していると、たま …
PyInstallerを使えば簡単にexeを作成できますが、1つのフォルダに複数のexeを作るにはどうすればよいのか分か …