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

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

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

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

Tkinter

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

Python

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

Q&A

解決済

1回答

4316閲覧

tkinter ボタンを押している間、何かしらのイベントを処理したい時

puroko3

総合スコア185

Python 3.x

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

Tkinter

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

Python

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

0グッド

2クリップ

投稿2019/02/20 09:04

編集2019/02/20 11:56

tkinterでボタンを押している間だけ処理を実行するというイベントフォーマットは無いのでしょうか?自分の力では見つける事が出来ませんでした。

python

1import tkinter as tk 2from tkinter import ttk 3import threading 4 5def button_up(event): 6 global flag 7 flag = True 8 while flag: 9 button3.place(x=0, y=button3.winfo_y() - 1) 10 11def button_down(event): 12 global flag 13 flag = True 14 while flag: 15 button3.place(x=0, y=button3.winfo_y() + 1) 16 17def button_stop(event): 18 global flag 19 flag = False 20 21root = tk.Tk() 22root.geometry("500x500") 23 24button1 = tk.Button(text="up") 25button1.pack() 26button1.bind("<Button-1>", lambda event:threading.Thread(target=button_up, args=([event])).start()) 27button1.bind("<ButtonRelease-1>", button_stop) 28 29button2 = tk.Button(text="down") 30button2.pack() 31button2.bind("<Button-1>", lambda event:threading.Thread(target=button_down, args=([event])).start()) 32button2.bind("<ButtonRelease-1>", button_stop) 33 34button3 = tk.Button(text="ボタン") 35button3.place(x=0, y=250) 36 37root.mainloop()

ButtonPressとButtonReleaseを使ってそれっぽい事は出来たんですが、tkinterの標準にあればそちらを使いたいと思っています。
もしないのであれば、どのような方法がベターなのか知りたいです。
わかる方いらしたらお願いします。

修正の依頼を受けて
tkinterの標準のものとは別に、ボタンを押している間ウィジェットが移動するというオリジナルのスクロールバーを作りたいと思っています。

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

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

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

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

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

t_obara

2019/02/20 09:30

具体的にそれで何をしたいのかをご提示されるとより適切な回答を得られるかと思われます。
puroko3

2019/02/20 10:01

コメントありがとうございます。 すいませんこちらの都合になりますが、具体的に何をするかというのは伏せさせて頂きたいです... また、直接的な解決というよりも、タイトル通り汎用的・抽象的な回答?を知りたいという感じです。
magichan

2019/02/20 10:33

「tkinter ボタンを押している間処理を実行」ってことは長時間処理を行う可能性があるわけで、その間 イベントループの処理を止めるわけにはいかないので、プロセスなりスレッドなりを立てることになります。 であれば、どのような処理を行うのかはかなり重要な要素かとおもいます。
puroko3

2019/02/20 12:08

コメントありがとうございます。 冷静に考えれば、伏せるほどの事でもなかったので追記しておきました。
guest

回答1

0

ベストアンサー

汎用的・抽象的な回答

あえて申し上げれば「どんな処理にでも向いたような方式」はないように思えます。

t_obaraさん、magichanさんが質問コメントを寄せておられますがその真意はおそらく質問者さんがやろうとしていることの詳細がわからないと回答できないとおっしゃっているわけではなく、「どのような類の処理なのか」を言えばよいだけと思います。

自分はご質問のコードからGUIの状態変更=>単純な2Dアニメーション的なものを実現するのが目的と想定してコメントしてみます。


ボタンを押している間だけ処理を実行するというイベントフォーマットは

イベントフォーマットというよりイベントシーケンスといった方がよいかと思いますが、そのようなものはないと思います。

tkinter自体にそのようなイベントシーケンスがないなら、なんらかの方法で自前で定義することになるでしょう。そのようなことを多用したいとき「多少なりともシンプル」にと考えたくなりますね。実現方法にはいろいろな工夫があり得ると思いますが例を2つほど挙げてみます。

(なお、質問者さんのコードは別のスレッドで「時間間隔を置かずに闇雲にGUIを変更し続ける」というものですがアニメーションかどうかに限らず「CPUを無暗に浪費するビジーループ的な処理」はお勧めできませんので「ある一定のインターバルでイベントハンドラーを起動する」という考え方を前提にします。)

案1 単に共通関数にする

<Button-1><ButtonRelease-1>を望むウィジェットに設定し、押されている間特定の関数を指定インターバルごとに起動させるような共通関数を定義する方法です。

python

1import tkinter as tk 2 3 4def bind_button1_down(widget, handler, interval=10): 5 assert interval > 0 6 e = None 7 8 def on_button1_down(ev): 9 nonlocal e 10 e = ev 11 on_tick() 12 13 def on_button1_released(ev): 14 nonlocal e 15 e = None 16 17 def on_tick(): 18 if e: 19 handler(e) 20 widget.after(interval, on_tick) 21 22 widget.bind('<Button-1>', on_button1_down) 23 widget.bind('<ButtonRelease-1>', on_button1_released) 24 25 26root = tk.Tk() 27root.geometry("500x500") 28 29button = tk.Button(root, text="press me") 30bind_button1_down(button, lambda ev: label.place(x=0, y=(label.winfo_y() + 1) % 450), interval=1) 31button.pack() 32 33label = tk.Label(root, text="***") 34label.place(x=0, y=0) 35root.mainloop()

案2 カスタムイベントが発生するクラスを定義

