Doing any drag-drop operations in Windows involves creating an IDataObject
, and giving that object to Windows.
The Virtual Treeview handles a lot of that grunt-work for you, creating an object that implements IDataObject
for you. The tree then raises events when you need to help populate it.
When passing "file-like" things through a copy-paste or a drag-drop, you are require to add two clipboard formats to the IDataObject
:
CF_FILEDESCRIPTOR
, and
CF_FILECONTENTS
In addition to support for formats that the virtualtree itself will add, you can choose to indicate support for more clipboard format.
OnGetUserClipboardFormats Event
This is the event where you are given a chance to add additional clipboard formats to the IDataObject
that the tree will be creating:
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;
The tree will then given the IDataObject
to the shell as part of the drag-drop operation.
Later, an application that the user dropped items onto will enumerate all formats in the IDataObject
, e.g.:
CF_HTML
("HTML Format")
CFSTR_FILEDESCRIPTOR
("FileGroupDescriptorW")
CFSTR_FILECONTENTS
("FileContents")
CF_ENHMETAFILE
And it will see that the IDataObject
contains FileDescriptor and FileContents.
The receiving application will then ask the IDataObject
to actually cough up data. (This "delayed-rendering" is a good thing, it means your source application doesn't actually have to read any content unless it actually gets requested).
OnRenderOleData Event
This is the event where the virtual tree realizes its IDataObject
has been asked to render something, and it needs you to finally render that actual content.
The general idea with these two clipboard formats is:
CF_FILEDESCRIPTOR
lets you return a record that describes the file-like thing (e.g. filename, file size, created date, last modified date, last accessed date)
CF_FILECONTENTS
lets you return an IStream
that contains the actual file contents
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;
The first helper function creates an array of FILE_DESCRIPTOR
objects, and copies them to a HGLOBAL
allocated memory:
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;
The second helper copies your file-like thing's binary contents to an 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;
Your attachment object, whatever it is has to know:
- how to represent itself as a
TFileDescriptor
- how to return the contents as an
IStream