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:
- selezione
- operazione taglia/copia (a secoda dei casi ovviamente)
- scelta della destinazione dell'oggetto selezionato
- operazione incolla
Il meccanismo Drag&Drop compie esattamente la medesima operazione ma con meno passaggi:
- selezione e drag (trascinamento) col mouse quindi drop (rilascio del pulsante del mouse)
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
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:
Incollare
L'applicazione che riceve il contenuto del Clipboard con l'operazione incolla deve:
- ricavare l'HANDLE del blocco di memoria allocato dall'applicazione da cui era stato fatta l'operazione di copia, con la funzione GetClipboardData
- una volta ricavato l'HANDLE, convertire questo in un puntatore al blocco di memoria (per esempio con la funzione GlobalLock)
- copiare il contenuto dal blocco di memoria globale in uno spazio di memoria allocato dall'applicazione (es. un array di chars)
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:
- 1) Il DropSource avvia l'operazione (applicazione A)
- 2) Il sistema operativo comunica con l'applicazione A e gestisce la situazione durante il drag
- 3) Il sistema operativo comunica con il DropTarget (applicazione B), questa riceve i dati (od annulla tutto) ed il drop viene concluso
- 4) Il sistema operativo comunica con il DropSource (applicazione A), questa riceve il feedback che il drag-and-drop si è concluso e come
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:
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:
Anche la Shell di Windows è coinvolta nell'operazione Drag&Drop esponendo i metodi delle interfacce COM seguenti:
All'applicazione drag-and-drop source:
- IDragSourceHelper
- IDragSourceHelper2
All'applicazione
drag-and-drop target:
Schema:
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.