用鼠标移动和缩放Tkinter画布
我想要做的事情是:在tkinter的画布上画一些几何图形(这里是矩形),并且能够用鼠标来浏览这个画布。点击并拖动可以移动画布,滚动鼠标可以放大或缩小。
在这个话题中,我找到了关于点击和拖动的部分:用鼠标移动tkinter画布。
我也写了一些代码来实现滚动放大。移动和放大这两个功能单独都能很好地工作。
问题是:如果我先移动画布,然后再放大,放大的焦点就不再是鼠标的位置了。
有没有什么建议呢?
这里有一段代码可以测试。
[编辑:现在应该可以在Linux和Windows上工作]
import Tkinter as tk
import random
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(self, width=400, height=400, background="bisque")
self.xsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
self.ysb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
self.canvas.configure(scrollregion=(0,0,1000,1000))
self.xsb.grid(row=1, column=0, sticky="ew")
self.ysb.grid(row=0, column=1, sticky="ns")
self.canvas.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#Plot some rectangles
for n in range(50):
x0 = random.randint(0, 900)
y0 = random.randint(50, 900)
x1 = x0 + random.randint(50, 100)
y1 = y0 + random.randint(50,100)
color = ("red", "orange", "yellow", "green", "blue")[random.randint(0,4)]
self.canvas.create_rectangle(x0,y0,x1,y1, outline="black", fill=color, activefill="black", tags=n)
self.canvas.create_text(50,10, anchor="nw", text="Click and drag to move the canvas\nScroll to zoom.")
# This is what enables using the mouse:
self.canvas.bind("<ButtonPress-1>", self.move_start)
self.canvas.bind("<B1-Motion>", self.move_move)
#linux scroll
self.canvas.bind("<Button-4>", self.zoomerP)
self.canvas.bind("<Button-5>", self.zoomerM)
#windows scroll
self.canvas.bind("<MouseWheel>",self.zoomer)
#move
def move_start(self, event):
self.canvas.scan_mark(event.x, event.y)
def move_move(self, event):
self.canvas.scan_dragto(event.x, event.y, gain=1)
#windows zoom
def zoomer(self,event):
if (event.delta > 0):
self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
elif (event.delta < 0):
self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
#linux zoom
def zoomerP(self,event):
self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
def zoomerM(self,event):
self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
2 个回答
8
这是一个简化的缩放示例。对于大幅度的缩放,建议使用更高级的技术,以免让内存里塞满一个超大的调整大小的图片。
别忘了在脚本的最后添加你图片的路径哦。
附注:想要了解高级的缩放示例,可以点击这里。
# -*- coding: utf-8 -*-
# WARNING: This is a simplified zoom example.
# You should use more advanced techniques to not cram the memory
# with a huge resized image for the large zooms.
import random
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
class AutoScrollbar(ttk.Scrollbar):
''' A scrollbar that hides itself if it's not needed.
Works only if you use the grid geometry manager '''
def set(self, lo, hi):
if float(lo) <= 0.0 and float(hi) >= 1.0:
self.grid_remove()
else:
self.grid()
ttk.Scrollbar.set(self, lo, hi)
def pack(self, **kw):
raise tk.TclError('Cannot use pack with this widget')
def place(self, **kw):
raise tk.TclError('Cannot use place with this widget')
class Zoom(ttk.Frame):
''' Simple zoom with mouse wheel '''
def __init__(self, mainframe, path):
''' Initialize the main Frame '''
ttk.Frame.__init__(self, master=mainframe)
self.master.title('Simple zoom with mouse wheel')
# Vertical and horizontal scrollbars for canvas
vbar = AutoScrollbar(self.master, orient='vertical')
hbar = AutoScrollbar(self.master, orient='horizontal')
vbar.grid(row=0, column=1, sticky='ns')
hbar.grid(row=1, column=0, sticky='we')
# Open image
self.image = Image.open(path)
# Create canvas and put image on it
self.canvas = tk.Canvas(self.master, highlightthickness=0,
xscrollcommand=hbar.set, yscrollcommand=vbar.set)
self.canvas.grid(row=0, column=0, sticky='nswe')
vbar.configure(command=self.canvas.yview) # bind scrollbars to the canvas
hbar.configure(command=self.canvas.xview)
# Make the canvas expandable
self.master.rowconfigure(0, weight=1)
self.master.columnconfigure(0, weight=1)
# Bind events to the Canvas
self.canvas.bind('<ButtonPress-1>', self.move_from)
self.canvas.bind('<B1-Motion>', self.move_to)
self.canvas.bind('<MouseWheel>', self.wheel) # with Windows and MacOS, but not Linux
self.canvas.bind('<Button-5>', self.wheel) # only with Linux, wheel scroll down
self.canvas.bind('<Button-4>', self.wheel) # only with Linux, wheel scroll up
# Show image and plot some random test rectangles on the canvas
self.imscale = 1.0
self.imageid = None
self.delta = 0.75
width, height = self.image.size
minsize, maxsize = 5, 20
for n in range(10):
x0 = random.randint(0, width - maxsize)
y0 = random.randint(0, height - maxsize)
x1 = x0 + random.randint(minsize, maxsize)
y1 = y0 + random.randint(minsize, maxsize)
color = ('red', 'orange', 'yellow', 'green', 'blue')[random.randint(0, 4)]
self.canvas.create_rectangle(x0, y0, x1, y1, outline='black', fill=color,
activefill='black', tags=n)
# Text is used to set proper coordinates to the image. You can make it invisible.
self.text = self.canvas.create_text(0, 0, anchor='nw', text='Scroll to zoom')
self.show_image()
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
def move_from(self, event):
''' Remember previous coordinates for scrolling with the mouse '''
self.canvas.scan_mark(event.x, event.y)
def move_to(self, event):
''' Drag (move) canvas to the new position '''
self.canvas.scan_dragto(event.x, event.y, gain=1)
def wheel(self, event):
''' Zoom with mouse wheel '''
scale = 1.0
# Respond to Linux (event.num) or Windows (event.delta) wheel event
if event.num == 5 or event.delta == -120:
scale *= self.delta
self.imscale *= self.delta
if event.num == 4 or event.delta == 120:
scale /= self.delta
self.imscale /= self.delta
# Rescale all canvas objects
x = self.canvas.canvasx(event.x)
y = self.canvas.canvasy(event.y)
self.canvas.scale('all', x, y, scale, scale)
self.show_image()
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
def show_image(self):
''' Show image on the Canvas '''
if self.imageid:
self.canvas.delete(self.imageid)
self.imageid = None
self.canvas.imagetk = None # delete previous image from the canvas
width, height = self.image.size
new_size = int(self.imscale * width), int(self.imscale * height)
imagetk = ImageTk.PhotoImage(self.image.resize(new_size))
# Use self.text object to set proper coordinates
self.imageid = self.canvas.create_image(self.canvas.coords(self.text),
anchor='nw', image=imagetk)
self.canvas.lower(self.imageid) # set it into background
self.canvas.imagetk = imagetk # keep an extra reference to prevent garbage-collection
path = 'doge2.jpg' # place path to your image here
root = tk.Tk()
app = Zoom(root, path=path)
root.mainloop()
6
鼠标事件是以“屏幕坐标”的形式报告的。当你有一个可以滚动的画布时,通常需要将这些坐标转换为“画布坐标”(也就是滚动区域的坐标)。
比如说,当你想要放大某个焦点时:
true_x = canvas.canvasx(event.x)
true_y = canvas.canvasy(event.y)