EasyGuiを用いたPythonスクリプトの簡易GUI化

 Pythonスクリプトにグラフィカルインタフェースを実装する場合の選択肢としては、多少煩雑になるのは覚悟の上でTKinterなどのGUIプログラミングキットを用いるのが一般的だろう。ただしこの手法を実践するにはコーディングに関するかなり高度なスキルが求められるため、初心者レベルのPythonプログラマにとっては敷居が高いというのが実状である。こうした面倒なコーディングを経ることなく、Pythonスクリプトへの簡易的なGUI機能の組み込みを可能にしてくれるのが、本稿で紹介する EasyGui というモジュールだ。実際EasyGuiを利用した場合、数行のコードを追加するだけでPythonスクリプトをグラフィカル化できてしまうのである。

 EasyGuiの能力を実感する最善の方法は、これを用いて手元にある既存のPythonスクリプトを改良してみることだ。本稿ではそのためのベースとして、メモ類、URLの控え、行動予定リストなどのパーソナルデータ管理用に私が作成したPygmynoteという簡易管理ツールを使用することにする。オリジナル状態のPygmynoteでもそれほど使い勝手が悪い訳ではないのだが、ここではレコード抽出操作を簡単化するために、いくつかのテキスト入力ボックスとダイアログを追加してみることにしよう。

 スクリプトの改良作業に着手するにあたっては、事前にEasyGuiをインストールしておかなくてはならない。まずは同モジュールの最新リリースをダウンロードし、アーカイブを展開して得られるeasygui.pyファイルを/usr/lib/python2.5/site-packagesディレクトリに移動しておく。そしてこのモジュールを利用するスクリプトについては、その先頭部に「from easygui import *」というインポート行を追加しておかなくてはならない。以上の準備が完了すると、EasyGuiを用いたスクリプトの改造が行えるようになっているはずだ。

 まずは手始めに“Pygmynote is ready. Pile up!”というユーザ歓迎用のメッセージ部を、もう少し見栄えのするメッセージボックスに置き換えてみよう。こうした機能を追加してくれるのがEasyGuiのmsgbox関数であるが、この関数を使用する最低限のパラメータ指定としては、下記のサンプルコードのようにメッセージ表示用のテキストだけを指定しておけばいい。

msgbox("Pygmynote is ready.")

 その他にもこの関数では、メッセージボックスのタイトル、ボタンのラベル(デフォルト値は“OK”)、イメージ表示用の画像という3つのパラメータを指定することができる。例えばメッセージボックスに彩りを加えたければ、最終パラメータを介して適切なgifイメージを表示させればいいはずだ。下記のサンプルコードでは、msgbox関数にて使用可能なすべてのパラメータを指定している。

image = "pygmynote.gif"
msgbox("Pygmynote is ready.", "Pygmynote", ok_button="Pile up!", image=image)

 次に、Pygmynote操作用のコマンド群を扱いやすくするため、使用可能なコマンドを一覧したメニューを表示させてみよう。このメニューを追加した場合のコマンド指定は、リスト上での項目選択後にOKをクリックするか、目的のコマンドを直接ダブルクリックすることにて行えるようになるのだ。こうした処理を可能にしてくれるのがEasyGuiに用意されたchoicebox関数であり、ここでも先のmsgbox関数と同様のパラメータを指定可能だが、この場合に指定すべき内容は、メッセージ用のテキスト、ウィンドウのタイトル、選択項目のリストという3つである。

msg ="What's your favorite fruit?"
title = "Fruity"
choices = ["Apple", "Apricot", "Pineapple"]
choice = choicebox(msg, title, choices)

 このメニューの実装後、リストに一覧された中から目的のコマンドを選択してOKをクリックする(あるいは当該コマンドを直接ダブルクリックする)と、関数の戻り値として選択された項目名が返される。ただし先に述べた用途にこのchoicebox関数を使うには、Pygmynoteというスクリプトにて使用可能なコマンドを一覧したリストを、下記のような形式にてパラメータ指定しなくてはならない。

msg ="Select command and press OK"
title = "Pygmynote"
choices = ["Help", "Insert new record", "Show all records", "Quit"]
command = choicebox(msg, title, choices)

 またその後実行する条件分岐のブロックではcommand=="n"command=="Insert new record"となるなど、choiceboxに渡した項目名とコマンド名とを一致させるための置き換えを施さなくてはならない。その他、このスクリプトを現状のままで実行するとリスト上の項目はアルファベット順でソートされるはずだが、この種の並び順については“Insert new recordは最初の項目にしたい”などの要望が出てくることだろう。実のところEasyGuiはソート順のカスタマイズ機能を実装していないのだが、その単純な回避策としては“1 – Insert new record”や“X – Quit”のように、項目名の先頭に強制ソート用の記号を付けておけばいいはずだ。

 次に手を付けるのは、検出されたレコードを表示するスクリプト部である。例えば既存レコードの全表示は、下記のコードブロックにて処理している。

elif command=="a":
  cursor.execute ("SELECT * FROM notes ORDER BY id ASC")
  rows = cursor.fetchall ()
  for row in rows:
      print "\n   %s %s [%s]" % (row[0], row[1], row[2])
  print "\n   Number of records: %d" % cursor.rowcount

 ここでは、こうしたレコード一覧に対する改良として、textbox関数を用いたテキストボックス形式での表示をさせてみよう。ただしここのコードブロックに関しては、検出されたレコード群を1つの文字列にまとめるための変更を追加しておかなくてはならない。そうした処理の実装にあたっては、result_list = []ステートメントにより空のリストを作成しておき、該当するレコードを.appendメソッドによりこのリストへ追加していけばいい。こうして取得した文字列は、下記のようにパラメータの1つとしてtextbox関数に渡すことになる。

