私は比較的簡単な解決策を持っていますが、Tkinter とその下にある tcl/tk テキスト ウィジェットがどのように機能するかについてある程度の知識が必要なため、複雑で理解しにくい可能性があります。ここでは、そのまま使用できる完全なソリューションとして紹介します。これは、非常にうまく機能する独自のアプローチを示していると思うからです。
このソリューションは、使用するフォントに関係なく、異なる行に異なるフォントを使用するかどうか、ウィジェットが埋め込まれているかどうかなどに関係なく機能することに注意してください。
Tkinter のインポート
始める前に、次のコードは、python 3.0 以降を使用している場合、tkinter がこのようにインポートされていることを前提としています。
import tkinter as tk
...またはこれ、python 2.xの場合:
import Tkinter as tk
行番号ウィジェット
行番号の表示に取り組みましょう。やりたいことは、数値を正確に配置できるようにキャンバスを使用することです。redraw
カスタム クラスを作成し、関連するテキスト ウィジェットの行番号を再描画するという名前の新しいメソッドを指定します。attach
また、テキスト ウィジェットをこのウィジェットに関連付けるためのメソッドも提供します。
このメソッドは、テキスト ウィジェット自体がメソッドを介してテキスト行の開始位置と終了位置を正確に教えてくれるという事実を利用していますdlineinfo
。これにより、キャンバス上のどこに行番号を描画するかを正確に知ることができます。また、行が表示されていない場合にdlineinfo
返されるという事実None
を利用して、行番号の表示をいつ停止するかを知ることができます。
class TextLineNumbers(tk.Canvas):
def __init__(self, *args, **kwargs):
tk.Canvas.__init__(self, *args, **kwargs)
self.textwidget = None
def attach(self, text_widget):
self.textwidget = text_widget
def redraw(self, *args):
'''redraw line numbers'''
self.delete("all")
i = self.textwidget.index("@0,0")
while True :
dline= self.textwidget.dlineinfo(i)
if dline is None: break
y = dline[1]
linenum = str(i).split(".")[0]
self.create_text(2,y,anchor="nw", text=linenum)
i = self.textwidget.index("%s+1line" % i)
これをテキスト ウィジェットに関連付けてからredraw
メソッドを呼び出すと、行番号が適切に表示されるはずです。
行番号の自動更新
これは機能しますが、致命的な欠陥があります: を呼び出すタイミングを知っておく必要がありますredraw
。キーを押すたびに起動するバインディングを作成できますが、マウス ボタンでも起動する必要があり、ユーザーがキーを押して自動繰り返し機能を使用する場合などを処理する必要があります。行番号も必要です。ウィンドウが拡大または縮小された場合、またはユーザーがスクロールした場合に再描画されるため、数値が変化する可能性のあるすべてのイベントを把握しようとするうさぎの穴に陥ります。
別の解決策として、何かが変更されるたびにテキスト ウィジェットでイベントを発生させるというものがあります。残念ながら、テキスト ウィジェットには、変更をプログラムに通知する機能が直接サポートされていません。これを回避するには、プロキシを使用してテキスト ウィジェットへの変更をインターセプトし、イベントを生成します。
「https://stackoverflow.com/q/13835207/7432」という質問への回答で、何かが変更されるたびにテキストウィジェットにコールバックを呼び出す方法を示す同様のソリューションを提供しました。今回は、ニーズが少し異なるため、コールバックの代わりにイベントを生成します。
カスタム テキスト クラス
<<Change>>
以下は、テキストが挿入または削除されたとき、またはビューがスクロールされたときにイベントを生成するカスタム テキスト ウィジェットを作成するクラスです。
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, *args):
# let the actual widget perform the requested action
cmd = (self._orig,) + args
result = self.tk.call(cmd)
# generate an event if something was added or deleted,
# or the cursor position changed
if (args[0] in ("insert", "replace", "delete") or
args[0:3] == ("mark", "set", "insert") or
args[0:2] == ("xview", "moveto") or
args[0:2] == ("xview", "scroll") or
args[0:2] == ("yview", "moveto") or
args[0:2] == ("yview", "scroll")
):
self.event_generate("<<Change>>", when="tail")
# return what the actual widget returned
return result
すべてを一緒に入れて
最後に、これら 2 つのクラスを使用するサンプル プログラムを次に示します。
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.text = CustomText(self)
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set)
self.text.tag_configure("bigfont", font=("Helvetica", "24", "bold"))
self.linenumbers = TextLineNumbers(self, width=30)
self.linenumbers.attach(self.text)
self.vsb.pack(side="right", fill="y")
self.linenumbers.pack(side="left", fill="y")
self.text.pack(side="right", fill="both", expand=True)
self.text.bind("<<Change>>", self._on_change)
self.text.bind("<Configure>", self._on_change)
self.text.insert("end", "one\ntwo\nthree\n")
self.text.insert("end", "four\n",("bigfont",))
self.text.insert("end", "five\n")
def _on_change(self, event):
self.linenumbers.redraw()
...そしてもちろん、これをファイルの最後に追加して、ブートストラップします。
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()