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

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

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

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Q&A

解決済

1回答

4024閲覧

動的に生成したチェックボックスのリストから、どれが押されたかを判別したい。

H.K2

総合スコア88

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

0グッド

1クリップ

投稿2019/03/10 07:35

前提・実現したいこと

Python 3.6で、Tkinterをつかって、動的に生成したラベルとチェックボックスから、
どのチェックボックスを押したかを判別したいです。画面イメージ(例)を貼り付けます。
※ボタンは別の用途に使う予定で、チェックボックスを押した時点で、どのチェックボックスがおされたかは、イベントでとるつもりで設置しています。

イメージとしては下記となります。

イメージ説明

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

エラーが出ないように作ることはできたのですが、動的に生成する関係で、チェックボックスを押したときに呼ばれた関数が共通にならざるを得なくなってしまっています。(self.cb_clicked)
そのため、どのチェックボックスを押されても同じ関数がコールされるため、
どのチェックボックスを押されたかが判断できなくなっています。
該当部分のクラス実装を下記に示します。(一部ラベルは、上位のクラスからstrのlistで飛んでくるため
中身は見えなくなっていますが、本質ではないため、省略します。)

ご回答いただきたいこと

下記に関してご回答いただければ幸甚に存じます。
①どのチェックボックスが押されたかを(できればこのクラス内で)判断する方法
(難しければ、ほかのクラスでの判別でもよいです)
②これらのチェックボックスのうち、N個まで同時に押せるが、N+1個にならないように
制御できる方法(今は無理やり「cb_clicked」でやろうとしていますが、思った処理になっていないので悩ましいです。。。N+1個になった場合、直近で押したもの以外で、一番若いチェックを外したいです。)
③(これはもし可能であればでよいのですが)スクロールバーが、右のチェックボックスとラベルのリストにうまい感じに張り付かないです。(以前ここでご質問させていただきました通り、Canvasにスクロールバーを貼り付けると、再描画がうまくいったのですが、チェックボックスとラベルのリストの大きさなどを変えるとCanvasのサイズと会わず、このようにずれてしまいます。もしよい方法をご存知の方がいれば、チェックボックスとラベルのテーブルの大きさが変わっても追従できるようにしたいです。

該当のソースコード

Python

1#  Checkboxを含むLabelのテーブル(スクロールバーの影響を防ぐ変更により、タイトル部分は外に出すことにした。) 2class LabelTableWithCb: 3 def __init__(self, master=None, num_y=3, p_x=0, p_y=0, dir_lbl=None): 4 # self.master = master 5 # place使うときは、フレームサイズを決めておく必要。 6 if dir_lbl is None: 7 dir_lbl = [] 8 9 self.pane = tk.Frame(master=master) 10 # self.pane.configure(width=400, height=600) 11 # self.pane.place(x=p_x, y=p_y) 12 self.pane.pack(side=LEFT) 13 14 # canvas, scrollbar設定 15 # self.canvas = tk.Canvas(master=self.pane, bg='blue') 16 self.canvas = tk.Canvas(master=self.pane) 17 self.canvas.propagate(False) # ウィジェットの大きさに関係なくフレームサイズ決めたいときに必要 18 self.tbl_frame = tk.Frame(self.canvas, bg="red") 19 self.my_scrollbar = tk.Scrollbar(self.pane, orient="vertical", command=self.canvas.yview) 20 21 self.cb = [] 22 self.cb_val = [] 23 24 # タイトル部分 25 title_lst = ["label", "cb"] 26 col_span = len(title_lst) 27 for x in range(col_span): 28 tk.Label(master=self.tbl_frame, text=title_lst[x], bd=2, relief="ridge" 29 ).grid(column=x, row=0, sticky=tk.NSEW) 30 # tk.Label(master=self.pane, text=title_lst[x], bd=2, relief="ridge" 31 # ).grid(column=x, row=0, sticky=tk.NSEW) 32 33 self.canvas.configure(yscrollcommand=self.my_scrollbar.set) 34 # self.canvas.grid(column=0, row=1, columnspan=col_span, sticky=tk.NSEW) 35 self.my_scrollbar.pack(side="right", fill="y") 36 self.canvas.pack(side="top") 37 self.canvas.create_window((0, 0), window=self.tbl_frame, anchor='nw') 38 self.tbl_frame.bind("<Configure>", self.update_frame) 39 40 for y in range(1, num_y): 41 tk.Label(master=self.tbl_frame, text=dir_lbl[y - 1], bd=2, 42 relief="ridge").grid(column=0, row=y, sticky=tk.NSEW) 43 # チェックボックスの列 44 self.cb_val.append(tk.BooleanVar()) 45 self.cb_val[y - 1].set(False) 46 self.cb.append(tk.Checkbutton(master=self.tbl_frame, variable=self.cb_val[y - 1], bd=2, relief="ridge", 47 command=self.cb_clicked)) 48 # self.cb.append(tk.Checkbutton(master=self.tbl_frame, variable=self.cb_val[y - 1], bd=2, relief="ridge")) 49 # self.cb[y - 1].bind("<Button-1>", self.cb_clicked) 50 self.cb[y - 1].grid(column=1, row=y, sticky=tk.NSEW) 51 52 self.create_widget() 53 54 def create_widget(self): 55 self.canvas.configure(yscrollcommand=self.my_scrollbar.set) 56 self.my_scrollbar.pack(side="right", fill="y") 57 self.canvas.pack(side="top") 58 self.canvas.create_window((0, 0), window=self.tbl_frame, anchor='nw') 59 self.tbl_frame.bind("<Configure>", self.update_frame) 60 61 def update_frame(self, event): 62 self.canvas.configure(scrollregion=self.canvas.bbox("all"), width=105, height=200) 63 pass 64 65 def get_cb_val(self, cb_id): 66 if self.cb_val[cb_id] is not None: 67 return self.cb_val[cb_id].get() 68 else: 69 pass 70 71 # チェックボックス押下時処理。チェックの値は、self.cb_valのlistに格納されている。 72 def cb_clicked(self): 73 # print('チェックボックス押下') 74 cnt = 0 75 for k, cb in enumerate(self.cb_val): 76 # print("{},".format(cb.get())) 77 if cb.get() is True: 78 cnt += 1 79 80 # チェックが2こ目になった時点で抜け。(本当は押したcbのIDが取れればよいが、簡単には分からないので暫定処置) 81 if cnt >= 2: 82 for cb2 in self.cb_val: 83 cb2.set(False) 84 cb.set(True) 85 break 86 87 # 確認用 88 for k, cb in enumerate(self.cb_val): 89 print("cb_id:{}, val:{},".format(k, cb.get())) 90

試したこと

QiitaやTkinter本家のページなどを見て実装方法を確認した

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

Python 3.6.6

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

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

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

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

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

guest

回答1

0

ベストアンサー

Checkbutton()commandパラメータに直接cb_clicked()を呼ぶのではなく lambda を使って

Python

1self.cb.append(tk.Checkbutton(master=self.tbl_frame, 2 variable=self.cb_val[y - 1], 3 bd=2, 4 relief="ridge", 5 command=lambda n=y: self.cb_clicked(n)))

のようにすることで、 cb_clicked() の引数に

Python

1 def cb_clicked(self, num): 2 ...

押されたCheckBoxの番号を渡すことができるかと思います。

投稿2019/03/11 11:46

magichan

総合スコア15898

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

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

H.K2

2019/03/11 12:30

ご回答ありがとうございます。 ①については上記で実装できました。自分で調べてたところ、クロージャ(高階関数)でも実装ができるようなのですが、lambda式と比べるとどっちが良いのでしょうか。
magichan

2019/03/12 00:02

確かにクロージャでも実現できますね。こんな感じかな? def command(n): __def fnc(): ____self.cb_clicked(n) return fnc tk.Checkbutton(...,command=command(y)) まあ、要は関数を呼んだときの引数の値を束縛できれば良いだけなので、『どちらでも好きなほうを使うと良い』のかと思います。 あと私の回答の方法ですが、キモとなるのは "lambda式を使うこと" なのではなく、"デフォルト引数を使うこと"となります。一応 lambda式を使わなくても記述可能です。
H.K2

2019/03/13 15:53

なるほど。ご回答ありがとうございます。勉強になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問