您的位置:首页 > 编程语言 > C#

CSharp Tips:Drag & Drop的高级应用

2010-11-18 15:38 316 查看
昨天,有同事问了我一个问题,这个问题的第一种描述方式是“怎么利用拖放的方式把一个进程中的流传递到第二个进程中”;第二种描述方式是“把应用程序中的数据(文件/流)通过拖放的方式复制到桌面或者Explorer中”。看似简单的描述下面隐藏了不少东西,有点意思,于是昨晚翻了翻资料。

这个问题涉及到两个技术点:跨进程的大数据块的传递,Drag & Drop。
跨进程的大数据块传递不考虑管道/Socket这类直接通讯方式的话,间接方式也很多,DDE、剪贴板、文件或者内存镜像文件。DDE有点古老,我都不记得该怎么玩了。实时性要求不高的话,保存成为临时文件,将文件名传递过去,是一个无论从系统开销,还是实现上都是一个不错的选择。至于内存镜像文件,是另一个专题,不在这里赘述了。剪贴板也是个比较有趣的东西,真要说的话,也有很多话可讲,也不在这里多说了。
数据传递的方案定了,再看Drag & Drop。在MSDN中查了一下,System.Window.Form.Control下有一组和拖放相关的方法和事件,例如DoDragDrop用来作为源启动一次拖放,DragEnter、DragOver、DragDrop是作为目标处理拖放的事件。整个拖放过程就是源在拖放开始的时候提交需要拖放的数据,并指明影响方式(例如Copy、Move之类),目标在拖放被触发(Drop的时候)时获取拖放数据并且进行相应处理。数据和数据格式是通过一个叫IDataObject的接口来描述的,它用来存储数据,申明支持的数据格式,并且对数据格式进行转换。思路清楚了,关键要实现一个IDataObject。

回过头看我们的问题,先解决“把应用程序中的数据(文件/流)通过拖放的方式复制到桌面或者Explorer中”这种情况。写一个小例子,一个窗口上面只有一个ListBox,里面有两个元素,分别对应硬盘上的两个文件,希望在ListBox中选中一个元素拖放到桌面,就把那个元素对应的文件复制到桌面。
代码逻辑很简单,处理ListBox的鼠标事件,判断是不是满足启动拖放的条件,当启动拖放时,把当前选中的元素封装到我们定义的IDataObject中,然后调用DoDragDrop方法。问题是IDataObject如果定义?有同学就问了我怎么知道桌面(Explorer)要求的数据格式是什么?易同学就这么问过。这也很简单,只要看看桌面作为拖放源的时候生成的DataObject支持那些格式,数据是什么形式的,仿照一下就行了。
在开始正式工作之前,先在窗口的DragEnter中写一段代码,检查DataSource支持的格式,运行程序,在桌面上拖到一个我们到我们写的应用程序窗口,一看输出,居然支持8种格式。作为工程师一定要把复杂的问题变得简单,看名字一分析,有几个格式叫“FileDrop”、“FileName”、“FileNameW”,返回的数据类型是一个字符串数组,每个成员是一个文件名,不用想,就和这些格式有关。马上动手,IDataObject类的实现如下,完整代码可到此处下载。
private class CDragingData : IDataObject
{
private static String[] g_asFormats = new String[] {"System.String","FileName","FileNameW","FileDrop" };

private String[] m_asData = null;

public CDragingData(String data)
{
m_asData = new String[1];
m_asData[0] = data;
}

#region IDataObject Members
public object GetData(Type format)
{
if (format != null && format.FullName == "System.String")
return m_asData[0];
else
return null;
}

public object GetData(string format)
{
if (String.IsNullOrEmpty(format))
return null;

switch (format.ToUpper())
{
case "STRING":
case "SYSTEM.STRING":
return m_asData[0];
case "FILENAME":
case "FILENAMEW":
case "FILEDROP":
return m_asData;
default:
return null;
}
}

public object GetData(string format, bool autoConvert)
{
return GetData(format);
}

public bool GetDataPresent(Type format)
{
if (format != null && format.FullName == "System.String")
return true;
else
return false;
}

public bool GetDataPresent(string format)
{
if (String.IsNullOrEmpty(format))
return false;

switch (format.ToUpper())
{
case "FILENAME":
case "FILENAMEW":
case "FILEDROP":
return true;
default:
return false;
}
}

public bool GetDataPresent(string format, bool autoConvert)
{
return GetDataPresent(format);
}

public string[] GetFormats()
{
return g_asFormats;
}

public string[] GetFormats(bool autoConvert)
{
return g_asFormats;
}

public void SetData(object data)
{
if (data == null)
m_asData[0] = null;
else
m_asData[0] = data.ToString();
}

public void SetData(Type format, object data)
{
SetData(data);
}

public void SetData(string format, object data)
{
SetData(data);
}

public void SetData(string format, bool autoConvert, object data)

{
SetData(data);
}
#endregion

}

运行程序,拖动ListBox中的元素到桌面,成了,文件复制过去了。在进一步测试,其实只需要支持“FileDrop”的格式就可以了。

再看另一种情况,从自己写的一个应用程序拖放到另一个自己写的应用程序,怎么办?更好办了,双方约定一下IDataObject实现类需要支持的格式,目标应用程序在自己的DragDrop时间中获取数据再执行操作就可以了。

最后提一点DoDragDrop方法的第一个参数,是支持字符串类型的,MSDN提供的示例也是传递一个字符串,但是跨进程的话应该不能直接用,就算是自己的程序估计也不行,跨进程一定需要自己定义一个实现IDataObject的类,想想COM,总要做Marshal的。这段个人猜测,没有验证,供参考。

参考文档
Ø Control.DoDragDrop方法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: