You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Tkinter文本框创建自动调整的书签功能

Solution for Dynamic Bookmarks in Tkinter Text Editor

Great question! The core issue you're dealing with—keeping bookmarks aligned when lines are inserted or deleted—has a clean, built-in solution in Tkinter: text marks. Unlike fixed line numbers, marks are dynamic position markers that automatically adjust as text changes, so you don't have to manually calculate offsets when lines are added or removed.

Step 1: Use Tkinter Marks for Bookmarks

Instead of storing line numbers for your bookmarks, create a Tkinter mark at the position of each bookmark. Marks are designed to move with text:

  • If you insert text above a mark, the mark shifts down to stay attached to its original line.
  • If you delete text above a mark, the mark shifts up accordingly.
  • You can configure mark "gravity" to control exactly how it behaves when text is inserted around it.

Step 2: Extend Your CustomText Class to Manage Bookmarks

Here's how to modify your existing CustomText class to add bookmark management using marks:

import tkinter as tk

class TextLineNumbers(tk.Canvas):
    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs)
        self.textwidget = None
        self.font_spacing = 0
        self.setfontspacing(self.font_spacing)
    def attach(self, text_widget):
        self.textwidget = text_widget
    def setfontspacing(self, spacing):
        self.font_spacing = spacing
    def redraw(self, *args):
        '''redraw line numbers and bookmarks'''
        self.delete("all")
        i = self.textwidget.index("@0,0")
        while True :
            dline= self.textwidget.dlineinfo(i)
            if dline is None:
                break
            y = dline[1] + self.font_spacing
            linenum = str(i).split(".")[0].zfill(7)
            self.create_text(2,y,anchor="nw", text=linenum)
            
            # Draw bookmark indicator if current line has a bookmark
            current_line = str(i).split(".")[0]
            has_bookmark = any(
                self.textwidget.get_bookmark_line(name) == current_line
                for name in self.textwidget.bookmarks
            )
            if has_bookmark:
                # Add a small dot to indicate a bookmark
                self.create_oval(40, y-4, 48, y+4, fill="#ff6b6b")
            
            i = self.textwidget.index("%s+1line" % i)

class CustomText(tk.Text):
    def __init__(self, *args, **kwargs):
        tk.Text.__init__(self, *args, **kwargs)
        # Create proxy for event generation
        self._orig = self._w + "_orig"
        self.tk.call("rename", self._w, self._orig)
        self.tk.createcommand(self._w, self._proxy)
        
        # Bookmark management
        self.bookmarks = {}  # Maps user-friendly names to internal mark IDs
        self._next_bookmark_id = 1

    def _proxy(self, *args):
        cmd = (self._orig,) + args
        result=None
        try:
            result = self.tk.call(cmd)
        except Exception:
            pass
        # Generate events as before
        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")
        if (args[0] in ("insert", "replace", "delete") ):
            self.event_generate("<<Text_Change>>", when="now")
        return result

    # Bookmark methods
    def add_bookmark(self, name=None):
        """Add a bookmark at the start of the current line. Returns the bookmark name."""
        mark_id = f"_bookmark_{self._next_bookmark_id}"
        self._next_bookmark_id += 1
        
        # Set the mark at the start of the current line
        line_start = self.index(tk.INSERT).split(".")[0] + ".0"
        self.mark_set(mark_id, line_start)
        
        # Set mark gravity to LEFT so it stays anchored to the start of the line
        self.mark_gravity(mark_id, tk.LEFT)
        
        # Use user-provided name or default
        bookmark_name = name if name else f"Bookmark {self._next_bookmark_id - 1}"
        self.bookmarks[bookmark_name] = mark_id
        return bookmark_name

    def remove_bookmark(self, bookmark_name):
        """Remove a bookmark by name."""
        if bookmark_name in self.bookmarks:
            self.mark_unset(self.bookmarks[bookmark_name])
            del self.bookmarks[bookmark_name]

    def get_bookmark_line(self, bookmark_name):
        """Get the current line number of a bookmark."""
        if bookmark_name not in self.bookmarks:
            return None
        index = self.index(self.bookmarks[bookmark_name])
        return index.split(".")[0]

    def get_all_bookmarks(self):
        """Get a dictionary of all bookmarks and their current line numbers."""
        return {name: self.get_bookmark_line(name) for name in self.bookmarks}

Key Details Explained

  • Mark Gravity: self.mark_gravity(mark_id, tk.LEFT) ensures the mark stays anchored to the start of its line. If you insert text at the beginning of the line, the mark won't move—exactly what you want for a line bookmark.
  • Bookmark Storage: The self.bookmarks dictionary maps user-friendly names (like "Bookmark 1") to internal mark IDs, making it easy to manage and reference bookmarks.
  • Visual Indicators: The modified TextLineNumbers.redraw() method adds a small red dot next to line numbers that have bookmarks, so users can see where bookmarks are located.

Why This Beats Manual Line Number Calculation

You won't need to parse the cmd arguments from the proxy function to track line inserts/deletes anymore. Tkinter handles all the position adjustments automatically, so your bookmarks will always point to the correct line—no matter how the text is edited.

Example Usage

To test this, you could add buttons to your UI to add/remove bookmarks:

root = tk.Tk()
text = CustomText(root)
text.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

linenumbers = TextLineNumbers(root, width=50)
linenumbers.attach(text)
linenumbers.pack(side=tk.LEFT, fill=tk.Y)

# Bind redraw to text changes
text.bind("<<Change>>", linenumbers.redraw)
text.bind("<Configure>", linenumbers.redraw)

# Add bookmark button
add_btn = tk.Button(root, text="Add Bookmark", command=lambda: print("Added:", text.add_bookmark()))
add_btn.pack()

# List bookmarks button
list_btn = tk.Button(root, text="List Bookmarks", command=lambda: print(text.get_all_bookmarks()))
list_btn.pack()

root.mainloop()

内容的提问来源于stack exchange,提问作者rkinca

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 08:42:45