Mega Code Archive

 
Categories / Delphi / Files
 

How to create a drop handler for a file type

Title: How to create a drop handler for a file type? Question: I want to launch an action when I drop files on a file type associated to my appllication (just as WinZip does with zip archives) Answer: I'm sorry for my english ;-) You know how WinZip behaves with zip files: it allows to add files to archive with a simple drag and drop operation on zip files. How they do this job? They did it through a shell extension. I won't explain here what a shell extension is, so let's say you already know its meaning; if you have just an idea only, you can open the folder \Demos\ActiveX\ShellExt of Delphi: there you will find a good example that shows how to associate a context menu to a file type (just as WinZip does), but what is if I need to transform a file into a "drop target"? A drop target can accept drag and drop operations and can fire an event when anything is dropped on it. Let's think to WinZip, again: if drag a file over a zip archive, WinZip adds it to the same archive that you choose as the drop target. This job also is done via a shell extension. We can start opening the "ContMenu.dpr" project that you see in the ShellExt folder of Delphi, then we have to open the "contextm.pas" unit. We can turn a file type into a drop target with some modifications in this last unit. Let's see the entire unit how it appears after I made the required changes (follow the comments in the unit). (* ------------ BEGINNING OF CODE ------------- *) unit ContextM; interface uses Windows, ActiveX, ComObj, ShlObj; type // We must add IUnknown, IPersitFile and IDropTarget, and we must remove IContextMenu interfaces // We wanto to change TContextMenu into TDropHandler for convenience TDropHandler = class(TComObject, IShellExtInit, IUnknown, IPersistFile, IDropTarget) private FFileName: array[0..MAX_PATH] of Char; Nfiles: integer; FFiles: array[0..max_PATH] of PChar; dest: string; protected // The {IContextMenu} section has been removed because we won't use that interface... { IShellExtInit } function IShellExtInit.Initialize = SEIInitialize; // Avoid compiler warning function SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; stdcall; // ... but we need to add the following two sections { IPersistFile } function IsDirty: HResult; stdcall; function Load(pszFileName: POleStr; dwMode: Longint): HResult; stdcall; function Save(pszFileName: POleStr; fRemember: BOOL): HResult; stdcall; function SaveCompleted(pszFileName: POleStr): HResult; stdcall; function GetCurFile(out pszFileName: POleStr): HResult; stdcall; function GetClassID(out classID: TCLSID): HResult; stdcall; { IDropTarget } function DragEnter(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall; function DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall; function DragLeave: HResult; stdcall; function Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall; end; const // Let's change Class_ContextMenu into Class_DropHandler for convenience: Class_DropHandler: TGUID = '{574AF620-AC3D-11D4-86B6-92AD195EF923}'; // You need to assign a different GUID for this handler, so you must click SHIFT+CTRL+G to obtain a new GUID to copy above. implementation uses ComServ, SysUtils, ShellApi, Registry; function TDropHandler.SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; var StgMedium: TStgMedium; FormatEtc: TFormatEtc; begin if (lpdobj = nil) then begin Result := E_INVALIDARG; Exit; end; with FormatEtc do begin cfFormat := CF_HDROP; ptd := nil; dwAspect := DVASPECT_CONTENT; lindex := -1; tymed := TYMED_HGLOBAL; end; // Render the data referenced by the IDataObject pointer to an HGLOBAL // storage medium in CF_HDROP format. Result := lpdobj.GetData(FormatEtc, StgMedium); if Failed(Result) then Exit; Result := NOERROR; ReleaseStgMedium(StgMedium); end; // Now we have to assign some job to the new functions that we must add because we // declared them before in this same unit. You may think that having to leave // some of these function only with a line of code is useless: well, you're wrong. // Without these "silly" function the dll won't work function TDropHandler.IsDirty: HResult; begin Result := E_NOTIMPL; end; function TDropHandler.Load(pszFileName: POleStr; dwMode: Integer): HResult; begin // Here we will retrieve the full path of the file on which other files are being dropped, // so we will store it in the DestFile string; DestFile:=WideCharToString(pszFileName); Result := S_OK; end; function TDropHandler.Save(pszFileName: POleStr; fRemember: BOOL): HResult; begin Result := E_NOTIMPL; end; function TDropHandler.SaveCompleted(pszFileName: POleStr): HResult; begin Result := E_NOTIMPL; end; function TDropHandler.DragEnter(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall; var StgMedium: TStgMedium; FormatEtc: TFormatEtc; hr: HRESULT; begin // Here starts the hard job with FormatEtc do begin cfFormat := CF_HDROP; ptd := nil; dwAspect := DVASPECT_CONTENT; lindex := -1; tymed := TYMED_HGLOBAL; end; hr := dataobj.QueryGetData(formatetc); if Failed(hr) then begin // If anything is gone bad, then we won't see any drag'n'drop dwEffect:=DROPEFFECT_NONE; Result := E_FAIL; Exit; end else begin // Everything is OK: we need only a drag and drop "COPY" action dwEffect:=DROPEFFECT_COPY; Result := NOERROR; end; end; function TDropHandler.DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall; begin dwEffect:=DROPEFFECT_COPY; Result := S_OK; end; function TDropHandler.DragLeave: HResult; stdcall; begin Result := S_OK; end; function TDropHandler.Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall; var StgMedium: TStgMedium; FormatEtc: TFormatEtc; hr: HRESULT; F: TextFile; begin // This is the most important part of the unit. Files are dropped on th drop target, // so our DLL we will do something. In this example we will write a text file // that contains all the filenames of the dropped files. if (dataobj = nil) then begin Result := E_INVALIDARG; Exit; end; with FormatEtc do begin cfFormat := CF_HDROP; ptd := nil; dwAspect := DVASPECT_CONTENT; lindex := -1; tymed := TYMED_HGLOBAL; end; // Render the data referenced by the IDataObject pointer to an HGLOBAL // storage medium in CF_HDROP format. hr := dataobj.GetData(FormatEtc, StgMedium); if Failed(hr) then Exit; // Writes a list of dragged files: this list could be read by our app. NFiles:=DragQueryFile(StgMedium.hGlobal, $FFFFFFFF, nil, 0); // How many file were dropped? AssignFile(F,'C:\Windows\Desktop\DroppedFiles.txt'); Rewrite(f); for i:=0 to nfiles-1 do begin // We are reading the list of files being dropped... DragQueryFile(StgMedium.hGlobal, i, FFileName , SizeOf(FFilename)); writeln(F, FFilename); // If we dropped a folder we will obtain the following: if GetFileAttributes(FFilename)=faDirectory then writeln (f,'Folder - '+ffilename); end; // Let's write on which file we dropped the other files... writeln(f,'Drop Target - '+DestFile); // We're finished! CloseFile(f); Result := NOERROR; ReleaseStgMedium(StgMedium); end; function TDropHandler.GetClassID(out classID: TCLSID): HResult; begin Result := E_NOTIMPL; end; function TDropHandler.GetCurFile(out pszFileName: POleStr): HResult; begin Result := E_NOTIMPL; end; type // Let's modify TContextMenuFactory into TDropHandlerFactory for convenience TDropHandlerFactory = class(TComObjectFactory) public procedure UpdateRegistry(Register: Boolean); override; end; procedure TDropHandlerFactory.UpdateRegistry(Register: Boolean); var ClassID: string; begin // To make this drop handler working, we must register it in the Registry. // This code is already in the originale COntextM.pas, but we still need some changes. if Register then begin inherited UpdateRegistry(Register); ClassID := GUIDToString(Class_DropHandler); // We want to transform .DPR files into drop target... CreateRegKey('DelphiProject\shellex', '', ''); // ... but we must change "CreateRegKey('DelphiProject\shellex\ContextMenuHandlers', '', '');" into the following: CreateRegKey('DelphiProject\shellex\DropHandler', '', ClassID); // "CreateRegKey('DelphiProject\shellex\ContextMenuHandlers\ContMenu', '', ClassID);" has been deleted if (Win32Platform = VER_PLATFORM_WIN32_NT) then with TRegistry.Create do try RootKey := HKEY_LOCAL_MACHINE; OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions', True); OpenKey('Approved', True); WriteString(ClassID, 'Delphi 4.0 Drop Handler Shell Extension Example'); finally Free; end; end else begin // Also, we must delete "DeleteRegKey('DelphiProject\shellex\ContextMenuHandlers\ContMenu');" / and change " DeleteRegKey('DelphiProject\shellex\ContextMenuHandlers');" into the following: DeleteRegKey('DelphiPorject\shellex\DropHandler'); DeleteRegKey('DelphiProject\shellex'); inherited UpdateRegistry(Register); end; end; initialization TDropHandlerFactory.Create(ComServer, TDropHandler, Class_DropHandler, '', 'Delphi 4.0 Drop Handler Shell Extension Example', ciMultiInstance, tmApartment); end. (* ------------ END OF CODE ------------- *) Save the unit and compile the DLL, then register it using the command line: regsvr32 c:\windows\desktop\contmenu.dll (if you placed the dll on the desktop) Finally, test the DLL: open a folder that contains Delphi Projects (.DPR files) and drop other files on them. The result is in a new text file on the desktop.