尝试使用鼠标滚轮更改滚动的 canvas 宽度

Trying to change a scrolled canvas width with mouse wheel

我正在尝试使用鼠标滚轮控制多个 canvases 宽度。我到目前为止是这样的:

import tkinter as tk

class App(tk.Frame):
    row_amount = 3

    def __init__(self, root):
        super(App, self).__init__(root)
        self.root = root

        self.main_frame = tk.Frame(root)
        self.main_frame.pack(expand=True, fill=tk.BOTH)

        self.row_collection = RowCollection(root, self.main_frame)
        for i in range(App.row_amount): self.row_collection.row()

        window_height = App.row_amount * 100
        window_width = root.winfo_screenwidth() - 30
        root.geometry(f'{window_width}x{window_height}+0+0')

        self.row_collection.right_frame.grid_columnconfigure(0, weight=1)
        self.row_collection.left_frame.grid_columnconfigure(0, weight=1)
        self.pack()


class RowCollection:
    """Collection of rows"""

    def __init__(self, root, frame):
        self.row_list = []
        self.root = root
        self.frame = frame
        self.right_frame = tk.Frame(self.frame, bg='red')
        self.right_frame.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH)
        self.left_frame = tk.Frame(self.frame)
        self.left_frame.pack(side=tk.LEFT, fill=tk.Y)

        self.scrollbar = tk.Scrollbar(self.right_frame, orient=tk.HORIZONTAL)
        self.scrollbar.config(command=self.scroll_x)

    def row(self):
        row = Row(self)
        self.row_list.append(row)

        return row

    def scroll_x(self, *args):
        for row in self.row_list:
            row.canvas.xview(*args)

    def zoomer(self, event=None):
        print('zooming')
        for row in self.row_list:
            scale_factor = 0.1
            curr_width = row.canvas.winfo_reqwidth()
            print(f'event delta={event.delta}')
            if event.delta > 0:
                row.canvas.config(width=curr_width * (1 + scale_factor))
            elif event.delta < 0:
                row.canvas.config(width=curr_width * (1 - scale_factor))
            row.canvas.configure(scrollregion=row.canvas.bbox('all'))



class Row:
    """Every row consists of a label on the left side and a canvas with a line on the right side"""

    row_count = 0
    label_width = 15
    line_weight = 3
    line_yoffset = 3
    padx = 20

    def __init__(self, collection):
        self.frame = collection.frame
        self.root = collection.root
        self.collection = collection
        self.canvas = None
        self.label = None
        self.text = f'Canvas {Row.row_count}'
        self.height = 100

        self.root.update()

        self.label = tk.Label(self.collection.left_frame,
                              text=self.text,
                              height=1,
                              width=Row.label_width,
                              relief='raised')
        self.label.grid(row=Row.row_count, column=0, sticky='ns')

        # configure row size to match future canvas height
        self.collection.left_frame.grid_rowconfigure(Row.row_count, minsize=self.height)

        self.root.update()

        self.canvas = tk.Canvas(self.collection.right_frame,
                             width=10000,
                             height=self.height,
                             bg='white',
                             highlightthickness=0)

        self.canvas.grid(row=Row.row_count, column=0, sticky=tk.W)

        self.root.update()

        # draw line
        self.line = self.canvas.create_rectangle(self.padx,
                                                 self.canvas.winfo_height() - Row.line_yoffset,
                                                 self.canvas.winfo_width() - self.padx,
                                                 self.canvas.winfo_height() - Row.line_yoffset + Row.line_weight,
                                                 fill='#000000', width=0, tags='line')

        # config canvas
        self.canvas.config(scrollregion=self.canvas.bbox('all'))
        self.canvas.config(xscrollcommand=self.collection.scrollbar.set)
        self.canvas.bind('<Configure>', lambda event: self.canvas.configure(scrollregion=self.canvas.bbox('all')))
        self.canvas.bind('<MouseWheel>', self.collection.zoomer)

        # Create point at canvas edge to prevent scrolling from removing padding
        self.bounding_point = self.canvas.create_rectangle(0, 0, 0, 0, width=0)
        self.bounding_point = self.canvas.create_rectangle(self.canvas.winfo_width(), self.canvas.winfo_width(),
                                                           self.canvas.winfo_width(), self.canvas.winfo_width(),
                                                           width=0)

        Row.row_count += 1

        self.collection.scrollbar.grid(row=Row.row_count, column=0, sticky='ew')


if __name__ == '__main__':

    root = tk.Tk()
    app = App(root)
    root.mainloop()

canvases本身在right_frame里面,canvases的数量由row_amount给定。 left_frame 包含每个 canvas 的标签。 canvases 应该允许非常宽,所以我最初将宽度值设置为 10000。因此,它们开始部分可见,其余部分可通过滚动条访问。

我想要的是鼠标滚轮控制整个canvas的大小(即当前可见的和使用滚动条可以查看的),类似于什么会发生在音频或视频编辑软件时间轴中。

现在,当我使用鼠标滚轮时,似乎调整大小的不是整个 canvas,而是 'visible' 部分。将其调整到足够小,您可以开始在 window.

的右侧部分看到它的框架背景

我在这里错过了什么?

What am I missing here?

我认为您缺少的是 canvas 的可绘制区域与 canvas 小部件的物理尺寸完全无关。 canvas 创建后无需调整其大小。您可以很好地超出小部件的边界。

如果您希望能够将不属于可见部分的元素滚动到视图中 canvas,您必须配置 scrollregion 以定义虚拟区域 canvas那应该是可见的。

您在评论中说您正在尝试创建时间轴。这是一个 canvas 小部件的示例,它通过每秒添加一个刻度标记来“增长”。请注意 canvas 只有 500,100,但可绘制区域每秒都会扩展。

import tkinter as tk

root = tk.Tk()
canvas = tk.Canvas(root, width=500, height=100, bg="black")

vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
hsb = tk.Scrollbar(root, orient="horizontal", command=canvas.xview)
canvas.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
canvas.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")

root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)

counter = 0
def add_tick():
    global counter

    # get the current state of the scrollbar. We'll use this later
    # to determine if we should auto-scroll
    xview = canvas.xview()

    # draw a new tickmark
    counter += 1
    x = counter * 50
    canvas.create_text(x, 52, anchor="n", text=counter, fill="white")
    canvas.create_line(x, 40, x, 50, width=3, fill="red")

    # update the scrollable region to include the new tickmark
    canvas.configure(scrollregion=canvas.bbox("all"))

    # autoscroll, only if the widget was already scrolled
    # as far to the right as possible
    if int(xview[1]) == 1:
        canvas.xview_moveto(1.0)

    canvas.after(1000, add_tick)


add_tick()
root.mainloop()