elif command=="Show all records":
  cursor.execute ("SELECT * FROM notes ORDER BY id ASC")
  rows = cursor.fetchall ()
  result_list = []
  for row in rows:
      record_str = "\n%s %s [%s]" % (row[0], row[1], row[2])
      result_list.append (record_str)
  textbox ("Found records:", "Pygmynote", result_list)

 スクリプトの実行途中でユーザの指定値に応じた制御を施すといった場合は、EasyGuiのboolbox関数が便利である。例えばPygmynoteのスクリプトではレコードの1つをアップデートする際に、ユーザに対して当該レコードのノートとタグのどちらをアップデートするかの確認が行われるようになっており、該当するレコード中のフィールドが実際にアップデートされるのは、こうした確認を経た後の処理とされている。下記のコードブロックは、このような処理をするオリジナルのスクリプト部である。

elif command=="u":
  input_id=raw_input("Record id: ")
  input_type=raw_input("Update note (n) or tags (t): ")
  if input_type=="n":
      input_update=raw_input("Note: ")
      sqlstr=escapechar(input_update)
      cursor.execute ("UPDATE notes SET note='" + sqlstr + "' WHERE id='" + input_id + "'""")
  else:
      input_update=raw_input("Tags: ")
      sqlstr=escapechar(input_update)
      cursor.execute ("UPDATE notes SET tags='" + sqlstr + "' WHERE id='" + input_id + "'""")
  print "\nRecord has been updated."

 boolbox関数を実行すると2つのボタンを配置したメッセージボックスが表示され、このうちユーザが1番目のボタンをクリックすると関数の戻り値として1が返され、それ以外の場合は0が返されるという仕様になっている。これが実際にどのような操作となるかは、下記のような簡単なサンプルコードを試験的に実行してみればいいだろう。

if boolbox("What do monkeys like most?", "Pygmynote", ["Bread", "Bananas"]):
  msgbox ("Well, not really.")
else:
  msgbox ("Yep, that's what they like most.")

 ここでの使用例としては、boolbox関数を介して“Note”と“Tags”のボタンを表示させ、ユーザの選択に応じた処理をスクリプトに実行させるようにしてみた。

elif command=="Update record":
  input_id=enterbox(msg='Record ID: ', title='Pygmynote', default='', strip=True)
  if boolbox("What do you want to update?", "Pygmynote", ["Note", "Tags"]):
    input_update=enterbox(msg='Enter note: ', title='Pygmynote', default='', strip=True)
    sqlstr=escapechar(input_update)
    cursor.execute ("UPDATE notes SET note='" + sqlstr + "' WHERE id='" + input_id + "'""")
  else:
    input_update=enterbox(msg='Enter tags: ', title='Pygmynote', default='', strip=True)
    sqlstr=escapechar(input_update)
    cursor.execute ("UPDATE notes SET tags='" + sqlstr + "' WHERE id='" + input_id + "'""")
  msgbox ("Record has been updated.", "Pygmynote", ok_button="Close")

 次に利用するintegerbox関数では、その名が示すとおりに、数値のみを受け付ける入力ボックスが表示される。この入力値については上限および下限値も指定できるので、ここではPygmynoteにおける使用例として、下記のようなカレンダ表示のスクリプト部にて対象月を指定する1~12の整数値をユーザに入力させるようにしてみた。

inputmonth=integerbox(msg='"Month (1-12): "', title='Pygmynotes', default='', argLowerBound=1, argUpperBound=12)

 EasyGuiには、ディレクトリ選択ダイアログボックスを利用するためのdiropenbox関数も用意されている。Pygmynoteでの使用例としては、ユーザが記録した全レコードをpygmynote.txtというテキストファイルに書き出すスクリプト部にて、この関数が役立つはずだ。オリジナルのコードにおける同ファイルの保存先はスクリプトの格納ディレクトリとされているが、下記のようにdiropenbox関数を組み込むだけで、任意のディレクトリをユーザにより選択可能にできてしまう。

elif command=="Save all records as pygmynote.txt":
  cursor.execute ("SELECT * FROM notes ORDER BY id ASC")
  rows = cursor.fetchall ()
  filedir = diropenbox(msg="Select directory", title="Pygmynote", default=None)
  filename = filedir + os.sep + "pygmynote.txt"
  if os.path.exists(filename):
      os.remove(filename)
  for row in rows:
     file = open(filename, 'a')
     file.write("%s\t%s\t[%s]\n" % (row[0], row[1], row[2]))
     file.close()
  msgbox ("Records have been saved in the pygmynote.txt file.", "Pygmynote", ok_button="Close")

 このようにEasyGuiに用意されている機能はあくまで簡易的なものであり、TKinterやwxPythonなどのより本格的なGUIプログラミングを置き換える存在ではないが、Pythonの初心者ユーザや最小限の手間でスクリプトをユーザフレンドリ化したいというプログラマにとっては充分に役立つはずだ。またこれから実際にEasyGuiを試してみるという場合は、利用可能なすべての関数を解説した「EasyGui Tutorial」ページが参考になるだろう。

Dmitri Popovは、フリーランスのライターとして、ロシア、イギリス、アメリカ、ドイツ、デンマークのコンピュータ雑誌に寄稿している。

Linux.com 原文(2008年9月8日)