別にクラスでなくても関数でもいいのですが・・・案1だとイベント設定がtkinterの他のイベントと雰囲気が違うので「あたかもそういうイベントが最初からあるかのように書ける(だけ)」というものです。

python

1import tkinter as tk 2 3 4class TkEx(tk.Tk): 5 def __init__(self, *args, **kwargs): 6 super().__init__(*args, **kwargs) 7 self.bind_all('<Button-1>', self.on_button1_pressed) 8 self.bind_all('<ButtonRelease-1>', self.on_button1_released) 9 self._button1down_event = None 10 self._button1down_interval = 10 11 12 def on_button1_pressed(self, ev): 13 self._button1down_event = ev 14 self.on_tick() 15 16 def on_button1_released(self, ev): 17 self._button1down_event = None 18 19 def on_tick(self): 20 if self._button1down_event: 21 self._button1down_event.widget.event_generate('<<ButtonDown-1>>', when='tail') 22 self.after(self._button1down_interval, self.on_tick) 23 24 def set_button1_donw_interval(self, interval): 25 self._button1down_interval = interval 26 27 28root = TkEx() 29root.geometry('500x500') 30root.set_button1_donw_interval(1) 31 32button = tk.Button(root, text="press me") 33button.bind('<<ButtonDown-1>>', lambda ev: label.place(x=0, y=(label.winfo_y() + 1) % 450)) 34button.pack() 35 36label = tk.Label(root, text="***") 37label.place(x=0, y=0) 38root.mainloop()

上の例は非常に単純化したものでボタン1に対してしか機能がありませんし、インターバルをボタンごとに変更するためにはもうちょっと工夫せねばなりません。そんなことを考えるにつけ、わざわざこんないびつな仕様のクラスを定義するよりは案1の共通関数ぐらいで充分な気がしました。コード例を書いておいてなんですが・・・上の例は筋が悪い感じがするのです。


追記:
magichanさんから有益なコメントをいただいています。回答コメントを見ていただけると例1/例2のいずれもいろいろ配慮すべき点があることがわかると思います。回答に書いたコードは「あくまでたたき台と思っていただきたい」点を強調しておきます。

元の回答でターゲット個別へのインターバル指定云々と書きましたが、それは一つの考慮点にしかすぎずもっといろいろ考えるべき点がありそうです。(例えば上のコード例ではボタンリリース時に状態を変え、リリース後に発火したafterハンドラーでコールバックを起動しない配慮をしていますが、after_cancelとした方がスマートに感じます。)

投稿2019/02/20 15:07

編集2019/02/21 03:57
KSwordOfHaste

総合スコア18392

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

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

KSwordOfHaste

2019/02/20 15:10

あ・・・回答している間に質問が編集されたことに気づきました。 自分の回答の前半はもはや不要と思いますがこのままでもいいでしょうかね・・・
magichan

2019/02/21 02:08

質問にある用途程度であれば afterを使ってのインターバルタイマーぐらいが適当でしょうね。スクロール用途であれば秒間 5回(200mS)程度でも十分な気がします。 あと案2はそんなに『筋が悪い感じ』ですかね?私が実装するとしたらたぶんそれに近いコードになるかとおもいます。
KSwordOfHaste

2019/02/21 02:35

インターバルについておっしゃるとおりと思います!1にしたのは「ビジーループにせずに最低でも1に」という程度のつもりでした。 案2ですが「クラスを拡張して望みの機能を付与する」方針は悪くないと思いましたが、自分が実際に挙げた拡張仕様例そのものはターゲットごとにインターバルを指定できない点が筋が悪いと感じました。文章を読んでみると何がいいたいか曖昧ですね・・・単に「インターバルを個別に指定できないのは筋が悪い」と書いたらどう思われますか?
magichan

2019/02/21 03:16

なるほど。確かにInterval時間を可変にできたほうが良いかとは思いますが、質問に対しての叩き台としては十分と思いました。
magichan

2019/02/21 03:18

あと、「Interval時間を可変にする」以外で拡張するとしたら - コンストラクタのcommandパラメータを使えるようにする - Interval時間が長い場合を考慮して、release時にはafter_cancel()呼ぶようにする - 正確なIntervalタイマーになるようにコールバックでの処理時間を測定して after() に渡す時間を調整する あたりがありそうですね。
KSwordOfHaste

2019/02/21 03:59 編集

>以外で拡張するとしたら コメントありがとうございます。 そうした配慮がコメントできていたらよかったのですが自分もafter_cancelを知らずちょっと筋が悪い回答になってました。個別のインターバル云々よりafter_cancelを用いてない方が筋がわるそうですw; とりあえず「たたき台程度に見てほしい」旨を追記させていただきました。
puroko3

2019/02/21 04:14

回答とコメントありがとうございます! afterと再帰使えば別スレッドに飛ばさなくても、いい感じに実行出来るんですね。勉強になります。 書いてくださったコードの考えとmagichanさんの拡張案を元に、実装していきたいと思います。 また機会があればよろしくお願いします。
KSwordOfHaste

2019/02/21 04:25

細かい点ですが...afterのハンドラーの中でafterを呼び出していることで再帰的な呼び出しに見えるかも知れませんが、afterは単にハンドラーを登録してすぐにreturnしてくる関数ですので「再帰」という用語は使わないほうがよいと思います。
puroko3

2019/02/21 04:50

再帰スタックが溜まらないから再帰ではないという事でしょうか。 それに自分自身で呼び出してるわけではないですね。失礼しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問