pythonのtkinterを使ってユーザー入力を取得する方法

tkinter、wxpython、pyqtあたりが有名どころのようである。 私はシンプルなことがしたいだけで、見た目も割とどうでも良いということでtkinterを使うことにした。 見た目にこだわったり複雑な処理をしたい場合には、後者二つの方が良いらしい。
使う機会があればこれらについても後ほど書くかもしれないがそんな複雑なスクリプトを書く機会には恵まれないんじゃないかなとも思う。 今の所はtkinterでハッピー。

tkinterをimportするときのエラー

最初にtkinterを使うにあたって一つ最初に躓いたことある。 たぶんインストール設定次第だと思うのだけど、matplotlibと一緒にインポートするとクラッシュする。 matplotlibでTkAggを使うように指定することによって回避できた。

import matplotib
matplotlib.use('TkAgg')
from matplitlib import pyplot as plt
from tkinter import *

といった感じで。

tkinterを使ってユーザー入力補助のスクリプト

ここでは適当なスペクトルデータを表示するプログラムを例に。

tkinterでファイルの読み込み

さてtkinterを使ってまずしたかったのは入力データファイルの読み込み。 自分で使うのにはlsなりdirなりで確認してargvsで読み込めば良い。 もしくはインプットファイル名を決めておいて、勝手に読み込ませるとか。
しかし今回プログラムをphdの学生に渡すのにちょっと手間取った。 特に彼女はあまりpythonというかスクリプトを使わないので。 またwindowsユーザーなのでコマンドライン操作にも慣れがない。
そうなるとプログラムを走らせた時にウインドウを開き、ファイルをクリックして開けるようにした方が間違いがないかなと思ったのだ。
前置きが長くなったが、まずはtkinterの必要なものをインポート。 他のインポートは省略。 brewer2mpl以外はありふれたものしかインポートしてないはず。

from tkinter import Tk, ttk
from tkinter import filedialog as tkfd

それからダミーのルートファイルを開いて隠しておく。 これをしておかないと意味のない小窓が開くので煩わしい。

tk = Tk()
tk.withdraw()

続いてaskopenfilenamesを使ってファイルのパスを取得しfilenamesに格納。 ファイルタイプは適当に。 初期ディレクトリは私はファイルと同じ場所にプログラムを持ってくことが多いので./でその場を指定。 もちろんデスクトップなど好きなところで良い。

filenames = tkfd.askopenfilenames(filetypes= [('txt','*.txt')], initialdir='./')

この操作でこの場合だとテキストファイルを複数開くことができる。 このfilenamesはタプルになるので、必要がある場合はリストに変換する。 取得するのはファイルパス。 なのでファイル名などが欲しい場合はosを使ってコンバージョンする。 他の方法もあるかも。
さてファイルパスが取得できたら一度メインウインドウを閉じておく。 これをしないで他の関数で再びメインウインドウを開くと止まってしまうことがある。

tk.destroy()

filedialogは色々あるのでちょうど良いものを使うと良い。

tkinterでユーザー入力の数値の取得

さてファイルを取得したら、そのファイルをどうしたいかなどをユーザーに決めてもらいたい場合がある。 ここではプロットする範囲を取得するという例で。

num1,num2 = get_range('plot')

get_rangeは以下の自己定義関数。 インデントは省略。

def get_range(word):
#インプット窓の動作を設定するクラス。
class Getr:
#クラス変数の初期化。
input1 =0
input2 =0
#コンストラクタでルートウインドウの修飾をする。
def __init__(self,root):
#インプット窓のラベルの設置。 
#場所の指定はgridが便利だと思うのだけど他はどうなんだろう。
label0 = ttk.Label(root,text = 'input')
label0.grid(row=0,column=1)
#インプット窓のラベルと入力欄の設置。
word1 = 'low ' + word + ' limit'
label = ttk.Label(root,text = word1)
label.grid(row=1,column=0)
self.ent = ttk.Entry(root)
self.ent.grid(row=1,column=1)
self.ent.focus_set()
#同じようにもう一つラベルと入力欄の設置。
word2 = 'high ' + word + ' limit'
label2 = ttk.Label(root,text = word2)
label2.grid(row=2,column=0)
self.ent2 = ttk.Entry(root)
self.ent2.grid(row=2,column=1)
self.ent2.focus_set()
#ボタンの設置と、ボタンを押した時の動作の設定。
#lambdaの理由は忘れたけど、これないと動かなかったはず。
but = ttk.Button(root, text='OK',width=10,command = lambda: self.get_quit())
but.grid(row=3,column=0)

#ボタン動作用のメソッドをいくつか。
def gettext(self):
Getr.input1 = self.ent.get()
Getr.input2 = self.ent2.get()
def quit(self):
root.destroy()
def get_quit(self):
self.gettext()
self.quit()
#クラスはここでおしまい。
#インプットルートウインドウ。 ここら辺はテンプレ。
root = Tk()
#メインウインドウでクラスGetrを。
range=Getr(root)
root.mainloop()
#インプットの取得。
return Getr.input1, Getr.input2

さてこうしてユーザー入力で得られる数値とプログラムで使いたいデータの列番号は異なっていたりするので、列番号に直しておく。 ここでargpartitionを使っている理由は早いからだけど、この場合はあまり関係ない。

def conv_line(plot,num):
num = float(num)
dif = np.absolute(plot-num)
mini = np.argpartition(dif,0,axis=0)
return mini[0][0]

こうして得られたライン1からライン2までのプロットを打つ。 ちなみに想定してるのはデータはx軸とy軸の2列データ。

def plot_raw(filenames,num1,num2):
num = len(filenames)
print (num," files read")
if num >2 and num <11:
bmap = brewer2mpl.get_map('spectral','diverging', num)
bmap2 = brewer2mpl.get_map('OrRd','sequential', num)
colors =bmap.mpl_colors
colors2 =bmap2.mpl_colors
for i in range(num):
plot = np.loadtxt(filenames[i])
lin1 = conv_line(plot,num1)
lin2 = conv_line(plot,num2)
plt.plot(plot[lin1:lin2,0],plot[lin1:lin2,1], '-',color=colors[i],mec=[1,0,0],ms=8,lw=0.5, alpha=1)
return

といった感じのスクリプトでした。 あえてpythonでプロットを打つ必要はないのだけど、打てると便利な場合もある。 私はバックグラウンド補正とか強度のキャリブレーションを読み込んだ複数ファイルにまとめてかけて、それからプロットを出力するのに使っている。 というわけでtkinterの一例でした。

関連記事

pythonのまとめ

D