经过一些研究,我能够弄清楚如何做到这一点,而无需实现 COM 接口 IDataObject(及其所有 FORMATETC gunk)。我认为它可能会引起同样困惑的其他人的兴趣,所以我写下了我的解决方案。如果能做得更聪明一点,我全是眼睛/耳朵!
System.Windows.Forms.DataObject 类有这个构造函数:
public DataObject(string format, object data)
我是这样称呼它的:
string expensive = GenerateStringVerySlowly();
var dataObject = new DataObject(
DataFormats.UnicodeText,
expensive);
DoDragDrop(dataObject, DragDropEffects.Copy);
上面的代码在复制操作时会将字符串数据放入HGLOBAL。但是,您也可以像这样调用构造函数:
string expensive = GenerateStringVerySlowly();
var dataObject = new DataObject(
DataFormats.UnicodeText,
new MemoryStream(Encoding.Unicode.GetBytes(expensive)));
DoDragDrop(dataObject, DragDropEffects.Copy);
与通过HGLOBAL 复制数据不同,后面的调用具有通过(COM)IStream 复制数据的良好效果。显然,在处理 COM IStream 和 .NET System.IO.Stream 之间的映射的 .NET 互操作层中正在发生一些魔法。
我现在要做的就是编写一个类,将流的创建推迟到最后一分钟 (Lazy object pattern),此时放置目标开始调用 Length、Read 等。看起来像这:(为简洁而编辑的部分)
public class DeferredStream : Stream
{
private Func<string> generator;
private Stream stm;
public DeferredStream(Func<string> expensiveGenerator)
{
this.generator = expensiveGenerator;
}
private Stream EnsureStream()
{
if (stm == null)
stm = new MemoryStream(Encoding.Unicode.GetBytes(generator()));
return stm;
}
public override long Length
{
get { return EnsureStream().Length; }
}
public override long Position
{
get { return EnsureStream().Position; }
set { EnsureStream().Position = value; }
}
public override int Read(byte[] buffer, int offset, int count)
{
return EnsureStream().Read(buffer, offset, count);
}
// Remaining Stream methods elided for brevity.
}
请注意,只有在第一次调用EnsureStream 方法时才会生成昂贵的数据。直到放置目标开始想要吸收IStream 中的数据时才会发生这种情况。最后我把调用代码改成:
var dataObject = new DataObject(
DataFormats.UnicodeText,
new DeferredStream(GenerateStringVerySlowly));
DoDragDrop(dataObject, DragDropEffects.Copy);
这正是我完成这项工作所需要的。但是,我在这里依靠放置目标的良好行为。例如,急切调用 Read 方法的行为不端的放置目标会导致代价高昂的操作提前发生。