青菜肉丝's Blog

Programming and More

wxpython的拖放(Drag and Drop)初探

青菜肉丝 posted @ 2010年10月01日 00:55 in python笔记 with tags python wxPython wxwidhget Drag and Drop 拖放 DnD , 4049 阅读

被wxpython的拖放(DnD,Drag and Drop)操作困扰了很久,因此有了这篇blog。

wxpython的DnD机制在网上的资料很少,往往只有语焉不详的例子。

那么,在这些范例背后所隐藏的,是怎样的DnD机制呢?

根据wx的文档,DnD本质上就是一个C&P的操作——一个数据传递的操作。

这个操作牵涉到哪些对象呢?

wxWindow类或者它的派生类的对象: srcWnd

wxWindow类或者它的派生类的对象: dstWnd

wxDropSource类的派生类的对象: dropSource

wxDropTarget类的派生类的对象:dropTarget

wxDataObject类的对象:dataObject

首先,srcWnd根据鼠标事件决定是否开始拖放。在一开始,产生一个dropSource,接着,将要拖放的数据(通过DataObject)和srcWnd放入dropSrouce中,然后,执行dropSource.DoDragDrop()。这样就进入了拖放的流程。

在DoDragDrop中,系统会不断检测鼠标的移动,一旦检测到鼠标所在的窗口可以接受dropSource中的数据时(检测要满足的条件如后所述),则将调用这个窗口拥有的dropTarget对象(这个对象一般在窗口创建时就建立了)的一系列函数:鼠标进入窗口时,调用dstWnd.dropTarget.OnEnter;鼠标离开窗口时,调用dstWnd.dropTarget.OnLeave;在窗口中移动时,调用dstWnd.dropTarget.OnDragOver;在松开鼠标按键(一般是左键)时,调用dstWnd.dropTarget.OnData。这样就完成了拖放的过程。

在完成了拖放过程之后,DoDragDrop的返回值将标识这次拖放是数据拷贝还是数据移动,源窗口可以根据这个返回值来处理源数据。这个返回值默认为wxDragCopy。一般是由dropTarget的OnData函数设定。

那么,在这个过程中,系统是如何知道目的窗口是可以接受源窗口所传递的数据呢?这就牵涉到一个数据格式的问题。

在wxWidgets中,数据格式通过wxDataFormat类来体现。wxDataFormat有两个成员:type和id,前者是一个整型,后者则是一个字符串。用户要么使用type来识别数据类型,要么使用id来识别数据类型。用户根据wxDataObject中的wxDataFormat信息,来决定如何处理通过wxDataObject::GetDataHere所获得的字节数组——换言之,根据格式决定数据的序列化和反序列化操作。

如上所述,系统也是根据dropSource中的格式和dropTarget中的格式是否一致来判定一个窗口是否能成为拖放目的地的。具体来说,判定过程如下:

1.鼠标所在的窗口有DropTarget对象

2.这个对象的DataObject中的格式与拖放源中的格式相同

以上即wxWidgets的DnD原理。在wxPython中,情形又有些细微的差别。这些差别主要体现在数据传递上。

wxPython为用户提供了DropDropTarget,TextDropTarget等简单的DropTarget派生类,用于处理常见的拖放操作,比如文字或者文件的拖放。用户想要处理这些拖放时,只要简单的继承这些类,并实现自己的OnDropText、OnDropFile等函数即可。

然而,要实现自定义的数据类的拖放,又应该怎么办?这时候就要用到wx.CustomDataObject这个类。这个类派生自wx.DataObjectSimple,一次只能持有一个数据,而不是DataObject里的多个数据。因此,也只有一种数据格式。这个数据格式在wx.CustomDataObject创建时通过构造函数传入。可以传入一个整形,或者是一个字符串。一般都是用字符串,否则全局的整形管理起来是相当麻烦的一件事情。

具体而言:

#产生数据源
			global ds
			ds=wx.DropSource(self)
			#产生数据对象
			tdo=wx.CustomDataObject("mydata")
			#设置数据对象的数据
			tdo.SetData("hello world")
			#设置数据源的数据对象
			ds.SetData(tdo)
			#开始拖放操作
			dragResult=ds.DoDragDrop()
			if dragResult==wx.DragCopy:
				pass
			elif dragResult==wx.DragMove:
				pass
			else:
				pass 

以上是使用wx.DropSource和wx.CustomDataObject的一段代码。这里要注意的是,这个数据源是全局的。之所以这么做的原因是,wxPython的DropTarget.OnData的接口中没有像C++的接口那样将数据源的数据对象传入。因此,只能通过这种设置全局变量的方式来获取数据源中的数据对象。

我们可以看到,这个数据源所携带的数据的类型id——在wx.CustomObject的构造函数中传入——是"mydata"。所以我们需要一个能处理“mydata”的DropSource:

class MyDropTarget(wx.PyDropTarget):
	def __init__(self,dstWnd):
		wx.PyDropTarget.__init__(self)
		#通过设置数据对象,设置DropTarget的数据格式
		cdo=wx.CustomDataObject("mydata")
		self.SetDataObject(cdo)
		self.target=dstWnd
		return
	def OnEnter(self,x,y,d):
		#print "OnEnter"
		if hasattr(self.target,"OnDragIn"):
			self.target.OnDragIn(x,y,d)
			return d
		return d
	def OnLeave(self):
		if hasattr(self.target,"OnDragOut"):
			self.target.OnDragOut()
		return
	def OnData(self,x,y,d):
		#print "on data,"+str(d)
		cdo=wx.CustomDataObject("mydata")
		#根据数据格式获取数据
		print ds.GetDataObject().GetDataHere(cdo.GetFormat())
		return d
	def OnDragOver(self,x,y,d):
		if hasattr(self.target,"OnDragOver"):
			self.target.OnDragOver(x,y,d)
		return d

上面这段代码中,在构造函数里设定了DropTarget的数据格式,使得系统能够知道:对“mydata”这类数据的拖放,持有这个MyDropTarget对象的窗体——dstWnd是可以接收源数据的。

在OnEnter、OnLeave、OnDragOver这几个函数中,都会检查和调用dstWnd中的函数,使得dstWnd有机会显示一些拖动的效果,比如改变背景色等。值得一提的是,dstWnd的这些函数中,在设置好拖动效果以后,都需要调用一下Refresh()函数,否则显示效果是不会改变的。

在OnData函数中,通过数据格式从全局的数据源——ds中获取数据。所获取的数据是一串string。这是wxPython的简化处理。这样可以利用python的pickle,将python对象序列化成string传递。换言之,在真正使用时,获取数据之后,还应该用pickle进行一次反序列化操作。

最后要提的是,一个窗口设定自己的DropTarget这项工作,是通过wx.Window.SetDropTarget这个函数来完成的。
 

以上即wxPython的DnD的大致内容,其实并不复杂。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter