問題の原因
filename
でファイルを選択した時に self.entry_list へ入力していますが
self.entry_list は、
python
1# create_widgets()
2
3for i in range(10):
4 # 最後の Entry が格納されます
5 self.entry_list = tk.Entry(self.master)
これを回避するためには、Entry をリストに入れるように変更します。
また、ボタンクリック時のイベントには何番目かを伝える必要が出てきます。
解決策として、以下の3点について説明します
- A. リストで管理する方法
- B. イベントのコールバック関数に他の引数を渡す方法
- C. 複合ウィジェットの作成
リストで管理する方法
python
1
2# ファイル先頭で標準ライブラリよりインポート
3
4from functools import partial
5
6
7# class Application 内変更点
8
9 def create_widgets(self):
10 self.entry_list = []
11 for i in range(10):
12 button = tk.Button(self.master, text="参照"+str(i))
13 button.bind("<1>", partial(self.filename, idx=i))
14 button.place(x=0, y=50*i)
15 entry = tk.Entry(self.master)
16 entry.place(x=100, y=50*i)
17 self.entry_list.append(entry)
18
19 def filename(self, event, idx=None):
20 test = r"C:"
21 file_path = filedialog.askopenfilename(initialdir=test)
22 self.entry_list[idx].insert(tk.END, file_path)
- Entry を リストに追加します。
- ボタンクリック時には何番目の Entry なのかを伝える必要があるので、
filenameメソッドに引数を追加します。
- そのままでは filename(event) が呼ばれ引数不一致になるので、
functools.partial を使い、呼び出し時に引数が渡るようにします。
- button_list は、リスト管理不要な為、変数名も button に変更しました。
イベントのコールバック関数に他の引数を渡す方法
- bind で登録した関数には 引数
event
が一個のみ渡され呼び出されます
- 追加の引数を渡す為には、登録時に引数を束縛する方法が取られます。
※ ボタンクリック時のイベントを、
functools.partial を使わずにlambdaで記述するなら、
lambda event: self.filename(event, i)
となります。
コードは差分のみですが、ここ迄で問題点は解消できます。
以下は改善案です。
応用で、idxの代わりに、入力対象のEntryを直接渡す事もできます。
python
1
2 def create_widgets(self):
3 for i in range(10):
4 entry = tk.Entry(self.master, name="entry{}".format(i))
5 entry.place(x=100, y=50*i)
6 button = tk.Button(self.master, text="参照"+str(i))
7 button.bind("<1>", partial(self.filename, entry=entry))
8 button.place(x=0, y=50*i)
9
10 def filename(self, event, entry=None):
11 test = r"C:"
12 file_path = filedialog.askopenfilename(initialdir=test)
13 entry.insert(tk.END, file_path)
14
15 def getEntry(self, idx):
16 return self.nametowidget(".entry{}".format(idx))
- Entry 生成時に name を付ける所がポイント
- Entry オブジェクトが必要な場合は、
nametowidget(".entry9") で 後から問い合わせ可能 (getEntry)
改善点:
- コードにリスト操作が混ざらない為、ロジックが簡潔に見えます
複合ウィジェットを作成する方法
より保守しやすいコードにする為の方法です。余談程度に。
Entry と Button のペアを切り出して別のクラスにします。
このような複数の部品から構成されるウィジェットは、
(tkinterの場合) Frame を継承したサブクラスにします。
python
1
2class FileEntry(tk.Frame):
3 def __init__(self, master, text="参照", *args, **kw):
4 super().__init__(master, *args, **kw)
5 button = self.button = tk.Button(self, text=text)
6 button.bind("<1>", self.clicked)
7 button.pack(fill=tk.BOTH, expand=0, side=tk.LEFT)
8 entry = self.entry = tk.Entry(self)
9 entry.pack(fill=tk.BOTH, expand=1, side=tk.RIGHT)
10
11 def clicked(self, event):
12 test = r"C:"
13 file_path = filedialog.askopenfilename(initialdir=test)
14 self.entry.insert(tk.END, file_path)
15
16
17class Application(tk.Frame):
18 def __init__(self, master):
19 super().__init__(master)
20 self.pack(fill=tk.BOTH, expand=1) # <- 変更点
21 master.geometry("500x1000")
22 master.title("open cv")
23 self.create_widgets()
24
25 def create_widgets(self):
26 self.entry_list = []
27 for i in range(10):
28 entry = FileEntry(self, text="参照{}".format(i))
29 entry.grid(column=0, row=i)
30 self.entry_list.append(entry)
31 self.grid_rowconfigure(i, minsize=50)
- インスタンス変数経由で entry にアクセスするので、
コールバックに追加の引数を渡す為の、
lambda や functools.partial といった一見判り辛いコードを省けます。
- 配置位置計算の *i を省ける。(これは place以外のレイアウトを使う事でも可能)
※ 上記コードはレイアウトを大幅に変えてしまったので参考程度にして下さい。
place() で固定位置に配置する必要がある場合には、向いてないかもしれません。
- 同じような部品が必要になった時、このクラスをインポートすることで
別ファイルからも再利用可能になります。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/04/22 05:21