OLE Drag and Drop for Mere Mortals

Part 1.

Clipboard and OLE Drag&Drop.




Introduzione



Drag&Drop vs Copy/Cut Paste

In Microsoft Windows è possibile l'operazione di copia/incolla o taglia/incolla di parti di testo selezionati col mouse, l'incorporazione di documenti in altri documenti o di questi in altre applicazioni od ancora il trasferimento di files da Windows Explorer, da una unità all'altra o da una cartella all'altra etc.

L'utente di Microsoft Windows per compiere l'operazione copia/incolla, oppure taglia/incolla, seleziona un "oggetto" (qui per oggetto si deve intendere un qualunque contenuto selezionato compreso un gruppo di files in Windows Explorer) quindi attivando i vari menu di sistema o utilizzando le scorciatoie e le combinazioni di tasti copiare/tagliare e incollare l'oggetto selezionato.

L'operazione copia/taglia incolla è perciò un'operazione in più fasi:


Il meccanismo Drag&Drop compie esattamente la medesima operazione ma con meno passaggi: Al Drag&Drop il sistema operativo risponde eseguendo tutti i passaggi: selezione,copia/taglia e incolla in maniera automatica e trasparente per l'utente. Il tutto in un unico movimento del cursore.

Il Clipboard


Copiare dove?

In Windows per le operazioni Copy/Cut Paste si usa il Clipboard che è uno spazio di memoria globale allocato
con una delle seguenti funzioni (a seconda dei casi):

HGLOBAL WINAPI GlobalAlloc(
  _In_ UINT   uFlags,
  _In_ SIZE_T dwBytes
);
HLOCAL WINAPI LocalAlloc(
  _In_ UINT   uFlags,
  _In_ SIZE_T uBytes
);
LPVOID WINAPI HeapAlloc(
  _In_ HANDLE hHeap,
  _In_ DWORD  dwFlags,
  _In_ SIZE_T dwBytes
);

GlobalAlloc e LocalAlloc sono implementate come funzioni wrapper e in realtà chiamano HeapAlloc usando un handle all'heap del processo che le esegue.



una volta allocato il blocco di memoria è necessario ricavare un puntatore ad esso con la funzione:

LPVOID WINAPI GlobalLock(
  _In_ HGLOBAL hMem
);

nel caso si stiano usando le funzioni GlobalAlloc o LocalAlloc. HeapAlloc invece restituisce già il puntatore al blocco di memoria allocato.



Ottenuto un puntatore generico (LPVOID) al blocco di memoria, l'oggetto selezionato viene "inserito" nel Clipboard con la funzione:

  
HANDLE WINAPI SetClipboardData(
  _In_     UINT   uFormat,
  _In_opt_ HANDLE hMem
);
La funzione SetClipboardData ha due argomenti:
l'HANDLE hMem, handle del blocco di memoria allocato
l'UINT uFormat, che è il formato dell'oggetto da copiare nel clipboard



Formati non standard

Oltre ai formati predefiniti ogni applicazione è libera di creare un nuovo formato. In questo è caso necessario registrare nel sistema il nuovo formato con la funzione:

UINT WINAPI RegisterClipboardFormat(
  _In_ LPCTSTR lpszFormat
);




Incollare

L'applicazione che riceve il contenuto del Clipboard con l'operazione incolla deve:




OLE Drag&Drop



Con la tecnologia OLE per il trasferimento di oggetti da e verso il Clipboard non viene allocato un blocco di memoria ma si usano storage media e metodi COM.
Le operazioni copy/cut paste non sono direttamente gestite dalle applicazioni ma c'è un protagonista dietro le quinte: il sistema operativo.

Per esempio, supponiamo che l'applicazione A (drag-and-drop source) possa trasferire dai dati all'applicazione B (drag-drop-target) ed il trasferimento avvenga attraverso l'operazione drag&drop, il sistema operativo tradurrebbe l'operazione:

Con OLE Drag&Drop quindi le due applicazione A e B non comunicano mai direttamente ma è il sistema operativo che sovraintende allo scambio di dati e ne gestisce i sincronismi secondo il seguente schema:

COM interfaces

Prima di vedere come funziona nel dettaglio, se si osserva lo schema generale qui sopra, si intuisce che il grosso del lavoro lo esegue il sistema operativo (attraverso alcune interfacce COM).
Quest'ultimo però affinchè A e B possano comunicare dev'essere informato di ciò che A e B sono e nonchè di quale scopo esse vogliano ottenere e, di conseguenza essere correttamente predisposto .

