简体   繁体   English

tkinter - 如何拖放小部件?

[英]tkinter - How to drag and drop widgets?

I am trying to make a Python program in which you can move around widgets.我正在尝试制作一个 Python 程序,您可以在其中移动小部件。

This is my code:这是我的代码:

import tkinter as tk 
main = tk.Tk()
notesFrame = tk.Frame(main, bd = 4, bg = "a6a6a6")
notesFrame.place(x=10,y=10)
notes = tk.Text(notesFrame)
notes.pack()
notesFrame.bind("<B1-Motion>", lambda event: notesFrame.place(x = event.x, y = event.y)

But, this gets super glitchy and the widget jumps back and forth.但是,这变得超级小故障,小部件来回跳动。

The behavior you're observing is caused by the fact that the event's coordinates are relative to the dragged widget.您观察到的行为是由事件的坐标相对于拖动的小部件这一事实引起的。 Updating the widget's position (in absolute coordinates) with relative coordinates obviously results in chaos.相对坐标更新小部件的位置(绝对坐标)显然会导致混乱。

To fix this, I've used the .winfo_x() and .winfo_y() functions (which allow to turn the relative coordinates into absolute ones), and the Button-1 event to determine the cursor's location on the widget when the drag starts.为了解决这个问题,我使用了.winfo_x().winfo_y()函数(允许将相对坐标转换为绝对坐标)和Button-1事件来确定拖动开始时光标在小部件上的位置.

Here's a function that makes a widget draggable:这是一个使小部件可拖动的函数:

def make_draggable(widget):
    widget.bind("<Button-1>", on_drag_start)
    widget.bind("<B1-Motion>", on_drag_motion)

def on_drag_start(event):
    widget = event.widget
    widget._drag_start_x = event.x
    widget._drag_start_y = event.y

def on_drag_motion(event):
    widget = event.widget
    x = widget.winfo_x() - widget._drag_start_x + event.x
    y = widget.winfo_y() - widget._drag_start_y + event.y
    widget.place(x=x, y=y)

It can be used like so:它可以像这样使用:

main = tk.Tk()

frame = tk.Frame(main, bd=4, bg="grey")
frame.place(x=10, y=10)
make_draggable(frame)

notes = tk.Text(frame)
notes.pack()

If you want to take a more object-oriented approach, you can write a mixin that makes all instances of a class draggable:如果您想采用更面向对象的方法,您可以编写一个mixin ,使类的所有实例都可拖动:

class DragDropMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        make_draggable(self)

Usage:用法:

# As always when it comes to mixins, make sure to
# inherit from DragDropMixin FIRST!
class DnDFrame(DragDropMixin, tk.Frame):
    pass

# This wouldn't work:
# class DnDFrame(tk.Frame, DragDropMixin):
#     pass

main = tk.Tk()

frame = DnDFrame(main, bd=4, bg="grey")
frame.place(x=10, y=10)

notes = tk.Text(frame)
notes.pack()

I came up with a different approach and it is useful when you want ALL of your widgets drag-able in the SAME window. I do also like the math used in this approach more than in the accepted answer.我想出了一种不同的方法,当您希望所有小部件都可以在同一个 window 中拖动时,它很有用。与公认的答案相比,我更喜欢这种方法中使用的数学。

The code is explained line by line as commented lines below:代码逐行解释如下注释行:

import tkinter as tk

def drag_widget(event):
    if (w:=root.dragged_widget): #walrus assignment
        cx,cy = w.winfo_x(), w.winfo_y() #current x and y
        #deltaX and deltaY to mouse position stored
        dx = root.marked_pointx - root.winfo_pointerx()
        dy = root.marked_pointy - root.winfo_pointery()
        #adjust widget by deltaX and deltaY
        w.place(x=cx-dx, y=cy-dy)
        #update the marked for next iteration
        root.marked_pointx = root.winfo_pointerx()
        root.marked_pointy = root.winfo_pointery()

def drag_init(event):
    if event.widget is not root:
        #store the widget that is clicked
        root.dragged_widget = event.widget
        #ensure dragged widget is ontop
        event.widget.lift()
        #store the currently mouse position
        root.marked_pointx = root.winfo_pointerx()
        root.marked_pointy = root.winfo_pointery()

def finalize_dragging(event):
    #default setup
    root.dragged_widget = None
    
root = tk.Tk()
#name and register some events to some sequences
root.event_add('<<Drag>>', '<B1-Motion>')
root.event_add('<<DragInit>>', '<ButtonPress-1>')
root.event_add('<<DragFinal>>', '<ButtonRelease-1>')
#bind named events to the functions that shall be executed
root.bind('<<DragInit>>', drag_init)
root.bind('<<Drag>>', drag_widget)
root.bind('<<DragFinal>>', finalize_dragging)
#fire the finalizer of dragging for setup
root.event_generate('<<DragFinal>>')
#populate the window
for color in ['yellow','red','green','orange']:
    tk.Label(root, text="test",bg=color).pack()

root.mainloop()

Tkinter has a module for this, documented in the module docstring. Tkinter 有一个模块,记录在模块文档字符串中。 It was expected that it would be replaced by a tk dnd module, but this has not happened.预计它将被 tk dnd 模块取代,但这并没有发生。 I have never tried it.我从来没有尝试过。 Searching SO for [tkinter] dnd returns this page .搜索[tkinter] dnd SO 会返回此页面 Below is the beginning of the docstring.下面是文档字符串的开头。

>>> from tkinter import dnd
>>> help(dnd)
Help on module tkinter.dnd in tkinter:

NAME
    tkinter.dnd - Drag-and-drop support for Tkinter.

DESCRIPTION
    This is very preliminary.  I currently only support dnd *within* one
    application, between different windows (or within the same window).
[snip]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM