【问题标题】:Trying to change a scrolled canvas width with mouse wheel尝试使用鼠标滚轮更改滚动的画布宽度
【发布时间】:2021-07-16 15:11:38
【问题描述】:

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

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()

画布本身在right_frame 内部,画布数量由row_amount 给出。 left_frame 包含每个画布的标签。画布应该可以很宽,所以我最初将宽度值设置为 10000。因此,它们开始部分可见,其余部分可通过滚动条访问。

我希望鼠标滚轮控制整个画布的大小(即当前可见的内容和可以使用滚动条查看的内容),类似于音频或视频编辑软件时间线。

现在,当我使用鼠标滚轮时,似乎调整大小的不是整个画布,而只是“可见”部分。将其调整到足够小,您可以开始在窗口右侧看到它的框架背景。

我在这里错过了什么?

【问题讨论】:

  • “应该允许画布非常宽,所以我最初设置的宽度值是 10000。” - 你有 10,000 像素宽的显示器吗?那将是令人印象深刻的!
  • 确实会:)。但不幸的是,不,不是现在。我想要实现的是音频或视频编辑软件中的时间线。我正在尝试控制画布的宽度,以便稍后实现水平缩放功能。到目前为止,我对使用 scale 的缩放方法没有运气,所以我想我可以更改画布宽度并重绘所有画布对象。它的效率非常低,但我并不期待有很多画布对象,所以我猜它会足够快以使其可用。
  • 好的,所以“宽度”实际上并不是指画布的物理宽度。您正在谈论画布的可滚动区域。画布的宽度和可绘制区域无关。你知道你可以画出远远超出画布物理尺寸的东西吗?
  • 是的!确切地说,可滚动区域。我想这就是我所说的“整个画布”。我不知道这个词指的是什么,对不起。如果我做对了,我的“可见画布”正是 width 属性所指的,毕竟,因此是行为。
  • 鉴于此,我的 zoomer 函数应该更改 scrollregion 而不是 width。对吗?

标签: python tkinter tkinter-canvas tkinter-layout


【解决方案1】:

我在这里错过了什么?

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

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

您在评论中说您正在尝试创建时间线。这是一个通过每秒添加一个刻度线来“增长”的画布小部件的示例。请注意,画布只有 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()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 1970-01-01
    • 2012-06-25
    • 2018-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多