level 8
小白羊的青春
楼主
你可以在你的应用程序中实现拖放源,拖放目标或者两者同时实现.
实现拖放源
要实现一个拖放源,也就是说要提供用户用于拖放操作的数据,你需要使用一个wxDropSource类的实例.要注意下面描述的事情都是在你的应用程序已 经认定一个拖放操作已经开始以后发生的.决定拖放是否开始的逻辑,是完全需要由应用程序自己决定的,一些控件会通过产生一个拖放开始事件来通知应用程序, 在这种情况下,你不需要自己关心这一部分的逻辑(对这一部分的逻辑的关心,可能会让你的鼠标事件处理代码变得混乱).在本章中,也会提供一个何时 wxWidgets通知你拖放操作开始的大概描述.
一个拖放源需要采取的动作包括下面几步:
1 准备工作
首先,必须先创建和初始化一个将被拖动的数据对象,如下所示:
wxTextDataObject myData(wxT("This text will be dragged."));
2 开始拖动 要开始拖动操作,最典型的方式是响应一个鼠标单击事件,创建一个wxDropSource对象,然后调用它的wxDropSource::DoDragDrop函数,如下所示:
wxDropSource dragSource(this);
dragSource.SetData(myData);
wxDragResult result = dragSource.DoDragDrop(wxDrag_AllowMove);
下表列出的标记,可以作为DoDragDrop函数的参数:
wxDrag_CopyOnly
只允许进行拷贝操作.
wxDrag_AllowMove
允许进行移动操作.
wxDrag_DefaultMove
默认操作是移动数据.
当创建wxDropSource对象的时候,你还可以指定发起拖动操作的窗口,并且可以选择拖动使用的光标,可选的范围包括拷贝,移动以 及不能释放等.这些光标在GTK+上是图标,而在别的平台上是光标,因此你需要使用wxDROP_ICON来屏蔽这种区别,正如我们很快就将看到的拖动文 本的例子中演示的那样.
3 拖动
对DoDragDrop函数的调用将会阻止应用程序进行其他处理,直到用户释放鼠标按钮(除非你重载了GiveFeedback函数以便 进行其他的特殊操作)当鼠标在应用程序的一个窗口上移动时,如果这个窗口可以识别这个拖动操作协议,对应的wxDropTarget函数就会被调用,参考 接下来的小节"实现一个拖放目的"
4 处理拖放结果
DoDragDrop函数返回一个拖放操作的结果,这个返回值的类型为wxDragResult,它的枚举值如下表所示:
wxDragError
在拖动操作的执行过程中出现了错误.
wxDragNone
数据源不被拖动目的接受.
wxDragCopy
数据已经被成功拷贝.
wxDragMove
数据已经被成功移动(仅适用于Windows).
wxDragLink
以完全一个链接操作.
wxDragCancel
用户已经取消了拖放操作.
你的应用程序可以针对不同的返回值进行自己的操作,如果返回值是wxDragMove,通常你需要删除绑定在数据源中的数据,然后更新屏幕显示.而如果返回值是wxDragNone,则表示拖动操作已经被取消了.下面举例说明:
switch (result)
{
case wxDragCopy: /* 数据被拷贝或者被链接:
无需特别操作 */
case wxDragLink:
break;
case wxDragMove: /* 数据被移动,删除原始数据 */
DeleteMyDraggedData();
break;
default: /* 操作被取消或者数据不被接受
或者发生了错误:
不做任何操作 */
break;
}
下面的例子演示了怎样实现一个文本数据拖放源.DnDWindow包含一个m_strText成员变量,当鼠标左键按下的时候,针对 m_strText的拖放操作开始,拖放操作的结果通过一个消息框显示.另外,拖放操作将会在鼠标已经拖动了一小段距离后才会开始,因此单击鼠标动作并不 会导致一个拖放操作.
void DnDWindow::OnLeftDown(wxMouseEvent& event )
{
if ( !m_strText.IsEmpty() )
{
// 开始拖动操作
wxTextDataObject textData(m_strText);
wxDropSource source(textData, this,
wxDROP_ICON(dnd_copy),
wxDROP_ICON(dnd_move),
wxDROP_ICON(dnd_none));
int flags = 0;
if ( m_moveByDefault )
flags |= wxDrag_DefaultMove;
else if ( m_moveAllow )
flags |= wxDrag_AllowMove;
wxDragResult result = source.DoDragDrop(flags);
const wxChar *pc;
switch ( result )
{
case wxDragError: pc = wxT("Error!"); break;
case wxDragNone: pc = wxT("Nothing"); break;
case wxDragCopy: pc = wxT("Copied"); break;
case wxDragMove: pc = wxT("Moved"); break;
case wxDragCancel: pc = wxT("Cancelled"); break;
default: pc = wxT("Huh?"); break;
}
wxMessageBox(wxString(wxT("Drag result: ")) + pc);
}
}
实现一个拖放目的
要实现一个拖放目的,也就是说要接收用户拖动的数据,你需要使用wxWindow::SetDropTarget函数,将某个窗口和一个 wxDropTarget绑定在一起,你需要实现一个wxDropTarget的派生类,并且重载它的所有纯虚函数.另外还需要重载OnDragOver 函数,以便返回一个wxDragResult类型的返回码,以说明当鼠标指针移过这个窗口的时候,光标应该怎样显示,并且重载OnData函数来实现放置 操作.你还可以通过继承wxTexTDropTarget或者wxFileDropTarget,或者重载它们的OnDropText或者 OnDropFiles函数来实现一个拖放目的.
下面的步骤将发生在拖放操作过程当中的拖放目的对象上.
1 初始化
wxWindow::SetDropTarget函数在窗口创建期间被调用,以便将其和一个拖放目的对象绑定.在窗口创建或者应用程序的 其他某个部分,通过函数wxDropTarget::SetDataObject,拖放目的对象和某一种数据类型绑定,这种数据类型将用来作为拖放源和播 放目的进行协商的依据.
2 拖动
当鼠标在拖放目的上以拖动的方式移动时,wxDropTarget::OnEnter,wxDropTarget:: OnDragOver和 wxDropTarget::OnLeave函数将在适当的时候被调用,它们都将返回一个对应的wxDragResult值.以便拖放操作可以对其进行合 适的用户界面反馈.
3 放置
当用户释放鼠标按钮的时候,wxWidgets通过调用函数wxDataObject::GetAllFormats询问窗口绑定的 wxDropTarget对象是否接受正在拖动的数据.如果数据类型是可接受的,那么wxDropTarget::OnData将被调用.拖放对象绑定的 wxDataObject对象将进行对应的数据填充动作.wxDropTarget::OnData函数将返回一个wxDragResult类型的值,这 个值将作为wxDropSource::DoDragDrop函数的返回值.
使用标准的拖放目的对象
wxWidgets提供了了标准的wxDropTarget的派生类,因此你不必在任何时候都需要实现自己的拖放对象.你只需要实现重载这些类的一个虚函数,以便在拖放的时候得到提示.
wxTextdropTarget对象可以接收被拖动的文本数据,你只需要重载OnDropText函数以便告诉wxWidgets当有文本数据被放置的时候做什么事情就可以了.下面的例子演示了当有文本数据被放置的时候,怎样将其添加列表框内.
// 一个拖放目的用来将文本填加到列表框
class DnDText : public wxTextDropTarget
{
public:
DnDText(wxListBox *owner) { m_owner = owner; }
virtual bool OnDropText(wxCoord x, wxCoord y,
const wxString& text)
{
m_owner->Append(text);
return true;
}
private:
wxListBox *m_owner;
};
// 设置拖放目的对象
wxListBox* listBox = new wxListBox(parent, wxID_ANY);
listBox->SetDropTarget(new DnDText(listBox));
下面的例子展示了怎样使用wxFileDropTarget,这个对象可接收从资源管理器里拖动的文件对象,并且报告拖动文件的数目以及它们的名称.
// 一个拖放目的类用来将拖动的文件名添加到列表框
class DnDFile : public wxFileDropTarget
{
public:
DnDFile(wxListBox *owner) { m_owner = owner; }
virtual bool OnDropFiles(wxCoord x, wxCoord y,
const wxArrayString& filenames)
{
size_t nFiles = filenames.GetCount();
wxString str;
str.Printf( wxT("%d files dropped"), (int) nFiles);
m_owner->Append(str);
for ( size_t n = 0; n < nFiles; n++ ) {
m_owner->Append(filenames[n]);
}
return true;
}
private:
wxListBox *m_owner;
};
// 设置拖放目的类
wxListBox* listBox = new wxListBox(parent, wxID_ANY);
listBox->SetDropTarget(new DnDFile(listBox));
创建一个自定义的拖放目的
现在我们来创建一个自定义的拖放目的,它可以接受URLs(网址).这一次我们需要重载OnData和 OnDragOver函数,我们还将实现一个可以被重载的虚函数OnDropURL.
// 一个自定义的拖放目的对象,可以拖放URL对象
class URLDropTarget : public wxDropTarget
{
public:
URLDropTarget() { SetDataObject(new wxURLDataObject); }
void OnDropURL(wxCoord x, wxCoord y, const wxString& text)
{
// 当然, 一个真正的应用程序在这里应该做些更有意义的事情
wxMessageBox(text, wxT("URLDropTarget: got URL"),
wxICON_INFORMATION | wxOK);
}
// URLs不能被移动,只能被拷贝
virtual wxDragResult OnDragOver(wxCoord x, wxCoord y,
wxDragResult def)
{
return wxDragLink;
}
// 这个函数调用了OnDropURL函数,以便它的派生类可以更方便的使用
virtual wxDragResult OnData(wxCoord x, wxCoord y,
wxDragResult def)
{
if ( !GetData() )
return wxDragNone;
OnDropURL(x, y, ((wxURLDataObject *)m_dataObject)->GetURL());
return def;
}
};
// 设置拖放目的对象
wxListBox* listBox = new wxListBox(parent, wxID_ANY);
listBox->SetDropTarget(new URLDropTarget);
更多关于wxDataObject的知识
正如我们已经看到的那样,wxDataObject用来表示所有可以被拖放.以及可以被剪贴板操作的数据.wxDataObject最重要的特性之一,是 在于它是一个"聪明"的数据块,和普通的包含一段内存缓冲,或者一些文件的哑数据不同.所谓"聪明"指的是数据对像自己可以知道它内部的数据可以支持什么 数据格式,以及怎样将它的内部数据表现为那种数据格式.
所谓支持的数据格式,意思是说,这种格式可以从一个数据对象的内部数据或者将被设置的内部数据产生.通常情况下,一个数据对象可以支持多种数据格式作为输入或者输出.因此,一个数据对象可以支持从一种格式创建内部数据,并将其转换为另外一种数据格式,反之亦然.
当你需要使用某种数据对象的时候,有下面几种可选方案:
使用一种内建的数据对象.当你只需要支持文本数据,图片数据,或者是文件列表数据的时候,你可以使用wxTextdataObject,wxBitmapDataObject,或wxFileDataObject.
使用wxDataObjectSimple.从wxDataObjectSimple产生一个派生类,是实现自定义数据格式的最简便的 方法,不过,这种派生类只能支持一种数据格式,因此,你将不能够使用它和别的应用程序进行交互.不过,你可以用它在你的应用程序以及你应用程序的不同拷贝 之间进行数据传输.
使用wxCustomDataObject的派生类(它是wxDataObjectSimple的一个子类)来实现自定义数据对象.
使用wxDataObjectComposite.这是一个简单而强大的解决方案,它允许你支持任意多种数据格式(如果你和前面的方法结合使用的话,可以实现同时支持标准数据和自定义数据).
直接使用wxDataObject.这种方案可以实现最大的灵活度和效率,但也是最难的一种实现方案.
在拖放操作和剪贴板操作中,使用多重数据格式最简单的方法是使用wxDataObjectComposite对象,但是,这种使用方法的 效率是很低的,因为每一个wxDataObjectSimple都使用自己定义的格式来保存所有的数据.试想一下,你将从剪贴板上以你自己的格式粘贴超过 两百页的文本,其中包含Word,RTF,HTML,Unicode,和普通文本,虽然现在计算机的能力已经足可以应付这样的任务,但是从性能方面考虑, 你最好还是直接从wxDataObject实现一个派生类,用它来定义你的数据格式,然后在程序中指定这种类型的数据.
剪贴板操作和拖放操作,潜在的数据传输机制将在某个应用程序真正请求数据的时候,才会进行数据拷贝.因此,尽管用户可能认为在自己点击了应用程序的拷贝按钮以后数据就已存在于剪贴板了,但实际上这时候仅仅是告诉剪贴板有数据存在了.
实现wxDataObject的派生类
我们来看一下实现一个新的wxDataObject的派生类要用到哪些东西.至于怎样实现的过程,我们前面已经介绍过了,它是非常简单的.因此,我们在这里不多说了.
每一个wxDataObject的派生类,都必须重载和实现它的纯虚成员函数,那些只能用来输出数据或者保存数据(意味着只能进行单向操作)的数据对象的GetFormatCount函数在其不支持的方向上应该总是返回零.
GetAllFormats函数的参考为一个wxDataFormat类型的列表,以及一个方向(获取或设置).它将所有自己在这个方向上支持的数据格式填入这个列表.GetFormatCount函数则用来检测列表中元素的个数.
GetdataHere函数的参数是一个wxDataFormat参数,以及一个void*缓冲区.如果操作成功,返回TRue,否则返 回false.这个函数必须将数据以给定的格式填入这个缓冲区,数据可以是任意的二进制数据,或者是文本数据,只要SetData函数可以识别就行了.
GetdataSize函数则返回在某种给定的数据格式下数据的大小.
GetFormatCount函数返回用于转换或者设置的当前支持数据类型的个数.
GetPreferredFormat函数则返回某个指定方向上优选的数据类型.
SetData函数的参考包括一个数据类型,一个整数格式的缓冲区大小,以及一个void*类型的缓冲区指针.你可以在适当的时候(比如将其拷贝到自己的内部结构的时候)对其进行适当的解释.这个函数在成功的时候返回TRue,失败的时候返回false.
wxWidgets的拖放操作例子
我们通过使用位于samples/dnd目录的wxWidgets的拖放操作的例子,来演示一下怎样制作一个自定义的拥有自定义数据类型 的数据对象.这个例子演示了一个简单的绘图软件,可以用来绘制矩形,三角形,或者椭圆形,并且允许你对其进行编辑,拖放到一个新的位置,拷贝到剪贴板以及 从新粘贴到应用程序等操作.你可以通过选择文件菜单中的新建命令来创建一个应用程序的主窗口.这个窗口的外观如下图所示:

这些图形是用继承字DnDShape的类来建模的,数据对象被称为DnDShapeDataObject.在学习怎样实现这个数据对象之前,我们先看一下应用程序是怎样使用它们的.当一个剪贴板拷贝操作被请求的时候,一个DnDShapeDataObject对象将被增加到剪贴板,这个对象包含当前正在操作的图形的拷贝,如果剪贴板上已经有了一个对象,那么旧的对象将被释放.代码如下所示:
void DnDShapeFrame::OnCopyShape(wxCommandEvent& event)
{
if ( m_shape )
{
wxClipboardLocker clipLocker;
if ( !clipLocker )
{
wxLogError(wxT("Can't open the clipboard"));
return;
}
wxTheClipboard->AddData(new DnDShapeDataObject(m_shape));
}
}
剪贴板的粘贴操作也很容易理解,调用wxClipboard::GetData函数来获取位于剪贴板的图形数据对象,然后从其中获取图形 数据.前面我们已经介绍过怎样在剪贴板拥有对应数据的时候允许paste菜单.shapeFormatId是一个全局变量,其中包含了自定义的数据格式名 称:wxShape.
void DnDShapeFrame::OnPasteShape(wxCommandEvent& event)
{
wxClipboardLocker clipLocker;
if ( !clipLocker )
{
wxLogError(wxT("Can't open the clipboard"));
return;
}
DnDShapeDataObject shapeDataObject(NULL);
if ( wxTheClipboard->GetData(shapeDataObject) )
{
SetShape(shapeDataObject.GetShape());
}
else
{
wxLogStatus(wxT("No shape on the clipboard"));
}
}
void DnDShapeFrame::OnUpdateUIPaste(wxUpdateUIEvent& event)
{
event.Enable( wxTheClipboard->
IsSupported(wxDataFormat(shapeFormatId)) );
}
I