18 KiB
wxPython:拖放简介
原文:https://www.blog.pythonlibrary.org/2012/06/20/wxpython-introduction-to-drag-and-drop/
这个时代的大多数计算机用户本能地使用拖放(DnD)。这周你可能用它把一些文件从一个文件夹转移到另一个文件夹。wxPython GUI 工具包提供了内置的拖放功能。在本教程中,我们将看到它是多么容易实现!
入门指南
wxPython 提供了几种不同的拖放方式。您可以有以下类型之一:
- wx(地名)。文件 DropTarget
- wx。TextDropTarget
- wx。PyDropTarget
前两个非常简单明了。最后一个,wx。PyDropTarget 只是 wx 的一个松散包装。DropTarget 本身。它增加了一些额外的方便方法。DropTarget 没有。我们从 wx 开始。FileDropTarget 示例。
创建 FileDropTarget
拖放文件
wxPython 工具包使得创建拖放目标变得非常简单。您确实需要重写一个方法来使它正常工作,但是除此之外,它非常简单。让我们花点时间看看这个示例代码,然后花点时间解释它。
import wx
########################################################################
class MyFileDropTarget(wx.FileDropTarget):
""""""
#----------------------------------------------------------------------
def __init__(self, window):
"""Constructor"""
wx.FileDropTarget.__init__(self)
self.window = window
#----------------------------------------------------------------------
def OnDropFiles(self, x, y, filenames):
"""
When files are dropped, write where they were dropped and then
the file paths themselves
"""
self.window.SetInsertionPointEnd()
self.window.updateText("\n%d file(s) dropped at %d,%d:\n" %
(len(filenames), x, y))
for filepath in filenames:
self.window.updateText(filepath + '\n')
########################################################################
class DnDPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
file_drop_target = MyFileDropTarget(self)
lbl = wx.StaticText(self, label="Drag some files here:")
self.fileTextCtrl = wx.TextCtrl(self,
style=wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY)
self.fileTextCtrl.SetDropTarget(file_drop_target)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(lbl, 0, wx.ALL, 5)
sizer.Add(self.fileTextCtrl, 1, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def SetInsertionPointEnd(self):
"""
Put insertion point at end of text control to prevent overwriting
"""
self.fileTextCtrl.SetInsertionPointEnd()
#----------------------------------------------------------------------
def updateText(self, text):
"""
Write text to the text control
"""
self.fileTextCtrl.WriteText(text)
########################################################################
class DnDFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="DnD Tutorial")
panel = DnDPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DnDFrame()
app.MainLoop()
那还不算太糟,是吗?首先要做的是子类化 wx。FileDropTarget ,这是我们用我们的 MyFileDropTarget 类完成的。在里面我们有一个被覆盖的方法, OnDropFiles 。它接受鼠标的 x/y 位置和放置的文件路径,然后将它们写出到文本控件中。要将拖放目标与文本控件挂钩,您需要查看 DnDPanel 类,在该类中我们调用文本控件的 SetDropTarget 方法,并将其设置为 drop target 类的一个实例。我们的 panel 类中还有两个方法,drop target 类调用它们来更新文本控件:SetInsertionPointEnd 和 updateText。注意,因为我们将面板对象作为放置目标传递,所以我们可以随意调用这些方法。如果 TextCtrl 是 drop 目标,我们必须做不同的事情,我们将在下一个例子中看到!
创建 TextDropTarget
拖放文本
wx。当您希望能够将一些选定的文本拖放到文本控件中时,可以使用 TextDropTarget。最常见的例子之一可能是将网页上的 URL 拖到地址栏中,或者将一些文本拖到 Firefox 的搜索框中。让我们花一些时间来学习如何在 wxPython 中创建这种拖放目标!
import wx
########################################################################
class MyTextDropTarget(wx.TextDropTarget):
#----------------------------------------------------------------------
def __init__(self, textctrl):
wx.TextDropTarget.__init__(self)
self.textctrl = textctrl
#----------------------------------------------------------------------
def OnDropText(self, x, y, text):
self.textctrl.WriteText("(%d, %d)\n%s\n" % (x, y, text))
#----------------------------------------------------------------------
def OnDragOver(self, x, y, d):
return wx.DragCopy
########################################################################
class DnDPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
lbl = wx.StaticText(self, label="Drag some text here:")
self.myTextCtrl = wx.TextCtrl(self,
style=wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY)
text_dt = MyTextDropTarget(self.myTextCtrl)
self.myTextCtrl.SetDropTarget(text_dt)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.myTextCtrl, 1, wx.EXPAND)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def WriteText(self, text):
self.text.WriteText(text)
########################################################################
class DnDFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="DnD Text Tutorial")
panel = DnDPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DnDFrame()
app.MainLoop()
我们必须再次子类化我们的拖放目标类。在这种情况下,我们称之为 MyTextDropTarget 。在那个类中,我们必须覆盖 OnDropText 和 OnDragOver 。我无法找到关于后者的令人满意的文档,但我猜它只是返回了被拖动数据的副本。OnDropText 方法将文本写出到文本控件中。注意,由于我们已经将放置目标直接绑定到文本控件(参见 panel 类),我们必须使用名为 WriteText 的方法来更新文本控件。如果您更改它,您会收到一条错误消息。
带有 PyDropTarget 的自定义 DnD
使用 PyDropTarget 拖放 URL
如果你还没有猜到,这些例子是来自官方 wxPython 演示的 DnD 演示的稍微修改版本。我们将使用一些基于他们的 URLDragAndDrop 演示的代码来解释 PyDropTarget。这个演示的有趣之处在于,您不仅可以创建一个可以接受拖动文本的小部件,还可以将另一个小部件中的一些文本拖回您的浏览器!让我们来看看:
import wx
########################################################################
class MyURLDropTarget(wx.PyDropTarget):
#----------------------------------------------------------------------
def __init__(self, window):
wx.PyDropTarget.__init__(self)
self.window = window
self.data = wx.URLDataObject();
self.SetDataObject(self.data)
#----------------------------------------------------------------------
def OnDragOver(self, x, y, d):
return wx.DragLink
#----------------------------------------------------------------------
def OnData(self, x, y, d):
if not self.GetData():
return wx.DragNone
url = self.data.GetURL()
self.window.AppendText(url + "\n")
return d
#######################################################################
class DnDPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD, False)
# create and setup first set of widgets
lbl = wx.StaticText(self, label="Drag some URLS from your browser here:")
lbl.SetFont(font)
self.dropText = wx.TextCtrl(self, size=(200,200),
style=wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY)
dt = MyURLDropTarget(self.dropText)
self.dropText.SetDropTarget(dt)
firstSizer = self.addWidgetsToSizer([lbl, self.dropText])
# create and setup second set of widgets
lbl = wx.StaticText(self, label="Drag this URL to your browser:")
lbl.SetFont(font)
self.draggableURLText = wx.TextCtrl(self, value="http://www.mousevspython.com")
self.draggableURLText.Bind(wx.EVT_MOTION, self.OnStartDrag)
secondSizer = self.addWidgetsToSizer([lbl, self.draggableURLText])
# Add sizers to main sizer
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(firstSizer, 0, wx.EXPAND)
mainSizer.Add(secondSizer, 0, wx.EXPAND)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def addWidgetsToSizer(self, widgets):
"""
Returns a sizer full of widgets
"""
sizer = wx.BoxSizer(wx.HORIZONTAL)
for widget in widgets:
if isinstance(widget, wx.TextCtrl):
sizer.Add(widget, 1, wx.EXPAND|wx.ALL, 5)
else:
sizer.Add(widget, 0, wx.ALL, 5)
return sizer
#----------------------------------------------------------------------
def OnStartDrag(self, evt):
""""""
if evt.Dragging():
url = self.draggableURLText.GetValue()
data = wx.URLDataObject()
data.SetURL(url)
dropSource = wx.DropSource(self.draggableURLText)
dropSource.SetData(data)
result = dropSource.DoDragDrop()
########################################################################
class DnDFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="DnD URL Tutorial", size=(800,600))
panel = DnDPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DnDFrame()
app.MainLoop()
第一个类是我们的拖放目标类。这里我们创建一个 wx。URLDataObject 存储我们的 URL 信息。然后在 OnData 方法中,我们提取 URL 并将其添加到绑定的文本控件中。在我们的 panel 类中,我们用与其他两个例子中相同的方式来连接拖放目标,所以我们将跳过它,继续学习新的内容。第二个文本控件是我们需要注意的地方。在这里,我们发现它通过 EVT 运动鼠标运动。在鼠标移动事件处理程序(OnStartDrag)中,我们检查以确保用户正在拖动。如果是,那么我们从文本框中获取值,并将其添加到新创建的 URLDataObject 中。接下来,我们创建一个 DropSource 的实例,并向其传递我们的第二个文本控件,因为它是源。我们将源的数据设置为 URLDataObject。最后,我们在我们的拖放源(文本控件)上调用 DoDragDrop ,它将通过移动、复制、取消或失败做出响应。如果您将 URL 拖到浏览器的地址栏,它会复制。否则很可能行不通。现在让我们利用我们所学的,创造一些原创的东西!
创建自定义拖放应用程序
使用 ObjectListView 拖放
我认为使用文件拖放目标演示程序并把它做成带有 ObjectListView 小部件(一个 ListCtrl 包装器)的东西会很有趣,它可以告诉我们一些关于我们正在放入的文件的信息。我们将显示以下信息:文件名,创建日期,修改日期和文件大小。代码如下:
import os
import stat
import time
import wx
from ObjectListView import ObjectListView, ColumnDefn
########################################################################
class MyFileDropTarget(wx.FileDropTarget):
""""""
#----------------------------------------------------------------------
def __init__(self, window):
"""Constructor"""
wx.FileDropTarget.__init__(self)
self.window = window
#----------------------------------------------------------------------
def OnDropFiles(self, x, y, filenames):
"""
When files are dropped, update the display
"""
self.window.updateDisplay(filenames)
########################################################################
class FileInfo(object):
""""""
#----------------------------------------------------------------------
def __init__(self, path, date_created, date_modified, size):
"""Constructor"""
self.name = os.path.basename(path)
self.path = path
self.date_created = date_created
self.date_modified = date_modified
self.size = size
########################################################################
class MainPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
self.file_list = []
file_drop_target = MyFileDropTarget(self)
self.olv = ObjectListView(self, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.olv.SetDropTarget(file_drop_target)
self.setFiles()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.olv, 1, wx.EXPAND)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def updateDisplay(self, file_list):
""""""
for path in file_list:
file_stats = os.stat(path)
creation_time = time.strftime("%m/%d/%Y %I:%M %p",
time.localtime(file_stats[stat.ST_CTIME]))
modified_time = time.strftime("%m/%d/%Y %I:%M %p",
time.localtime(file_stats[stat.ST_MTIME]))
file_size = file_stats[stat.ST_SIZE]
if file_size > 1024:
file_size = file_size / 1024.0
file_size = "%.2f KB" % file_size
self.file_list.append(FileInfo(path,
creation_time,
modified_time,
file_size))
self.olv.SetObjects(self.file_list)
#----------------------------------------------------------------------
def setFiles(self):
""""""
self.olv.SetColumns([
ColumnDefn("Name", "left", 220, "name"),
ColumnDefn("Date created", "left", 150, "date_created"),
ColumnDefn("Date modified", "left", 150, "date_modified"),
ColumnDefn("Size", "left", 100, "size")
])
self.olv.SetObjects(self.file_list)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="OLV DnD Tutorial", size=(800,600))
panel = MainPanel(self)
self.Show()
#----------------------------------------------------------------------
def main():
""""""
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
if __name__ == "__main__":
main()
大部分你以前见过的东西。我们有自己的 FileDropTarget 子类,我们将面板连接到它,然后将 ObjectListView 小部件连接到 DropTarget 实例。我们还有一个保存文件相关数据的泛型类。如果你运行这个程序并把文件夹放入其中,你将不会得到正确的文件大小。您可能需要遍历文件夹,并添加其中文件的大小,以使其工作。你可以自己解决这个问题。无论如何,程序的核心是在更新显示方法中。在这里,我们抓取文件的重要统计数据,并将其转换为更可读的格式,因为大多数人不理解从纪元开始以秒为单位的日期。一旦我们对数据稍加处理,我们就显示它。这不是很酷吗?
包扎
至此,您应该知道如何在 wxPython 中进行至少 3 种不同类型的拖放操作。希望您能负责任地使用这些新信息,并在不久的将来创建一些新的开源应用程序。祝你好运!
进一步阅读
- wxPython dragandrop维基页面
- zetcode 的 wxPython DragAndDrop 页面
- DropTarget 文档
- wxPython:使用 ObjectListView 代替 ListCtrl



