Tkinter:迷你 canvas windows 作为滑块

Tkinter: mini canvas windows as a slider

我是 Tkinter 的新手。我想用光标和小 window:

来控制图像的移动

enter image description here

我试过这段代码,但结果并不是我想要的。

import tkinter as tk
from io import BytesIO
import requests
from PIL import Image , ImageTk


def full_dimensions(imag_fs):
    top = tk.Toplevel(root)
    img = tk.Label(top, image=imag_fs)
    img.pack()


def get_image():
    _url = 'https://i.imgur.com/4m7AHVu.gif'
    _img = requests.get(_url)
    if _img.status_code == 200:
        _content = BytesIO(_img.content)
    else:
        _content = 'error.gif'
    print('image loaded')
    return _content


root = tk.Tk()

_content =  get_image()   
_x = Image.open(_content)
imag_fs = ImageTk.PhotoImage(_x)
_x.thumbnail((100, 100), Image.ANTIALIAS)

imag = ImageTk.PhotoImage(_x)
img = tk.Button(root, image=imag, command=lambda: full_dimensions(imag_fs))
img.grid(column=3, row=1)

root.mainloop()

我测试了一个window,但是当我导入图片时我无法控制

import tkinter as tk

main_window = tk.Tk()


def check_hand_enter():
    canvas.config(cursor="hand1")


def check_hand_leave():
    canvas.config(cursor="")


canvas = tk.Canvas(width=200, height=200)
tag_name = "polygon"

canvas.create_polygon((25, 25), (25, 100), (125, 100), (125, 25), outline='black', fill="", tag=tag_name)

canvas.tag_bind(tag_name, "<Enter>", lambda event: check_hand_enter())
canvas.tag_bind(tag_name, "<Leave>", lambda event: check_hand_leave())

canvas.pack()
main_window.mainloop()

下面是我将如何做到这一点(一个简单的例子):

# import all necessary modules and classes
from tkinter import Tk, Canvas, Frame
from PIL import Image, ImageTk
import requests

# checking if the file exists, if it doesn't exist download it, if can't download it, exit the program
try:
    open('space.jpg')
except FileNotFoundError:
    url = 'https://images5.alphacoders.com/866/866360.jpg'
    img = requests.get(url)
    if img.status_code == 200:
        with open('space.jpg', 'wb') as file:
            file.write(img.content)
        print('File not found. Downloaded the necessary file.')
    else:
        print('File not found. Could not download the necessary file.')
        exit()


class MovableImage(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.parent = parent

        # dictionary for storing information about movement
        self.start_coords = {'x': 0, 'y': 0, 'move': False}

        # dictionary for storing information about movement
        self.start_coords_main = {'x': 0, 'y': 0, 'move': False}

        # loads the image
        self.image = Image.open('space.jpg')
        # sets the images to their corresponding variables so that they can be referenced later
        # resizes the smaller image to fit the navigation window
        self.main_image = ImageTk.PhotoImage(self.image)
        self.nav_image = ImageTk.PhotoImage(self.image.resize((200, 100), Image.ANTIALIAS))

        # creates the canvas to store the bigger image on
        self.main_canvas = Canvas(self, width=700, height=500, highlightthickness=0)
        self.main_canvas.pack()
        # puts image on canvas
        self.main_image_id = self.main_canvas.create_image((0, 0), image=self.main_image, anchor='nw', tags='main_image')

        # creates the smaller canvas that will be used for navigation
        self.nav_canvas = Canvas(self.main_canvas, width=200, height=100, highlightthickness=0)
        # adds the smaller canvas as a window to the main_canvas
        self.main_canvas.create_window((500, 400), window=self.nav_canvas, anchor='nw', tags='nav_canvas')
        # adds the resized image to nav_canvas
        self.nav_canvas.create_image((0, 0), image=self.nav_image, anchor='nw')
        # creates a rectangle to indicate the current view of the image
        self.nav_box = self.nav_canvas.create_rectangle((0, 0, 70, 50), outline='white')

        # binds functions
        self.main_canvas.bind('<Button-1>', self.set_start_coords_main)
        self.main_canvas.bind('<B1-Motion>', self.move_coords_main)

        # binds functions
        self.nav_canvas.bind('<Button-1>', self.set_start_coords)
        self.nav_canvas.bind('<B1-Motion>', self.move_coords)

    # function that sets the starting coords so that they can be referenced later, also sets whether the box can be moved at all
    def set_start_coords(self, event):
        x1, y1, x2, y2 = self.nav_canvas.coords(self.nav_box)
        if x1 < event.x < x2 and y1 < event.y < y2:
            self.start_coords['x'] = event.x - x1
            self.start_coords['y'] = event.y - y1
            self.start_coords['move'] = True
        else:
            self.start_coords['move'] = False

    # the moving part, this takes reference from the starting coords and uses them for calculation
    # basic border checks and then the actual moving
    def move_coords(self, event):
        if not self.start_coords['move']:
            return

        dx = self.start_coords['x']
        dy = self.start_coords['y']
        x = event.x - dx
        y = event.y - dy

        if x < 0:
            x = 0
        elif x + 70 > 200:
            x = 130
        if y < 0:
            y = 0
        elif y + 50 > 100:
            y = 50

        self.nav_canvas.coords(self.nav_box, x, y, x + 70, y + 50)
        self.main_canvas.coords(self.main_image_id, -x * 10, -y * 10)

    # function that sets the starting coords so that they can be referenced later, also sets whether the box can be moved at all
    def set_start_coords_main(self, event):
        x1, y1, x2, y2 = self.main_canvas.bbox('main_image')
        if x1 < event.x < x2 and y1 < event.y < y2:
            self.start_coords_main['x'] = event.x - x1
            self.start_coords_main['y'] = event.y - y1
            self.start_coords_main['move'] = True
        else:
            self.start_coords_main['move'] = False

    # the moving part, this takes reference from the starting coords and uses them for calculation
    # basic border checks and then the actual moving
    def move_coords_main(self, event):
        if not self.start_coords_main['move']:
            return

        dx = self.start_coords_main['x']
        dy = self.start_coords_main['y']
        x = event.x - dx
        y = event.y - dy

        if x < -1300:
            x = -1300
        elif x > 0:
            x = 0
        if y < -500:
            y = -500
        elif y > 0:
            y = 0

        self.nav_canvas.coords(self.nav_box, -x / 10, -y / 10, -x / 10 + 70, -y / 10 + 50)
        self.main_canvas.coords(self.main_image_id, x, y)


# basic Tk() instance and afterwards the root.mainloop()
root = Tk()

MovableImage(root).pack()

# mainloop
root.mainloop()

有几点值得一提:这是一个非常硬编码的示例,几乎只适用于分辨率为 2000x1000 像素的图像,其他图像可能无法正确显示或调整大小后效果不佳。对于这个问题,您将不得不自己解决或询问另一个问题,了解您在尝试调整此问题时遇到的问题。因此,如果可以将任何图像放在那里并且它可以工作,那就太好了。

关于代码,很简单:

导入模块

检查文件(可能有更好的方法,但这也有效),然后如果文件不存在就下载它,如果做不到,好吧,退出程序。

然后设置一些参考资料(一个字典,这样 global 就不必使用了,而且出于其他原因我会说字典更好太像所有必要的变量都在一个地方,例如 xy 变量名不是全局使用的)。

然后设置第一个函数,它将注册鼠标点击。这样做是为了获取鼠标相对于可移动方块的位置(基本上是鼠标在方块上的位置如果方块是独立的 window 或 sth) 并且可以移动,但在此之前函数会检查鼠标是否在方块内。如果不移动被禁用,例如,如果您要在正方形外单击并开始移动,它不会那样做。

那么移动函数就定义好了。在那里检索相对的 x 和 y 坐标,但首先它检查它是否可以移动,如果不能,它停止执行该函数。然后进行更多计算并检查边界(同样,这是非常硬编码的,应该更改为更动态的函数,以便它可以根据图片检测到边界)。然后是移动部分,它只是将导航框和实际图片移动到它们相应的位置(这有点硬编码,但基本上如果你要将比例保持在较小框的 1/10,那么这个特定部分将与不同的图像)。

然后是 基本 Tk() 启蒙,然后是 .mainloop()

中间只是打开图像并将其设置为 2 个变量并调整将转到导航框的那个的大小。

然后创建主要canvas,其中将显示主要图像。

然后将该图像添加到canvas并保留 id 引用以便稍后移动该图像。

创建较小的 canvas 并且不打包它或任何东西,而是将实例作为 window 添加到主 canvas这样它就在它上面(如您问题中的照片所示)。同样,它以硬编码方式放置。

然后将导航图像添加到导航canvas并添加将移动的小框。

然后绑定函数nav_canvas中的鼠标activity。

如果您有任何问题,请提问。 请注意, 如果您使用自己的图像,则 try/except 不是必需的,它将始终随程序一起提供。它在那里是因为您可能没有这张确切的图片以及那些确切的尺寸和那个确切的名称所以它有点临时用于测试目的。

编辑:将代码放在继承自 Frameclass 中,以便可以将其作为小部件放置。