"""Drag-and-drop support for Tkinter.This is very preliminary. I currently only support dnd *within* oneapplication, between different windows (or within the same window).I am trying to make this as generic as possible -- not dependent onthe use of a particular widget or icon type, etc. I also hope thatthis will work with Pmw.To enable an object to be dragged, you must create an event bindingfor it that starts the drag-and-drop process. Typically, you shouldbind <ButtonPress> to a callback function that you write. The functionshould call Tkdnd.dnd_start(source, event), where 'source' is theobject to be dragged, and 'event' is the event that invoked the call(the argument to your callback function). Even though this is a classinstantiation, the returned instance should not be stored -- it willbe kept alive automatically for the duration of the drag-and-drop.When a drag-and-drop is already in process for the Tk interpreter, thecall is *ignored*; this normally averts starting multiple simultaneousdnd processes, e.g. because different button callbacks alldnd_start().The object is *not* necessarily a widget -- it can be anyapplication-specific object that is meaningful to potentialdrag-and-drop targets.Potential drag-and-drop targets are discovered as follows. Wheneverthe mouse moves, and at the start and end of a drag-and-drop move, theTk widget directly under the mouse is inspected. This is the targetwidget (not to be confused with the target object, yet to bedetermined). If there is no target widget, there is no dnd targetobject. If there is a target widget, and it has an attributednd_accept, this should be a function (or any callable object). Thefunction is called as dnd_accept(source, event), where 'source' is theobject being dragged (the object passed to dnd_start() above), and'event' is the most recent event object (generally a <Motion> event;it can also be <ButtonPress> or <ButtonRelease>). If the dnd_accept()function returns something other than None, this is the new dnd targetobject. If dnd_accept() returns None, or if the target widget has nodnd_accept attribute, the target widget's parent is considered as thetarget widget, and the search for a target object is repeated fromthere. If necessary, the search is repeated all the way up to theroot widget. If none of the target widgets can produce a targetobject, there is no target object (the target object is None).The target object thus produced, if any, is called the new targetobject. It is compared with the old target object (or None, if therewas no old target widget). There are several cases ('source' is thesource object, and 'event' is the most recent event object):- Both the old and new target objects are None. Nothing happens.- The old and new target objects are the same object. Its methoddnd_motion(source, event) is called.- The old target object was None, and the new target object is notNone. The new target object's method dnd_enter(source, event) iscalled.- The new target object is None, and the old target object is notNone. The old target object's method dnd_leave(source, event) iscalled.- The old and new target objects differ and neither is None. The oldtarget object's method dnd_leave(source, event), and then the newtarget object's method dnd_enter(source, event) is called.Once this is done, the new target object replaces the old one, and theTk mainloop proceeds. The return value of the methods mentioned aboveis ignored; if they raise an exception, the normal exception handlingmechanisms take over.The drag-and-drop processes can end in two ways: a final target objectis selected, or no final target object is selected. When a finaltarget object is selected, it will always have been notified of thepotential drop by a call to its dnd_enter() method, as describedabove, and possibly one or more calls to its dnd_motion() method; itsdnd_leave() method has not been called since the last call todnd_enter(). The target is notified of the drop by a call to itsmethod dnd_commit(source, event).If no final target object is selected, and there was an old targetobject, its dnd_leave(source, event) method is called to complete thednd sequence.Finally, the source object is notified that the drag-and-drop processis over, by a call to source.dnd_end(target, event), specifying eitherthe selected target object, or None if no target object was selected.The source object can use this to implement the commit action; this issometimes simpler than to do it in the target's dnd_commit(). Thetarget's dnd_commit() method could then simply be aliased todnd_leave().At any time during a dnd sequence, the application can cancel thesequence by calling the cancel() method on the object returned bydnd_start(). This will call dnd_leave() if a target is currentlyactive; it will never call dnd_commit()."""import tkinter# The factory functiondef dnd_start(source, event):h = DndHandler(source, event)if h.root:return helse:return None# The class that does the workclass DndHandler:root = Nonedef __init__(self, source, event):if event.num > 5:returnroot = event.widget._root()try:root.__dndreturn # Don't start recursive dndexcept AttributeError:root.__dnd = selfself.root = rootself.source = sourceself.target = Noneself.initial_button = button = event.numself.initial_widget = widget = event.widgetself.release_pattern = "<B%d-ButtonRelease-%d>" % (button, button)self.save_cursor = widget['cursor'] or ""widget.bind(self.release_pattern, self.on_release)widget.bind("<Motion>", self.on_motion)widget['cursor'] = "hand2"def __del__(self):root = self.rootself.root = Noneif root:try:del root.__dndexcept AttributeError:passdef on_motion(self, event):x, y = event.x_root, event.y_roottarget_widget = self.initial_widget.winfo_containing(x, y)source = self.sourcenew_target = Nonewhile target_widget:try:attr = target_widget.dnd_acceptexcept AttributeError:passelse:new_target = attr(source, event)if new_target:breaktarget_widget = target_widget.masterold_target = self.targetif old_target is new_target:if old_target:old_target.dnd_motion(source, event)else:if old_target:self.target = Noneold_target.dnd_leave(source, event)if new_target:new_target.dnd_enter(source, event)self.target = new_targetdef on_release(self, event):self.finish(event, 1)def cancel(self, event=None):self.finish(event, 0)def finish(self, event, commit=0):target = self.targetsource = self.sourcewidget = self.initial_widgetroot = self.roottry:del root.__dndself.initial_widget.unbind(self.release_pattern)self.initial_widget.unbind("<Motion>")widget['cursor'] = self.save_cursorself.target = self.source = self.initial_widget = self.root = Noneif target:if commit:target.dnd_commit(source, event)else:target.dnd_leave(source, event)finally:source.dnd_end(target, event)# ----------------------------------------------------------------------# The rest is here for testing and demonstration purposes only!class Icon:def __init__(self, name):self.name = nameself.canvas = self.label = self.id = Nonedef attach(self, canvas, x=10, y=10):if canvas is self.canvas:self.canvas.coords(self.id, x, y)returnif self.canvas:self.detach()if not canvas:returnlabel = tkinter.Label(canvas, text=self.name,borderwidth=2, relief="raised")id = canvas.create_window(x, y, window=label, anchor="nw")self.canvas = canvasself.label = labelself.id = idlabel.bind("<ButtonPress>", self.press)def detach(self):canvas = self.canvasif not canvas:returnid = self.idlabel = self.labelself.canvas = self.label = self.id = Nonecanvas.delete(id)label.destroy()def press(self, event):if dnd_start(self, event):# where the pointer is relative to the label widget:self.x_off = event.xself.y_off = event.y# where the widget is relative to the canvas:self.x_orig, self.y_orig = self.canvas.coords(self.id)def move(self, event):x, y = self.where(self.canvas, event)self.canvas.coords(self.id, x, y)def putback(self):self.canvas.coords(self.id, self.x_orig, self.y_orig)def where(self, canvas, event):# where the corner of the canvas is relative to the screen:x_org = canvas.winfo_rootx()y_org = canvas.winfo_rooty()# where the pointer is relative to the canvas widget:x = event.x_root - x_orgy = event.y_root - y_org# compensate for initial pointer offsetreturn x - self.x_off, y - self.y_offdef dnd_end(self, target, event):passclass Tester:def __init__(self, root):self.top = tkinter.Toplevel(root)self.canvas = tkinter.Canvas(self.top, width=100, height=100)self.canvas.pack(fill="both", expand=1)self.canvas.dnd_accept = self.dnd_acceptdef dnd_accept(self, source, event):return selfdef dnd_enter(self, source, event):self.canvas.focus_set() # Show highlight borderx, y = source.where(self.canvas, event)x1, y1, x2, y2 = source.canvas.bbox(source.id)dx, dy = x2-x1, y2-y1self.dndid = self.canvas.create_rectangle(x, y, x+dx, y+dy)self.dnd_motion(source, event)def dnd_motion(self, source, event):x, y = source.where(self.canvas, event)x1, y1, x2, y2 = self.canvas.bbox(self.dndid)self.canvas.move(self.dndid, x-x1, y-y1)def dnd_leave(self, source, event):self.top.focus_set() # Hide highlight borderself.canvas.delete(self.dndid)self.dndid = Nonedef dnd_commit(self, source, event):self.dnd_leave(source, event)x, y = source.where(self.canvas, event)source.attach(self.canvas, x, y)def test():root = tkinter.Tk()root.geometry("+1+1")tkinter.Button(command=root.quit, text="Quit").pack()t1 = Tester(root)t1.top.geometry("+1+60")t2 = Tester(root)t2.top.geometry("+120+60")t3 = Tester(root)t3.top.geometry("+240+60")i1 = Icon("ICON1")i2 = Icon("ICON2")i3 = Icon("ICON3")i1.attach(t1.canvas)i2.attach(t2.canvas)i3.attach(t3.canvas)root.mainloop()if __name__ == '__main__':test()
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。