Pythonバッチにお飾りGUI

Pythonバッチに Tkinter でお飾りGUI

こんな貴方に

日常的に、しょうもない作業を Python にやらせている貴方。「汚れ作業は機械に」を日々実践している。普段は自分さえ使えればいい、と、テキトーな「半自動系ゴミプログラム」で済ませる日々。

そんなある日、「皆にも使わせれば俺の作業も減るじゃねーか、バカヤロー」、と思いましたとさ。

さて、ここで問題がある。自分ひとりで使うなら、コマンドラインだけで済ませてしまうので、イチイチ GUI なんか作らない。が、人に使わせるのにそれはさすがに酷だ。説明するのも鬱陶しいし。

いかに手間をかけずに「お飾り GUI を作れるか?」。これが本日のお題。ただし入力に限る。バッチ作ってるなら、出力はなにがしかファイルとかだろうしね。

もうひとつの前提。

「セキュリティが厳しくて、好き勝手に OSS を持ってくることは厳禁」というマゾヒスティックな環境(かなり一般的)にいるとする。ただし Python だけはなぜか許されている、とする。(多分貴方かほかの誰かが普及活動をした結果だ。)

然るに、出来るだけ標準添付ライブラリの世界を出ないでやりたい。

簡単に複雑な GUI を作れる開発環境なんかこれまで一度として登場していない

GUI プログラミングは昔からダルい。今でもダルい。今後もダルくあり続ける。なぜダルいか。答えは簡単。

  • 逐次実行でないのでプログラミングしながら振る舞いを想像しにくいから
  • デザイン…

どんなに優秀な環境も、この2点だけは突破出来ない。

であるから、サボれる限界があって、その限界を超えた時点で、本気モードに入らないといけない。つまり「行儀の良い GUI プログラミング」に突入しなければならない。

「お気楽~」的なサイトや書籍は多いけれど、別にコーディング量がお気楽なわけじゃねっす。

選択肢はもはや Tkinter しかない

Tkinter が好きだろうとそうでなかろうと、「Tkinter は、Python をインストールすれば、大抵の場合は一緒に付いてくる」。

何かの理由でほかに選択肢がある貴方は、この記事は忘れましょう。

ファイル選択、フォルダ選択

単一ファイル選択

読み込みのための単一ファイルなら、tkFileDialog.askopenfilename。

1 # -*- coding: utf-8 -*-
2 import tkFileDialog
3 filez = tkFileDialog.askopenfilename(title="Choose file")
4 #場合によっては unicode にデコード必要かも
5 #import sys
6 #targets = filez.decode(sys.stdout.encoding)
7 print(filez)

ちょっと気持ち悪いのもいて、読み込みのための単一ファイルを開いて返す、tkFileDialog.askopenfile がいる。

1 # -*- coding: utf-8 -*-
2 import tkFileDialog
3 fi = tkFileDialog.askopenfile(title="Choose file")  # open済みで返る
4 #読み込みモードを指定出来る:
5 #fi = tkFileDialog.askopenfile(mode="rb", title="Choose file")
6 print(fi.read())

書き込み用、いわゆる「Save As」用には、tkFileDialog.asksaveasfilename と tkFileDialog.asksaveasfile。使い方は同じ。

複数ファイル選択

読み込みのための複数ファイルなら、tkFileDialog.askopenfilenames。

1 # -*- coding: utf-8 -*-
2 import tkFileDialog
3 filez = tkFileDialog.askopenfilenames(title="Choose files")
4 #場合によっては unicode にデコード必要かも
5 #import sys
6 #targets = [t.decode(sys.stdout.encoding) for t in filez]
7 for path in filez:
8     print(path)

単一ファイルの場合と同様に、tkFileDialog.askopenfiles がいる。

1 # -*- coding: utf-8 -*-
2 import tkFileDialog
3 filez = tkFileDialog.askopenfiles(title="Choose files")
4 #読み込みモードを指定出来る:
5 #filez = tkFileDialog.askopenfiles(mode="rb", title="Choose files")
6 for fi in filez:
7     print(fi.read())

Save As に相当するものはないが、tkFileDialog.askopenfiles の mode=”w” とかで(嘘つき UI だけど)出来たりするんだろうか? とは言えほとんどこのニーズはないとは思う。(なぜって、順不同で返ってきた「書き込み対象ファイル」を受け取って、一体どうやって正しい振る舞いを書けるというのだ。)

フォルダ選択

tkFileDialog.askdirectory。

1 # -*- coding: utf-8 -*-
2 import tkFileDialog
3 folder = tkFileDialog.askdirectory(title="Choose folder")
4 print(folder)

そういえば、学生時代は UNIX しか使ったことがなく、はじめて Windows に触れた際に「フォルダ」という用語がどうしても馴染めなかったのを思い出した。

整数入力だけ/浮動小数入力だけ/文字列入力だけ

tkSimpleDialogで。

1 # -*- coding: utf-8 -*-
2 import Tkinter
3 import tkSimpleDialog
4 root = Tkinter.Tk()
5 root.withdraw()
6 print(
7     tkSimpleDialog.askinteger(
8         u"askinteger", u"整数をくれい", minvalue=1, maxvalue=10)
9     )

1 # -*- coding: utf-8 -*-
2 import Tkinter
3 import tkSimpleDialog
4 root = Tkinter.Tk()
5 root.withdraw()
6 print(
7     tkSimpleDialog.askfloat(
8         u"askfloat", u"小数でくれい", minvalue=0.1, maxvalue=10.0)
9     )

 1 # -*- coding: utf-8 -*-
 2 import Tkinter
 3 import tkSimpleDialog
 4 root = Tkinter.Tk()
 5 root.withdraw()
 6 print(
 7     tkSimpleDialog.askstring(
 8         u"askstring", u"文字列が好きだぁ")
 9     )
10 print(
11     tkSimpleDialog.askstring(
12         u"askstring", u"みえないなんてやだぁ", show="*")
13     )


テキスト入力(マルチライン)たった一つ

ここから急に鬱陶しくなってくる。すなわちもはや「CommonDialog的」ではなく、一撃では出来ない。つまりは、ここいらあたりからが「本気で取り組む必要がある」かどうかの境目となる。

仕様はこんなだとする:

  1. 「~てください」的なプロンプトはタイトルバーに出ればいい
  2. OK, Cancelボタンだけは付いていて、Cancel かどうかがわかる必要はある
  3. 2項目以上入力させたい場合には何個もダイアログが立ち上がっても気にしない
  4. (Tcl/Tkベースでは付きまとう)rootウィンドウは出ちゃイヤ
  5. 入力チェックなんかいらない
    • バッチに戻ってからやればいい
    • 入力チェックエラーならダイアログ立ち上げなおせばいい
  6. 型なんか気にしない
    • 数値が必要ならバッチに戻ってから変換すればいい

仕様というより「サボりたい、という願望」が含まれている。そういう記事なんです。

以下がこれを実現するもの。

 1 # -*- coding: utf-8 -*-
 2 # 厳重注意:「バッチに付けるお飾りGUI」以外の目的での参考には絶対にしないこと。
 3 # ことごとく(GUIプログラミング的に)マナー違反してます。
 4 class tkTrickeryTextQuery(object):
 5     def __init__(self, title, **kw):
 6         import Tkinter
 7         #import ScrolledText
 8         from Tkinter import E, W, N, S  # for sticky
 9         self.root = Tkinter.Tk()
10         self.root.title(title)
11         self.value = None
12         
13         self.ent = Tkinter.Text(  # Entry -> Text
14             self.root, **kw)
15         # ScrolledText.ScrolledText ならスクロールバーがついたもの。
16 
17         self.ent.grid(row=0, column=0, columnspan=10)
18         btno = Tkinter.Button(
19             self.root, text="OK", command=self._ok)
20         btno.grid(row=1, column=0, sticky=E + W)
21         btnc = Tkinter.Button(
22             self.root, text="Cancel", command=self.root.destroy)
23         btnc.grid(row=1, column=1, sticky=E + W)
24         self.root.mainloop()
25 
26     def __call__(self):
27         return self.value
28         
29     def _ok(self):
30         self.value = self.ent.get('1.0', 'end-1c')  #
31         self.root.destroy()
32 
33 if __name__ == "__main__":
34     print(tkTrickeryTextQuery(
35             u"ほげを入力しなされ", width=100, height=10)())


