3

背景:
我有一个使用 Tkinter 作为 GUI 基础的程序。该程序有一个画布,其中填充了大量的对象。目前,为了移动屏幕上的所有对象,我只是将一个移动函数绑定到标签“all”,这当然会移动屏幕上的所有对象。然而,跟踪所有画布对象的位置对我来说至关重要——即在每次移动之后我都会记录新的位置,这似乎不必要地复杂。

问题:
仅使用鼠标(不使用滚动条)有效滚动/拖动整个画布(屏幕大小的几倍)的最佳方法是什么?

我的尝试:
我已经实现了滚动条,并找到了几个设置滚动条的指南,但没有一个涉及这个特殊要求。

不使用的滚动条方法示例:

from Tkinter import *

class Canvas_On:    
    def __init__(self, master):

        self.master=master
        self.master.title( "Example")
        self.c=Canvas(self.master, width=find_res_width-20, height=find_res_height, bg='black', scrollregion=(0,0,5000,5000))
        self.c.grid(row=0, rowspan=25, column=0)
        self.c.tag_bind('bg', '<Control-Button-1>', self.click)
        self.c.tag_bind('bg', '<Control-B1-Motion>', self.drag)

        self.c.tag_bind('dot', '<Button-1>', self.click_item)
        self.c.tag_bind('dot', '<B1-Motion>', self.drag_item)

        draw=Drawing_Utility(self.c)
        draw.drawer(self.c)

    def click(self, event):
        self.c.scan_mark(event.x, event.y)

    def drag(self, event):
        self.c.scan_dragto(event.x, event.y)

    def click_item(self, event):
        self.c.itemconfigure('dot 1 text', text=(event.x, event.y))
        self.drag_item = self.c.find_closest(event.x, event.y)
        self.drag_x, self.drag_y = event.x, event.y
        self.c.tag_raise('dot')
        self.c.tag_raise('dot 1 text')

    def drag_item(self, event):
        self.c.move(self.drag_item, event.x-self.drag_x, event.y-self.drag_y)
        self.drag_x, self.drag_y = event.x, event.y

class Drawing_Utility:
    def __init__(self, canvas):
        self.canvas=canvas
        self.canvas.focus_set()


    def drawer(self, canvas):
        self.canvas.create_rectangle(0, 0, 5000, 5000, 
                            fill='black', tags='bg')
        self.canvas.create_text(450,450, text='', fill='black', activefill='red', tags=('draggable', 'dot', 'dot 1 text'))
        self.canvas.create_oval(400,400,500,500, fill='orange', activefill='red', tags=('draggable', 'dot', 'dot 2'))
        self.canvas.tag_raise(("dot"))

root=Tk()
find_res_width=root.winfo_screenwidth()
find_res_height=root.winfo_screenheight()
run_it=Canvas_On(root)  
root.mainloop() 

我的特殊问题

我的程序生成所有画布对象坐标,然后绘制它们。这些物体以不同的模式排列,但至关重要的是,它们必须“知道”彼此的位置。当使用@abarnert 提供的方法在画布上移动时,以及我编写的移动所有画布对象的类似方法时,出现的问题是每个对象都“认为”它位于绘制对象之前生成的画布坐标处。例如,如果我将画布向左拖动 50 像素并单击程序中的一个对象,它会向右跳 50 像素回到它的原始位置。我对此的解决方案是编写一些代码,在释放鼠标按钮时,记录最后一个位置并更新每个对象的坐标数据。但是,我' 我正在寻找删除最后一步的方法-我希望有一种方法可以移动画布,使对象位置是绝对的,并假设类似于“滚动”功能的功能可以做到这一点。我意识到我在这里闲逛了,但是我在上面的示例中添加了几行突出显示我的问题的行 - 通过移动画布,您可以看到坐标发生了变化。再次感谢你。

4

1 回答 1

4

我会先给你最简单版本的代码,然后解释它,以便你可以根据需要扩展它。