Seguendo l'esempio dello schema qui sopra, l'applicazione A, dovrà comunicare al sistema operativo che è un drag-and-drop source

L'applicazione B dovrà comunicare al sistema, viceversa, di essere un drag-and-drop target.

Tecnicamente parlando:

L'applicazione A (drop source) dovrà implementare l'interfaccia COM IDropSource. Un'applicazione che implementi l'interfaccia IDropSource deve obbligatoriamente implementare anche l'interfaccia IDataObject nel medesimo oggetto.

L'applicazione B (drop target) dovrà invece implementare l'interfaccia COM IDropTarget.


Schema:

COM interfaces

Anche la Shell di Windows è coinvolta nell'operazione Drag&Drop esponendo i metodi delle interfacce COM seguenti:

All'applicazione drag-and-drop source:


All'applicazione drag-and-drop target:



Schema:

drag-and-drop

Interfacce COM in dettaglio

IDropSource

Si deve implementare quest'interfaccia COM quando la nostra applicazione è un drag-and-drop source

Metodi:

HRESULT GiveFeedback(
  [in] DWORD dwEffect
);
Abilita l'applicazione drag-drop-source a dare all'utente che sta eseguendo l'operazione drag&drop un feedback visivo.
Il parametro dwEffect è un DWORD e corrisponde ad un valore incluso nella enum DROPEFFECT i cui valori possono essere:
DROPEFFECT_NONE
DROPEFFECT_COPY
DROPEFFECT_MOVE
DROPEFFECT_LINK
DROPEFFECT_SCROLL



HRESULT QueryContinueDrag(
  [in] BOOL  fEscapePressed,
  [in] DWORD grfKeyState
);
Determina se l'operazione drag-and-drop corrente deve continuare, essere cancellata o conclusa



IDropTarget

Si deve implementare quest'interfaccia COM quando la nostra applicazione è un drag-and-drop target


Metodi:

HRESULT DragEnter(
  [in]      IDataObject *pDataObj,
  [in]      DWORD       grfKeyState,
  [in]      POINTL      pt,
  [in, out] DWORD       *pdwEffect
);
Indica se un drop può essere accettato dall'applicazione e se si l'effetto di questo.
Come si può vedere il primo parametro di questo metodo è un puntatore all'interfaccia IDataObject.


HRESULT DragLeave();
Rimuove il feedback visivo associato all'applicazione target e rilascia le risorse occupate



HRESULT DragOver(
  [in]      DWORD  grfKeyState,
  [in]      POINTL pt,
  [in, out] DWORD  *pdwEffect
);

Fornisce il feedback visivo all'utente dell'applicazione target e comunica l'effetto drop alla funzione DoDragDrop così che quest'ultima inoltri effetto drop (qualunque esso sia, anche se annullato) all'applicazione drag-and-drop source.



HRESULT Drop(
  [in]      IDataObject *pDataObj,
  [in]      DWORD       grfKeyState,
  [in]      POINTL      pt,
  [in, out] DWORD       *pdwEffect
);

Qui viene eseguita la vera e propria operazione di incorporazione dei dati nella finestra target e rilascia le risorse

Anche in questo metodo il primo parametro è un puntatore all'interfaccia IDataObject




IDataObject

Abilita il trasferimento degli oggetti selezionati e gestisce le notifiche quando questi subiscono delle modifiche.
Com'è facilmente intuibile l'interfaccia IDataObject espone i metodi chiave per la gestione delle operazioni drag-and-drop.

Devono implementare quest'interfaccia tutte le applicazioni server e quelle OLE containers (le applicazioni containers sono quelle che incorporano o collegano documenti di applicazioni terze all'interno dei propri) che intendono trasferire dati ad altre applicazioni.

Metodi


HRESULT DAdvise(
  [in]  FORMATETC   *pformatetc,
  [in]  DWORD       advf,
  [in]  IAdviseSink *pAdvSink,
  [out] DWORD       *pdwConnection
);


HRESULT DUnadvise(
  [in] DWORD dwConnection
);


HRESULT EnumDAdvise(
  [out] IEnumSTATDATA **ppenumAdvise
);


HRESULT EnumFormatEtc(
  [in]  DWORD          dwDirection,
  [out] IEnumFORMATETC **ppenumFormatEtc
);


