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

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

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

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

Python

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

Q&A

解決済

1回答

2286閲覧

【ttk.Combobox】マウススクロールしてもリストの値を動かさないようにしたい【Python】

netz-eng

総合スコア105

Tkinter

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

Python

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

0グッド

0クリップ

投稿2022/05/23 14:44

編集2022/05/24 11:36

実現したいこと・仕様

独学でtkinterのGUIを学んでおります。

ttk.Comboboxの仕様として、ウィジェット上でマウススクロールをすると、以下のような動作をします。

リストを表示していないとき
スクロール距離に応じて、Comboboxに設定されたリスト内の値を移動(?)し、Entry部に表示する
リストを表示しているとき
リストがスクロールされ、Entry部の表示は変わらない

********************************
変更したい内容

リストを表示していないとき
ウィジェット上でスクロールしても、Combobox内の値は変更しない

********************************

解決法をご教示願いたいです。

該当のソースコード

Python

1import tkinter as tk 2import tkinter.ttk as ttk 3 4app = tk.Tk() 5 6lst_cb = list(range(20)) 7cb = ttk.Combobox(app, values=lst_cb) 8cb.pack() 9 10app.mainloop()

試したこと

マウススクロールをbindして、無意味な処理(現在の値を取得して、代入)をさせてみましたが、効果はありませんでした。
マウススクロール関数のあとに、勝手に値が書き換えられてしまいます。

Python

1def MouseScroll(event): 2 s = event.widget.get() 3 event.widget.set(s)

https://projetos.prime.cv/gds/python/env_gds/tcl/tk8.6/ttk/combobox.tcl

こちらに、tcl言語でComboboxウィジェットにマウススクロールをbindしている記載があります。
これを無効化できればと思うのですが、書き方がわかりません。

また、リストの表示有無でマウススクロールbindの有無を切り替えられるかも不明です。

試したこと2

return "break"をマウススクロール関数の最後に入れましたが、マウススクロールに伴ってEntry部の表示は変更されてしまいました。

Python

1def onScroll(event): 2 return "break" 3 4cb.bind_all("<MouseWheel>", onScroll)

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

Windows11
Python 3.9.7
tk 8.6.11

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

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

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

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

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

guest

回答1

0

ベストアンサー

イベントの解除自体は unbind や unbind_class メソッドで解除可能ですが、

リスト表示の有無での挙動を実装するのは複雑で、まず
ttk.Combobox は 複数のウィジェットが組み合わさった実装で、
入力部分(Entry)と選択部分のリスト(Listbox)は、内部で別ウィジェットとなっています。

選択部分のリストとスクロールバーはCombobox 生成時には生成されておらず、
ユーザが選択操作した初回に動的に生成されます。→生成時に イベントを bind 出来ない

代替手段として考えられるのは、bind all で 全てのMouseWheelイベントを捕まえて
ウィジェットのIDで判別する方法

コンボボックス の ID が .!combobox の時、各内部ウィジェットのIDは
.!combobox.popdown ツールウィンドウ(Toplevel)
.!combobox.popdown.f.l ポップダウンリスト
.!combobox.popdown.f.sb リストのスクロールバー

マウススクロール関数のあとに、勝手に値が書き換えられてしまいます。

tkinter の(文書化されてない)仕様で、イベントハンドラの関数で return "break" と
文字列 "break" を返す事で以降のイベントをキャンセル出来ます。

リストの表示有無でマウススクロールbindの有無を切り替えられるかも不明です。

可能ではありますが、リスト表示の判別は
Tcl/Tk/Ttkの内部実装のソースコードを読み解く必要があります。

実装のヒント:

  • winfo_exists でリストが生成されているかどうかを確認して、
  • winfo_viewable で表示非表示の確認

python

