質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

1917閲覧

tkinterでの複数ファイルパスの取得

blacktt_red

総合スコア11

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2020/04/21 14:49

前提・実現したいこと

tkinterにて複数のButton(110)とEntry(110)があるなかで、
Buttonを押すとButtonのナンバーと同じEntryナンバーに選択した
ファイルパスを入力したい。

発生している問題・エラーメッセージ

Button(1)を押し、ファイルを選択すると最後のEntryに
ファイルパスが入力される。

該当のソースコード

Python

1import cv2 2import numpy as np 3import tkinter as tk 4import tkinter.filedialog as filedialog 5 6class Application(tk.Frame): 7 def __init__(self, master): 8 super().__init__(master) 9 self.place() 10 master.geometry("500x1000") 11 master.title("open cv") 12 self.create_widgets() 13 14 15 def create_widgets(self): 16 for i in range(10): 17 button_list = tk.Button(self.master, text="参照"+str(i)) 18 button_list.bind("<1>", self.filename) 19 button_list.place(x=0, y=50*i) 20 self.entry_list = tk.Entry(self.master) 21 self.entry_list.place(x=100, y=50*i) 22 23 def filename(self, event): 24 test = r"C:" 25 file_path = filedialog.askopenfilename(initialdir=test) 26 self.entry_list.insert(tk.END, file_path) 27 28 29def main(): 30 win = tk.Tk() 31 app = Application(master=win) 32 app.mainloop() 33 34 35if __name__=="__main__": 36 main()

試したこと

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

問題の原因

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/21 20:47

teamikl

総合スコア8760

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

blacktt_red

2020/04/22 05:21

回答頂きありがとうございます。 Python初学者のため複数の解決策とそのロジックを説明頂き理解が深まりました。 また、保守の面からコードの書き方に対する御指南頂き大変参考になりました。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問