class Canvas_On:    
    def __init__(self, master):
        # ... your original code here ...
        self.c.bind('<Button-1>', self.click)
        self.c.bind('<B1-Motion>', self.drag)

    def click(self, event):
        self.c.scan_mark(event.x, event.y)

    def drag(self, event):
        self.c.scan_dragto(event.x, event.y)

首先,简单的部分:手动滚动画布。正如文档所解释的,您使用xviewandyview方法,就像您的滚动条command一样。或者您可以直接调用xview_movetoand yview_moveto(或foo_scroll方法,但它们似乎不是您想要的)。你可以看到我实际上并没有使用这些;我会在下面解释。

接下来,要捕获画布上的单击和拖动事件,您只需 bind <B1-Motion>,就像正常的拖放一样。

这里棘手的一点是,拖动事件为您提供屏幕像素坐标,而xview_movetoandyview_moveto方法从顶部/左侧的 0.0 到底部/右侧的 1.0 取一小部分。因此,您需要捕获原始单击的坐标(通过绑定<Button-1>; 使用它,拖动事件的坐标和画布的 bbox,您可以计算moveto分数。如果您正在使用该scale方法并想要拖动适当地放大/缩小时,您也需要考虑到这一点。

但是除非你想做一些不寻常的事情,否则scan辅助方法会为你完成计算,所以调用它们会更简单。


请注意,这还将捕获画布上项目的单击和拖动事件,而不仅仅是背景。这可能是您想要的,除非您打算使项目在画布内可拖动。在后一种情况下,在所有其他项目下方添加一个背景矩形项目(透明或具有您为画布本身设计的任何背景),tag_bind而不是bind画布本身。(IIRC,对于旧版本的 Tk,您必须为背景项目创建一个标签tag_bind……但如果是这样,您可能已经必须这样做来绑定所有其他项目,所以这里也是一样的。无论如何,即使没有必要,我也会这样做,因为标签是创建可以全部绑定在一起的项目组的便捷方式。)

所以:

class Canvas_On:    
    def __init__(self, master):
        # ... your original code here ...
        self.c.tag_bind('bg', '<Button-1>', self.click)
        self.c.tag_bind('bg', '<B1-Motion>', self.drag)
        self.c.tag_bind('draggable', '<Button-1>', self.click_item)
        self.c.tag_bind('draggable', '<B1-Motion>', self.drag_item)
    # ... etc. ...
    def click_item(self, event):
        x, y = self.c.canvasx(event.x), self.c.canvasy(event.y)
        self.drag_item = self.c.find_closest(x, y)
        self.drag_x, self.drag_y = x, y
        self.tag_raise(item)
    def drag_item(self, event):
        x, y = self.c.canvasx(event.x), self.c.canvasy(event.y)
        self.c.move(self.drag_item, x-self.drag_x, y-self.drag_y)
        self.drag_x, self.drag_y = x, y

class Drawing_Utility:
    # ...
    def drawer(self, canvas):
        self.c.create_rectangle(0, 0, 5000, 5000, 
                                fill='black', tags='bg')
        self.c.create_oval(50,50,150,150, fill='orange', tags='draggable')
        self.c.create_oval(1000,1000,1100,1100, fill='orange', tags='draggable')

现在您可以通过其背景拖动整个画布,但拖动其他项目(标记为“可拖动”的项目)将执行您想要的任何其他操作。


如果我正确理解您的评论,那么您剩下的问题是您在想要画布坐标时尝试使用窗口坐标。文档中的坐标系部分解释了这种区别。

因此,假设您有一个放置在 500, 500 的项目,原点位于 0, 0。现在,您将画布滚动到 500, 0。项目的窗口坐标现在是 0, 500,但它的画布坐标仍然是 500、500。正如文档所说:

要将窗口坐标转换为画布坐标,请使用canvasxcanvasy方法

于 2013-09-19T18:14:45.527 回答