1 2def onMouseWheel(event): 3 widgetID = str(event.widget) 4 5 if widgetID.startswith(f"{cb}"): 6 if int(app.tk.eval(f"winfo exists {widgetID}.popdown"): 7 if int(app.tk.eval(f"winfo viewable {widgetID}.popdown"): 8 ... 9 10 11app.bind_all("<MouseWheel>", onMouseWheel) 12

リストを表示していないとき
ウィジェット上でスクロールしても、Combobox内の値は変更しない

クラス毎挙動を変更する (全てのttk.Comboboxが対象)

python

1app.unbind_class("TCombobox", "<MouseWheel>")

もしくはインスタンス毎の挙動変更 (変数cbのみが対象)

python

1cb.bind("<MouseWheel>", lambda _: "break")

追記: ttk.Combobox スクロールバーのマウスホィール対応

python

1 2import tkinter as tk 3from tkinter import ttk 4 5root = tk.Tk() 6combo = ttk.Combobox(root, values=list(range(20))) 7combo.pack() 8 9def onMouseWheel(event): 10 widgetID = str(event.widget) 11 12 if widgetID.endswith(".f.sb"): 13 listbox = widgetID.replace(".f.sb", ".f.l") 14 delta = -event.delta//120 # NOTE: Windows only 15 16 root.tk.eval(f""" 17 {listbox} yview scroll {delta} units 18 """) 19 20root.bind_all("<MouseWheel>", onMouseWheel) 21root.mainloop() 22

bind_all でグローバルの MouseWheel イベントの捕捉ではなく、
「f.sb」に bind する実装方法、差分のみ (Tcl言語 / win用)

python

1 2 root.tk.eval(f""" 3 ttk::combobox::Post {combo} 4 ttk::combobox::Unpost {combo} 5 6 if {{[winfo exists {combo}.popdown.f]}} {{ 7 bind {combo}.popdown.f.sb <MouseWheel> {{ 8 {combo}.popdown.f.l yview scroll [expr {{-(%D/120)}}] units 9 }} 10 }} 11 """) 12

{{ }} となっているのはPythonのf-stringでのエスケープです。
tcl 言語では単一の括弧ですが、f-string での変数の展開と被る為のエスケープ。

投稿2022/05/23 22:17

編集2022/05/25 13:54
teamikl

総合スコア8664

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

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

netz-eng

2022/05/24 10:50

早速のご回答、ありがとうございます。 >選択部分のリストとスクロールバーはCombobox 生成時には生成されておらず、 >tkinter の(文書化されてない)仕様で、イベントハンドラの関数で return "break" と文字列 "break" を返す事で以降のイベントをキャンセル出来ます なんと、そんな仕様があるんですね…… 教えていただいた内容を噛み砕きながら、試行錯誤してみます。 取り急ぎお礼まで。
netz-eng

2022/05/24 12:10

思いのほか、あっさりと解決することができました。 質問文には書いておりませんでしたが、ウィジェット上でスクロールした際、Entry値は変更することなく、別の処理のみ行いたかったので、以下のように書きました。 ①一旦コンボボックス全体からマウススクロールをunbindしたのち、必要な処理を行う関数をbindする app.unbind_class("TCombobox", "<MouseWheel>") cb.bind_all("<MouseWheel>", onScroll) ②onScroll関数内で、popdownの存在判定 def onScroll(event): w = str(event.widget) if not "popdown" in w: # 行いたい処理 合わせて、問題の主旨とは異なる質問になるのですが、お尋ねしたいことがあります。 ポップダウンが表示されているとき、ポップダウン右端のスクロールバー(.!combobox.popdown.f.sb)上でマウススクロールをしたときも、ポップダウン(.f.l)リストをスクロールするようにしたいのですが、 この場合は「f.sb」に何をbindすればいいのでしょうか?
teamikl

2022/05/25 04:04 編集

> 「f.sb」に何をbindすればいいのでしょうか? 動的に生成される「f.sb」 に対しては、bind を仕掛けるタイミングの検出が難しいので、 <MouseWheel> イベント全てを bind_all して スクロールバーを移動します。 但し、ttk.Combobox の内部ウィジェットの為、tkinter からのアクセスが難しく、 tk.eval 関数で tcl のコマンドを直接やりとりする事になるかもしれません。(未確認) tcl 側で実装する際のヒント ソースコード内の ttk::combobox::Scroll がマウスホィールに登録されたイベントハンドラですが、 想定する引数が異なる為、この関数をスクロールバーにbindは出来ません。 ソースコード該当箇所 ttk::bindMouseWheel TCombobox [list ttk::combobox::Scroll %W] https://projetos.prime.cv/gds/python/env_gds/tcl/tk8.6/ttk/utils.tcl ttk::bindMouseWheel https://projetos.prime.cv/gds/python/env_gds/tcl/tk8.6/ttk/combobox.tcl bind $bindtag <MouseWheel> [append callback { [expr {-(%D/120)}]}] 他の言語で言う構文木を操作するマクロの様なもので、 tcl のソースコードはリストで表現でき、 bind $bindtag <MouseWheel> [append callback { [expr {-(%D/120)}]}] ※ append は list に対する要素の追加 win32 の場合 MouseWheel に bind されるのは bind TCombobox <MouseWheel> { ttk::combobox::Scroll %W [expr {-(%D/120)}] } %D はマウスホィールイベントの event.delta %W にはイベント対象のウィジェットのIDが入りますが、ここでは Combobox 想定なので スクロールバー「f.sb」に bind する場合は、この辺の引数の調整が必要。 スクロールバーの MouseWheel 対応については yview scroll https://wiki.tcl-lang.org/page/mousewheel
teamikl

2022/05/25 13:14 編集

> > 「f.sb」に何をbindすればいいのでしょうか? 追加情報ですが、Listbox, Scrollbar のイベントハンドラの設定自体は ttk::combobox の実装内で既にできてそうです。 <MouseWheel> を bind しなくても、以下の設定だけでもマウスホィール対応でした listbox.config(yscrollcommand=scrollbar.set) scrollbar.config(command=listbox.yview) ttk::combobox のスクロールバーのMouseWheelイベントが効かないのは、 別のイベント伝搬の問題かもしれません。 マウスホィール移動時にリストボックスを動かす設定自体は pythonで書くと (※ windows用) scrollbar.bind("<MouseWheel>", lambda e: listbox.yview_scroll(-e.delta//120, "units")) tcl で書くと .sb (scrollbar) .lb (listbox) として bind <MouseWheel> .sb {.lb yview scroll [expr {-(%D/120)}] units}
teamikl

2022/05/25 13:33 編集

コンボボックスのスクロールバー上のマウスホィール対応について それっぽい動作を実装出来たので、回答内にサンプルコードを追記しておきます。 bind で実装したい場合の案は、ttk::combobox::Post/Unpost を呼び出し 予め強制的にポップダウンリストを表示する事で「f.sb」を明示的に生成しておく、等が考えられます。
netz-eng

2022/05/26 13:30

色々と考察、試行くださりありがとうございます。 回答に追記いただいた書き方(bindでないほう)で、非常にシンプルに実現することができました! tclで直接(?)記述して実装する方法もあるんですね。自分にはまだ難解です……。 必要に迫られたときに、また頂いたご回答を見ながら勉強してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問