やや長いが、この程度であれば、たとえ「インターネットから切り離されている」としても、例えばスマホ片手に写経出来なくはないであろう。

真面目に Tkinter で GUI を作りたい場合は真似しないほうがいい。「仕様」に書いた通りであるが、Tkinter.Tk() が主役ではない「バッチプログラムのお飾り GUI」に特化したコードである。具体的には、

1 root = Tkinter.Tk()
2 root.withdraw()

がプログラムのトップレベルにいないのが行儀悪い。ただ、「一個だけ入力が必要なのだ」というお飾りGUI時は、イチイチ汎用考えたくないのだ。

プルダウンたった一つとかも出来るが…

一般的なコントロールがただ一つ、という GUI はおそらくプルダウンまでが限界で、「チェックボックスただ一つ」なんて馬鹿げているし、「ラジオボタンただ一つ」などありえない。リストボックスくらいはありえるが、それが必要な UI なら、きっと本気モードに移行した方がいい。きっと他のコントロール付けたくなるから。となれば、ここがサボれる限界、と思う。

以下の通り:

 1 # -*- coding: utf-8 -*-
 2 # 厳重注意:「バッチに付けるお飾りGUI」以外の目的での参考には絶対にしないこと。
 3 # ことごとく(GUIプログラミング的に)マナー違反してます。
 4 class tkTrickeryPulldownQuery(object):
 5     def __init__(self, title, values, **kw):
 6         import Tkinter
 7         import ttk
 8         from Tkinter import E, W, N, S  # for sticky
 9         self.root = Tkinter.Tk()
10         self.root.title(title)
11         self.value = None
12         
13         self.ent = ttk.Combobox(
14             self.root, **kw)
15         self.ent['values'] = values
16         self.ent.current(0)  # 先頭アイテム選択
17 
18         self.ent.grid(row=0, column=0, columnspan=10)
19         btno = Tkinter.Button(
20             self.root, text="OK", command=self._ok)
21         btno.grid(row=1, column=0, sticky=E + W)
22         btnc = Tkinter.Button(
23             self.root, text="Cancel", command=self.root.destroy)
24         btnc.grid(row=1, column=1, sticky=E + W)
25         self.root.mainloop()
26 
27     def __call__(self):
28         return self.value
29         
30     def _ok(self):
31         self.value = self.ent.get()  #
32         self.root.destroy()
33 
34 if __name__ == "__main__":
35     print(tkTrickeryPulldownQuery(
36             u"選んでね", values=[u'リンゴ', u'みかん', u'ニシキヘビ'], state='readonly')())
37     print(tkTrickeryPulldownQuery(
38             u"選ぶか入力しろよ", values=[u'そうか', 'そうなのか', u'そうでもない'])())


Python 3.xではインターフェイスが違うので注意

Python 2.7 を例にした。Tkinter、ScrolledText はモジュールが変更になっていて、Python 3.x では tkinter、tkinter.scrolledtext。

参考: Google Code に近いものがあるようだ

今回のお題にはそぐわないが、

https://code.google.com/p/pybase/source/browse/pybase/#pybase%2Ftk

なんてのがある。内容から察するに、Python 標準に取り込まれた元。プルダウンについてはもっと複雑なことが一撃で出来るようになっているようだ。

参考: 本気で Tkinter に取り組みたいなら

Python のソース配布物に、Tkinter のサンプルコードが含まれている。Tkinter に限らず色んなサンプル(Demo)が入っているので、入手しておくと良い。

tkColorChooser、tkMessageBoxはいいよね?

ともに良く使うだろうけど、

1 >>> import tkColorChooser
2 >>> help(tkColorChooser)
3 >>> import tkMessageBox
4 >>> help(tkMessageBox)

ですぐにわかると思う。