После некоторого исследования я смог выяснить, как это сделать, не используя COM-интерфейс IDataObject
(со всеми его галочками FORMATETC
). Я думал, что это может представлять интерес для других в том же затруднительном положении, поэтому я написал свое решение. Если это можно сделать более умно, я все глаза/уши!
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
. По-видимому, некоторая магия происходит на уровне взаимодействия .NET, который обрабатывает сопоставление между COM IStream
и .NET System.IO.Stream
.
Все, что я должен был сделать сейчас было написать класс, который не отложенное создание потока до самой последней минуты (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
, скажем, вызовет дорогостоящую операцию раньше.