2014-09-11 185 views
4

这里是我想要的内容的描述: 在tkinter画布中绘制几何对象(这里是矩形)的集合,并且可以使用鼠标浏览此画布。点击并拖动画布,滚动放大并缩小。用鼠标移动和缩放tkinter画布

使用这个话题,我找到的点击和拖动部分: Move a tkinter canvas with Mouse 用鼠标

我设法写点东西滚动放大。 移动和缩放都可以单独使用。

问题: 如果我移动然后放大,缩放的焦点不再是光标的位置。

有什么建议吗?

下面一段代码来测试

[编辑:现在应该适用于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() 
+0

当我运行的代码我不能缩放在Windows上即时通讯。 – W1ll1amvl 2014-09-11 19:37:32

+0

我为Windows兼容性添加了一些行。不知道它是否会起作用... – 2014-09-12 12:57:42

+0

不管它仍然不起作用?它是否始终专注于相同的位置,即始终是右侧的一小部分,还是随机的? – W1ll1amvl 2014-09-13 01:57:23

回答

4

鼠标事件被报道在 '屏幕坐标'。当你有一个滚动的 画布时,你经常需要将这些数字转换成'canvas(即scrollregion) 坐标'。

例如。对于您的变焦对焦:

true_x = canvas.canvasx(event.x) 
true_y = canvas.canvasy(event.y) 
1

这是一个简化的缩放示例。您应该使用更先进的技术,以便不用大型缩放大型图像填充内存。

不要忘记在脚本的末尾放置一个指向图像的路径。

P.S.对于高级放大示例look here

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