Выполнение любых операций перетаскивания в Windows включает создание IDataObject
и передачу этого объекта Windows.
Виртуальное древовидное представление выполняет для вас большую часть этой тяжелой работы, создавая объект, который реализует IDataObject
для вас. Затем дерево вызывает события, когда вам нужно помочь заполнить его.
При передаче "файловых" вещей через копирование-вставку или перетаскивание, вам необходимо добавить два форматы буфера обмена в IDataObject
:
CF_FILEDESCRIPTOR
и CF_FILECONTENTS
В дополнение к поддержке форматов, которые само виртуальное дерево добавит, вы можете выбрать поддержку большего формата буфера обмена.
Событие OnGetUserClipboardFormats
Это событие, в котором вам предоставляется возможность добавить дополнительные форматы буфера обмена в IDataObject
, которое будет создавать дерево:
procedure TForm1.lvAttachmentsGetUserClipboardFormats(Sender: TBaseVirtualTree;
var Formats: TFormatEtcArray);
var
i: Integer;
begin
//Add formats for CF_FILEDESCRIPTOR and CF_FILECONTENTS
i := Length(Formats);
SetLength(Formats, i + 1);
Formats[i].cfFormat := CF_FILEDESCRIPTOR;
Formats[i].ptd := nil;
Formats[i].dwAspect := DVASPECT_CONTENT;
Formats[i].lindex := -1;
Formats[i].tymed := TYMED_HGLOBAL;
i := Length(Formats);
SetLength(Formats, i + 1);
Formats[i].cfFormat := CF_FILECONTENTS;
Formats[i].ptd := nil;
Formats[i].dwAspect := DVASPECT_CONTENT;
Formats[i].lindex := 0;
Formats[i].tymed := TYMED_ISTREAM;
end;
Затем дерево передаст оболочке IDataObject
как часть операции перетаскивания.
Позже приложение, в которое пользователь перетянул элементы, будет перечислять все форматы в IDataObject
, например:
CF_HTML
("HTML Формат") CFSTR_FILEDESCRIPTOR
("FileGroupDescriptorW") CFSTR_FILECONTENTS
("FileContents") CF_ENHMETAFILE
И он увидит, что IDataObject
содержит FileDescriptor и FileContents .
Получающее приложение затем попросит IDataObject
фактически выкашливать данные. (Это «отложенный рендеринг» - хорошая вещь, это означает, что вашему исходному приложению на самом деле не нужно читать какой-либо контент, если только его не запрашивают).
Это событие, когда виртуальное дерево понимает, что его IDataObject
попросили отрисовать что-то, и ему нужно, чтобы вы наконец отрисовали этот фактический контент.
Общая идея с этими двумя форматами буфера обмена:
CF_FILEDESCRIPTOR
позволяет вернуть запись, описывающую файлоподобную вещь (например, имя файла, размер файла, дата создания, дата последнего изменения, дата последнего доступа) CF_FILECONTENTS
позволяет вернуть IStream
, который содержит фактическое содержимое файла
procedure TForm1.lvAttachmentsRenderOLEData(Sender: TBaseVirtualTree; const FormatEtcIn: tagFORMATETC;
out Medium: tagSTGMEDIUM; ForClipboard: Boolean; var Result: HRESULT);
var
global: HGLOBAL;
stm: IStream;
begin
if FormatEtcIn.cfFormat = CF_FILEDESCRIPTOR then
begin
global := GetAttachmentFileDescriptorsFromListView(lvAttachments, ForClipboard);
if global = 0 then
Exit;
ZeroMemory(@Medium, SizeOf(Medium));
Medium.tymed := TYMED_HGLOBAL;
Medium.hGlobal := global;
Result := S_OK;
end
else if FormatEtcIn.cfFormat = CF_FILECONTENTS then
begin
ZeroMemory(@Medium, SizeOf(Medium));
Medium.tymed := TYMED_ISTREAM;
Result := GetAttachmentStreamFromListView(lvAttachments, ForClipboard, FormatEtcIn.lindex, stm);
if Failed(Result) then
Exit;
Medium.stm := Pointer(stm);
IUnknown(Medium.stm)._AddRef;
Result := S_OK;
end;
end;
Первая вспомогательная функция создает массив объектов FILE_DESCRIPTOR
и копирует их в выделенную память HGLOBAL
:
function GetAttachmentFileDescriptorsFromListView(Source: TVirtualStringTree; ForClipboard: Boolean): HGLOBAL;
var
i: Integer;
nCount: Integer;
nodes: TNodeArray;
descriptors: TFileDescriptorDynArray;
data: TAttachment;
begin
Result := 0;
if ForClipboard then
nodes := Source.GetSortedCutCopySet(False)
else
nodes := Source.GetSortedSelection(False);
if Length(nodes) = 0 then
Exit;
nCount := 0;
for i := 0 to Length(nodes) - 1 do
begin
//Get the file thing from this node
data := GetNodeDataFromNode(nodes[i]);
if not Assigned(data) then
Continue;
//Increase the size of our descriptors array by one
Inc(nCount);
SetLength(Descriptors, nCount);
//Fill in the next descriptor
descriptors[nCount-1] := data.ToWindowsFileDescriptor;
end;
Result := FileDescriptorsToHGLOBAL(descriptors);
end;
Второй помощник копирует двоичное содержимое вашей файловой вещи в IStream
:
function GetAttachmentStreamFromListView(Source: TVirtualStringTree; ForClipboard: Boolean; lindex: Integer; var stm: IStream): HResult;
var
nodes: TNodeArray;
data: TAttachment;
begin
Result := E_FAIL;
if ForClipboard then
nodes := Source.GetSortedCutCopySet(False)
else
nodes := Source.GetSortedSelection(False);
if Length(nodes) = 0 then
Exit;
if (lIndex < Low(Nodes)) or (lIndex > High(Nodes)) then
begin
Result := DV_E_LINDEX;
Exit;
end;
//Get the file thing from this node
data := GetNodeDataFromNode(nodes[i]);
if not Assigned(data) then
Continue;
//Fetch the content into a IStream wrapped memory stream
stm := data.GetStream(nil);
Result := S_OK;
end;
Ваш объект вложения, что бы он ни знал:
- как вернуть содержимое как
IStream