DragDropContext.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. unit DragDropContext;
  2. // -----------------------------------------------------------------------------
  3. // Project: Drag and Drop Component Suite.
  4. // Module: DragDropContext
  5. // Description: Implements Context Menu Handler Shell Extensions.
  6. // Version: 4.0
  7. // Date: 18-MAY-2001
  8. // Target: Win32, Delphi 5-6
  9. // Authors: Anders Melander, anders@melander.dk, http://www.melander.dk
  10. // Copyright © 1997-2001 Angus Johnson & Anders Melander
  11. // -----------------------------------------------------------------------------
  12. interface
  13. uses
  14. DragDrop,
  15. DragDropComObj,
  16. Menus,
  17. ShlObj,
  18. ActiveX,
  19. Windows,
  20. Classes;
  21. {$include DragDrop.inc}
  22. type
  23. ////////////////////////////////////////////////////////////////////////////////
  24. //
  25. // TDropContextMenu
  26. //
  27. ////////////////////////////////////////////////////////////////////////////////
  28. // Partially based on Borland's ShellExt demo.
  29. ////////////////////////////////////////////////////////////////////////////////
  30. // A typical shell context menu handler session goes like this:
  31. // 1. User selects one or more files and right clicks on them.
  32. // The files must of a file type which has a context menu handler registered.
  33. // 2. The shell loads the context menu handler module.
  34. // 3. The shell instantiates the registered context menu handler object as an
  35. // in-process COM server.
  36. // 4. The IShellExtInit.Initialize method is called with a data object which
  37. // contains the dragged data.
  38. // 5. The IContextMenu.QueryContextMenu method is called to populate the popup
  39. // menu.
  40. // TDropContextMenu uses the PopupMenu property to populate the shell context
  41. // menu.
  42. // 6. If the user chooses one of the context menu menu items we have supplied,
  43. // the IContextMenu.InvokeCommand method is called.
  44. // TDropContextMenu locates the corresponding TMenuItem and fires the menu
  45. // items OnClick event.
  46. // 7. The shell unloads the context menu handler module (usually after a few
  47. // seconds).
  48. ////////////////////////////////////////////////////////////////////////////////
  49. TDropContextMenu = class(TInterfacedComponent, IShellExtInit, IContextMenu)
  50. private
  51. FContextMenu: TPopupMenu;
  52. FMenuOffset: integer;
  53. FDataObject: IDataObject;
  54. FOnPopup: TNotifyEvent;
  55. FFiles: TStrings;
  56. procedure SetContextMenu(const Value: TPopupMenu);
  57. protected
  58. procedure Notification(AComponent: TComponent;
  59. Operation: TOperation); override;
  60. { IShellExtInit }
  61. function Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
  62. hKeyProgID: HKEY): HResult; stdcall;
  63. { IContextMenu }
  64. function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast,
  65. uFlags: UINT): HResult; stdcall;
  66. function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
  67. function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
  68. pszName: LPSTR; cchMax: UINT): HResult; stdcall;
  69. public
  70. constructor Create(AOwner: TComponent); override;
  71. destructor Destroy; override;
  72. property DataObject: IDataObject read FDataObject;
  73. property Files: TStrings read FFiles;
  74. published
  75. property ContextMenu: TPopupMenu read FContextMenu write SetContextMenu;
  76. property OnPopup: TNotifyEvent read FOnPopup write FOnPopup;
  77. end;
  78. ////////////////////////////////////////////////////////////////////////////////
  79. //
  80. // TDropContextMenuFactory
  81. //
  82. ////////////////////////////////////////////////////////////////////////////////
  83. // COM Class factory for TDropContextMenu.
  84. ////////////////////////////////////////////////////////////////////////////////
  85. TDropContextMenuFactory = class(TShellExtFactory)
  86. protected
  87. function HandlerRegSubKey: string; virtual;
  88. public
  89. procedure UpdateRegistry(Register: Boolean); override;
  90. end;
  91. ////////////////////////////////////////////////////////////////////////////////
  92. //
  93. // Component registration
  94. //
  95. ////////////////////////////////////////////////////////////////////////////////
  96. procedure Register;
  97. ////////////////////////////////////////////////////////////////////////////////
  98. //
  99. // Misc.
  100. //
  101. ////////////////////////////////////////////////////////////////////////////////
  102. ////////////////////////////////////////////////////////////////////////////////
  103. ////////////////////////////////////////////////////////////////////////////////
  104. //
  105. // IMPLEMENTATION
  106. //
  107. ////////////////////////////////////////////////////////////////////////////////
  108. ////////////////////////////////////////////////////////////////////////////////
  109. implementation
  110. uses
  111. DragDropFile,
  112. DragDropPIDL,
  113. Registry,
  114. ComObj,
  115. SysUtils;
  116. ////////////////////////////////////////////////////////////////////////////////
  117. //
  118. // Component registration
  119. //
  120. ////////////////////////////////////////////////////////////////////////////////
  121. procedure Register;
  122. begin
  123. RegisterComponents(DragDropComponentPalettePage, [TDropContextMenu]);
  124. end;
  125. ////////////////////////////////////////////////////////////////////////////////
  126. //
  127. // Utilities
  128. //
  129. ////////////////////////////////////////////////////////////////////////////////
  130. ////////////////////////////////////////////////////////////////////////////////
  131. //
  132. // TDropContextMenu
  133. //
  134. ////////////////////////////////////////////////////////////////////////////////
  135. constructor TDropContextMenu.Create(AOwner: TComponent);
  136. begin
  137. inherited Create(AOwner);
  138. FFiles := TStringList.Create;
  139. end;
  140. destructor TDropContextMenu.Destroy;
  141. begin
  142. FFiles.Free;
  143. inherited Destroy;
  144. end;
  145. function TDropContextMenu.GetCommandString(idCmd, uType: UINT;
  146. pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HResult;
  147. var
  148. ItemIndex: integer;
  149. begin
  150. ItemIndex := integer(idCmd);
  151. // Make sure we aren't being passed an invalid argument number
  152. if (ItemIndex >= 0) and (ItemIndex < FContextMenu.Items.Count) then
  153. begin
  154. if (uType = GCS_HELPTEXT) then
  155. // return help string for menu item.
  156. StrLCopy(pszName, PChar(FContextMenu.Items[ItemIndex].Hint), cchMax);
  157. Result := NOERROR;
  158. end else
  159. Result := E_INVALIDARG;
  160. end;
  161. function TDropContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;
  162. var
  163. ItemIndex: integer;
  164. begin
  165. Result := E_FAIL;
  166. // Make sure we are not being called by an application
  167. if (FContextMenu = nil) or (HiWord(Integer(lpici.lpVerb)) <> 0) then
  168. Exit;
  169. ItemIndex := LoWord(lpici.lpVerb);
  170. // Make sure we aren't being passed an invalid argument number
  171. if (ItemIndex < 0) or (ItemIndex >= FContextMenu.Items.Count) then
  172. begin
  173. Result := E_INVALIDARG;
  174. Exit;
  175. end;
  176. // Execute the menu item specified by lpici.lpVerb.
  177. try
  178. try
  179. FContextMenu.Items[ItemIndex].Click;
  180. Result := NOERROR;
  181. except
  182. on E: Exception do
  183. begin
  184. Windows.MessageBox(0, PChar(E.Message), 'Error',
  185. MB_OK or MB_ICONEXCLAMATION or MB_SYSTEMMODAL);
  186. Result := E_UNEXPECTED;
  187. end;
  188. end;
  189. finally
  190. FDataObject := nil;
  191. FFiles.Clear;
  192. end;
  193. end;
  194. function TDropContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst,
  195. idCmdLast, uFlags: UINT): HResult;
  196. var
  197. i: integer;
  198. Last: integer;
  199. Flags: UINT;
  200. function IsLine(Item: TMenuItem): boolean;
  201. begin
  202. {$ifdef VER13_PLUS}
  203. Result := Item.IsLine;
  204. {$else}
  205. Result := Item.Caption = '-';
  206. {$endif}
  207. end;
  208. begin
  209. Last := 0;
  210. if (FContextMenu <> nil) and (((uFlags and $0000000F) = CMF_NORMAL) or
  211. ((uFlags and CMF_EXPLORE) <> 0)) then
  212. begin
  213. FMenuOffset := idCmdFirst;
  214. for i := 0 to FContextMenu.Items.Count-1 do
  215. if (FContextMenu.Items[i].Visible) then
  216. begin
  217. Flags := MF_STRING or MF_BYPOSITION;
  218. if (not FContextMenu.Items[i].Enabled) then
  219. Flags := Flags or MF_GRAYED;
  220. if (IsLine(FContextMenu.Items[i])) then
  221. Flags := Flags or MF_SEPARATOR;
  222. // Add one menu item to context menu
  223. InsertMenu(Menu, indexMenu, Flags, FMenuOffset+i,
  224. PChar(FContextMenu.Items[i].Caption));
  225. inc(indexMenu);
  226. Last := i+1;
  227. end;
  228. end else
  229. FMenuOffset := 0;
  230. // Return number of menu items added
  231. Result := MakeResult(SEVERITY_SUCCESS, FACILITY_NULL, Last)
  232. end;
  233. function TDropContextMenu.Initialize(pidlFolder: PItemIDList;
  234. lpdobj: IDataObject; hKeyProgID: HKEY): HResult;
  235. begin
  236. FFiles.Clear;
  237. if (lpdobj = nil) then
  238. begin
  239. Result := E_INVALIDARG;
  240. Exit;
  241. end;
  242. // Save a reference to the source data object.
  243. FDataObject := lpdobj;
  244. // Extract source file names and store them in a string list.
  245. with TFileDataFormat.Create(nil) do
  246. try
  247. if GetData(DataObject) then
  248. FFiles.Assign(Files);
  249. finally
  250. Free;
  251. end;
  252. if (Assigned(FOnPopup)) then
  253. FOnPopup(Self);
  254. Result := NOERROR;
  255. end;
  256. procedure TDropContextMenu.SetContextMenu(const Value: TPopupMenu);
  257. begin
  258. if (Value <> FContextMenu) then
  259. begin
  260. if (FContextMenu <> nil) then
  261. FContextMenu.RemoveFreeNotification(Self);
  262. FContextMenu := Value;
  263. if (Value <> nil) then
  264. Value.FreeNotification(Self);
  265. end;
  266. end;
  267. procedure TDropContextMenu.Notification(AComponent: TComponent;
  268. Operation: TOperation);
  269. begin
  270. if (Operation = opRemove) and (AComponent = FContextMenu) then
  271. FContextMenu := nil;
  272. inherited;
  273. end;
  274. ////////////////////////////////////////////////////////////////////////////////
  275. //
  276. // TDropContextMenuFactory
  277. //
  278. ////////////////////////////////////////////////////////////////////////////////
  279. function TDropContextMenuFactory.HandlerRegSubKey: string;
  280. begin
  281. Result := 'ContextMenuHandlers';
  282. end;
  283. procedure TDropContextMenuFactory.UpdateRegistry(Register: Boolean);
  284. var
  285. ClassIDStr: string;
  286. begin
  287. ClassIDStr := GUIDToString(ClassID);
  288. if Register then
  289. begin
  290. inherited UpdateRegistry(Register);
  291. CreateRegKey(FileClass+'\shellex\'+HandlerRegSubKey+'\'+ClassName, '', ClassIDStr);
  292. if (Win32Platform = VER_PLATFORM_WIN32_NT) then
  293. with TRegistry.Create do
  294. try
  295. RootKey := HKEY_LOCAL_MACHINE;
  296. OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions', True);
  297. OpenKey('Approved', True);
  298. WriteString(ClassIDStr, Description);
  299. finally
  300. Free;
  301. end;
  302. end else
  303. begin
  304. if (Win32Platform = VER_PLATFORM_WIN32_NT) then
  305. with TRegistry.Create do
  306. try
  307. RootKey := HKEY_LOCAL_MACHINE;
  308. OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions', True);
  309. OpenKey('Approved', True);
  310. DeleteKey(ClassIDStr);
  311. finally
  312. Free;
  313. end;
  314. DeleteRegKey(FileClass+'\shellex\'+HandlerRegSubKey+'\'+ClassName);
  315. inherited UpdateRegistry(Register);
  316. end;
  317. end;
  318. end.