HRESULT GetCanonicalFormatEtc(
  [in]  FORMATETC *pformatectIn,
  [out] FORMATETC *pformatetcOut
);

HRESULT GetData(
  [in]  FORMATETC *pformatetcIn,
  [out] STGMEDIUM *pmedium
);


HRESULT GetDataHere(
  [in]      FORMATETC *pformatetc,
  [in, out] STGMEDIUM *pmedium
);


HRESULT QueryGetData(
  [in] FORMATETC *pformatetc
);


HRESULT SetData(
  [in] FORMATETC *pformatetc,
  [in] STGMEDIUM *pmedium,
  [in] BOOL      fRelease
);

I nomi dei due metodi, SetData e GetData, non lasciano dubbi sulla loro funzione.

SetData viene chiamato dopo aver istanziato un oggetto per essere quest'ultimo trasferito

GetData viene chiamato da un'applicazione consumer per ottenere un oggetto istanziato tramite SetData



I metodi SetData e GetData hanno in comune due parametri :

FORMATETC *p
STGMEDIUM *p

Il primo è un puntatore ad una struttura FORMATETC che definisce il formato dell'oggetto da trasferire

Il secondo è un puntatore alla struttura STGMEDIUM che definisce lo storage medium dell'oggetto da trasferire

FORMATETC

typedef struct tagFORMATETC {
  CLIPFORMAT     cfFormat;
  DVTARGETDEVICE *ptd;
  DWORD          dwAspect;
  LONG           lindex;
  DWORD          tymed;
} FORMATETC, *LPFORMATETC;
Questa struttura rappresenta un formato generalizzato del clipboard.



Membri della struttura FORMATETC:

cfFormat : il tipo di formato del clipboard, può essere standard, privato o OLE
ptd: un puntatore alla struttura DVTARGETDEVICE che contiene informazioni circa il target device per il quale l'oggetto da trasferire è stato realizzato
dwAspect: un DWORD corrispondente ad un valore dell'enumerazione DVASPECT
lindex: indice utilizzato quando l'oggetto da trasferire dev'essere diviso in più parti. Spesso questo valore è -1, cioè l'intero oggetto
tymed: un valore dell'enum TYMED che indica il tipo di storage medium usato per il trasferimento dell'oggetto




STGMEDIUM

typedef struct tagSTGMEDIUM {
  DWORD    tymed;
  union {
    HBITMAP       hBitmap;
    HMETAFILEPICT hMetaFilePict;
    HENHMETAFILE  hEnhMetaFile;
    HGLOBAL       hGlobal;
    LPOLESTR      lpszFileName;
    IStream       *pstm;
    IStorage      *pstg;
  };
  IUnknown *pUnkForRelease;
} STGMEDIUM, *LPSTGMEDIUM;

Questa struttura rappresenta una generalizzazione di un handle ad uno spazio di memoria globale usata per il trasferimento di oggetti


Membri della struttura STGMEDIUM

tymed: il tipo di storage medium. Questo valore è un valore tra quelli della enum TYMED

Il valore di questo membro varia a seconda dei valori dei campi della union
I membri della union vengono ignorati se questo membro è TYMED_NULL;

unnamed union:

dal sito Microsoft :

hBitmap
Bitmap handle. The tymed member is TYMED_GDI.

hMetaFilePict
Metafile handle. The tymed member is TYMED_MFPICT.

hEnhMetaFile
Enhanced metafile handle. The tymed member is TYMED_ENHMF.

hGlobal
Global memory handle. The tymed member is TYMED_HGLOBAL.

lpszFileName
Pointer to the path of a disk file that contains the data. The tymed member is TYMED_FILE.

pstm
Pointer to an IStream interface. The tymed member is TYMED_ISTREAM.

pstg
Pointer to an IStorage interface. The tymed member is TYMED_ISTORAGE.

pUnkForRelease
Pointer to an interface instance that allows the sending process to control the way the storage is released when the receiving process calls the ReleaseStgMedium function.



Nella parte 2 di questo tutorial scriveremo un'applicazione di esempio in plain Windows SDK e vedremo in azione le interfacce e i metodi che che abbiamo letto fin qui.




Giuseppe Pischedda 2018.


Se il post ti è piaciuto puoi fare una donazione all'autore, l'importo è a tua libera scelta.

Grazie