背景:
我有一个程序使用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个像素到它的原始位置。我的解决方案是编写一些代码,在释放鼠标按钮时,记录最后一个位置并更新每个对象的坐标数据。但是,我正在寻找一种方法来删除这最后一步 - 我希望有一种方法可以移动画布,使对象位置是绝对的,并假设一个类似于'scroll'函数的函数会这样做。我意识到我在这里已经漫步了,但是我在上面的例子中添加了几行来突出我的问题 - 通过移动画布可以看到坐标发生变化。再次感谢你。
答案 0 :(得分: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)
首先,简单的部分:手动滚动画布。正如documentation所述,您使用xview
和yview
方法,就像您的滚动条command
一样。或者您可以直接致电xview_moveto
和yview_moveto
(或foo_scroll
方法,但它们似乎不是您想要的。)你可以看到我实际上没有使用这些;我将在下面解释。
接下来,要捕获画布上的点击并拖动事件,您只需绑定<B1-Motion>
,就像正常的拖放一样。
这里棘手的一点是,拖动事件为您提供了屏幕像素坐标,而xview_moveto
和yview_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')
现在你可以通过它的背景拖动整个画布,但拖动其他项目(标记为'draggable'的那些)将会做你想做的任何事情。
如果我正确理解你的评论,你剩下的问题是当你想要画布坐标时你正试图使用窗口坐标。文档中的坐标系部分解释了这一区别。
所以,假设你有一个放置在500,500的项目,原点是0,0。现在,你将画布滚动到500,0。项目的窗口坐标现在为0 ,500,但它的画布坐标仍然是500,500。正如文档所说:
要从窗口坐标转换为画布坐标,请使用
canvasx
和canvasy
方法