Перетащите из VirtualTreeView в оболочку (Ole drag and drop) - PullRequest
1 голос
/ 05 марта 2020

Я пытаюсь перетащить из VirtualTreeView, чтобы создать файл в оболочке (перетащите из VirtualTreeView в папку в проводнике или папке на рабочем столе).

Я нашел только пример делать противоположное (оболочка для VirtualTreeView), но я не могу найти никакого примера для этого. Помощь

Ответы [ 2 ]

3 голосов
/ 05 апреля 2020

Выполнение любых операций перетаскивания в 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
0 голосов
/ 10 марта 2020

Вы можете найти информацию в руководстве, написанном Микаэлем Ван Каннейтом

https://www.freepascal.org/~michael/articles/dragdrop2/dragdrop2.pdf

, или вы можете использовать: Drag and Drop Component Suite для Delphi

https://github.com/DelphiPraxis/The-Drag-and-Drop-Component-Suite-for-Delphi

...