naritoブログ

このブログはDjangoとBootstrap4で作成されました
ソースコード

Tkinterで、Textウィジェットにon_change、on_scrollイベントを作る

プログラミング関連 Python Tkinter 約60日前
2017年10月12日16:03
TkinterのTextウィジェットには、他のGUIライブラリによくあるon_change やon_scrollのようなイベントはありません。(ないよね?)
なので、これらが欲しい場合は自前で書く必要があります。

スタックオーバーフローには、そのためのナイスな解決策がありました。
https://stackoverflow.com/questions/16369470/tkinter-adding-line-number-to-text-widget/16375233#16375233

これを基に作成したのが、以下のコードです。
import tkinter as tk


class CustomText(tk.Text):
    """Textの、イベントを拡張したウィジェット."""

    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)
        self.tk.eval('''
            proc widget_proxy {widget widget_command args} {
                set result [uplevel [linsert $args 0 $widget_command]]
                if {([lrange $args 0 1] == {xview moveto}) ||
                    ([lrange $args 0 1] == {xview scroll}) ||
                    ([lrange $args 0 1] == {yview moveto}) ||
                    ([lrange $args 0 1] == {yview scroll})} {
                    event generate  $widget <<Scroll>> -when tail
                }
                if {([lindex $args 0] in {insert replace delete})} {
                    event generate  $widget <<Change>> -when tail
                }
                # return the result from the real widget command
                return $result
            }
            ''')
        self.tk.eval('''
            rename {widget} _{widget}
            interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
        '''.format(widget=str(self)))


root = tk.Tk()
root.title('Editor Test')

text_widget = CustomText(root)
text_widget.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W))

# on_changeと、on_scrollイベントの設定。printするだけ
text_widget.bind('<<Scroll>>', lambda event: print('scroll'))
text_widget.bind('<<Change>>', lambda event: print('change'))

root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.mainloop()




tkinterにはTcl/Tkレベルの機能の呼び出しや、Tcl/Tkコードを評価する仕組みがあります。その一つがtk.evalです。
非常に面白い例だと、例えば以下のようなことができます。
import tkinter as tk


class MyTk(tk.Tk):

    def __init__(self):
        super().__init__()
        self.eval("""
            wm title . "Test"
            grid [ttk::button .b -text "Hello World"]
        """)


root = MyTk()
root.mainloop()




ちゃんと表示されました!



self.eval内にはTcl/Tkコードを書いています。タイトルの設定と、ttk.Buttonを作成しgridレイアウトで配置しています。
殆どの場合、Tkinterを使う上でTcl/Tkコードを書く必要はないのですが、今回のような例もあります。


tk.evalを使うのに抵抗があるのならば、以下のようなやり方もできます。
https://stackoverflow.com/questions/40617515/python-tkinter-text-modified-callback