Unit1.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. unit Unit1;
  2. interface
  3. uses
  4. DragDrop,
  5. DropSource,
  6. DropTarget,
  7. DragDropPIDL,
  8. PathComboBox,
  9. Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  10. ComCtrls, ActiveX, ShellApi, ShlObj, Buttons, ExtCtrls,StdCtrls, CommCtrl;
  11. type
  12. TForm1 = class(TForm)
  13. ListView1: TListView;
  14. Panel1: TPanel;
  15. DropPIDLSource1: TDropPIDLSource;
  16. Button1: TButton;
  17. StatusBar1: TStatusBar;
  18. Label1: TLabel;
  19. DropPIDLTarget1: TDropPIDLTarget;
  20. sbUpLevel: TSpeedButton;
  21. procedure FormCreate(Sender: TObject);
  22. procedure FormDestroy(Sender: TObject);
  23. procedure ListView1MouseDown(Sender: TObject; Button: TMouseButton;
  24. Shift: TShiftState; X, Y: Integer);
  25. procedure Button1Click(Sender: TObject);
  26. procedure ListView1DblClick(Sender: TObject);
  27. procedure ListView1KeyPress(Sender: TObject; var Key: Char);
  28. procedure DropPIDLTarget1Drop(Sender: TObject; ShiftState: TShiftState;
  29. Point: TPoint; var Effect: Integer);
  30. procedure sbUpLevelClick(Sender: TObject);
  31. procedure DropPIDLTarget1DragOver(Sender: TObject;
  32. ShiftState: TShiftState; Point: TPoint; var Effect: Integer);
  33. private
  34. CurrentShellFolder: IShellFolder;
  35. CurrentFolderImageIndex: integer;
  36. FImageList: TImageList;
  37. FRecyclePIDL: pItemIdList;
  38. FIsDragging: boolean;
  39. //custom component NOT installed in the IDE...
  40. PathComboBox: TPathComboBox;
  41. procedure PathComboBoxChange(Sender: TObject);
  42. procedure SetCurrentFolder;
  43. procedure RefreshListNames;
  44. procedure PopulateListview;
  45. public
  46. { Public declarations }
  47. end;
  48. var
  49. Form1: TForm1;
  50. implementation
  51. {$R *.DFM}
  52. //---------------------------------------------------------------------
  53. // TLVItemData class
  54. // (objects used to store extra data with each Listview item)
  55. //---------------------------------------------------------------------
  56. type
  57. TLVItemData = class
  58. SortStr: string; {just used to sort the listview}
  59. RelativePIDL: pItemIDList; {each item stores its own PIDLs}
  60. AbsolutePIDL: pItemIDList;
  61. public
  62. destructor Destroy; override;
  63. end;
  64. destructor TLVItemData.Destroy;
  65. begin
  66. //nb: ShellMalloc interface declared and assigned in DropSource.pas
  67. ShellMalloc.Free(RelativePIDL);
  68. ShellMalloc.Free(AbsolutePIDL);
  69. inherited;
  70. end;
  71. //---------------------------------------------------------------------
  72. // Local functions ...
  73. //---------------------------------------------------------------------
  74. //Used to sort the listview...
  75. function ListviewSort(Item1, Item2: TListItem;
  76. lParam: Integer): Integer; stdcall;
  77. Begin
  78. if (Item1<>nil) and (Item2<>nil) and (Item1<>Item2) then
  79. Result:= lstrcmpi( pChar(TLVItemData(Item1.Data).SortStr),
  80. pChar(TLVItemData(Item2.Data).SortStr) )
  81. else Result:=0;
  82. End;
  83. //---------------------------------------------------------------------
  84. //Just used for sorting listview...
  85. function GetPathName(Folder: IShellFolder; Pidl: PItemIdList): String;
  86. var StrRet: TStrRet;
  87. Begin
  88. Result:='';
  89. Folder.GetDisplayNameOf(Pidl,SHGDN_FORPARSING,StrRet);
  90. case StrRet.uType of
  91. STRRET_WSTR: Result:=WideCharToString(StrRet.pOleStr);
  92. STRRET_OFFSET: Result:=PChar(UINT(Pidl)+StrRet.uOffset);
  93. STRRET_CSTR: Result:=StrRet.cStr;
  94. End;
  95. end;
  96. //---------------------------------------------------------------------
  97. // TForm1 class ...
  98. //---------------------------------------------------------------------
  99. procedure TForm1.FormCreate(Sender: TObject);
  100. var
  101. sfi: TShFileInfo;
  102. begin
  103. //get access to the shell imagelist...
  104. FImageList := TImageList.create(self);
  105. FImageList.handle :=
  106. shgetfileinfo('',0,sfi,sizeof(tshfileinfo), shgfi_sysiconindex or shgfi_smallicon);
  107. FImageList.shareimages := true;
  108. FImageList.BlendColor := clHighlight;
  109. Listview1.SmallImages := FImageList;
  110. //Create our custom component...
  111. PathComboBox := TPathComboBox.create(self);
  112. PathComboBox.parent := self;
  113. PathComboBox.top := 35;
  114. PathComboBox.left := 2;
  115. PathComboBox.width := 434;
  116. PathComboBox.ShowVirtualFolders := true;
  117. PathComboBox.OnChange := PathComboBoxChange;
  118. PathComboBox.path := extractfilepath(paramstr(0));
  119. //SetCurrentFolder;
  120. DropPIDLTarget1.register(Listview1);
  121. fRecyclePIDL := nil;
  122. ShGetSpecialFolderLocation(0,CSIDL_BITBUCKET ,fRecyclePIDL);
  123. end;
  124. //---------------------------------------------------------------------
  125. procedure TForm1.FormDestroy(Sender: TObject);
  126. var
  127. i: integer;
  128. begin
  129. DropPIDLTarget1.unregister;
  130. with Listview1.items do
  131. for i := 0 to Count-1 do
  132. TLVItemData(Item[i].data).free;
  133. FImageList.free;
  134. ShellMalloc.Free(fRecyclePIDL);
  135. end;
  136. //---------------------------------------------------------------------
  137. //---------------------------------------------------------------------
  138. // Start a Drag and Drop (DropPIDLSource1.execute) ...
  139. //---------------------------------------------------------------------
  140. procedure TForm1.ListView1MouseDown(Sender: TObject; Button: TMouseButton;
  141. Shift: TShiftState; X, Y: Integer);
  142. const
  143. AllowedAttribMask: Longint = (SFGAO_CANCOPY or SFGAO_CANMOVE);
  144. var
  145. i: integer;
  146. attr: UINT;
  147. res: TDragResult;
  148. tmpImageList: TImageList;
  149. dummyPt: TPoint;
  150. DraggingFromRecycle: boolean;
  151. attributes: integer;
  152. begin
  153. //If no files selected then exit...
  154. if Listview1.SelCount = 0 then
  155. exit;
  156. statusbar1.simpletext := '';
  157. if (DragDetectPlus(TWinControl(Sender).Handle, Point(X,Y))) then
  158. begin
  159. // OK, HOW TO KNOW IF WE'RE DRAGGING FROM THE 'RECYCLE BIN'...
  160. DraggingFromRecycle := False;
  161. // ILIsEqual() doesn't seem to work in Win95/Win98 ...
  162. if ILIsEqual(fRecyclePIDL,PathCombobox.pidl) then
  163. DraggingFromRecycle := true
  164. else
  165. begin
  166. // OK, not great but this works in Win95/Win98 ...
  167. attributes := integer(GetFileAttributes(pchar(PathCombobox.path)));
  168. if (attributes <> -1) and (attributes and FILE_ATTRIBUTE_HIDDEN <> 0) and
  169. (attributes and FILE_ATTRIBUTE_SYSTEM <> 0) then
  170. DraggingFromRecycle := true;
  171. end;
  172. // CopyFolderPidlToList automatically deletes anything from a previous dragdrop...
  173. DropPIDLSource1.CopyFolderPidlToList(PathComboBox.Pidl);
  174. // Fill DropSource1.Files with selected files in ListView1...
  175. for i := 0 to Listview1.items.Count-1 do
  176. if (Listview1.items.item[i].Selected) then
  177. with TLVItemData(Listview1.items.item[i].data) do
  178. begin
  179. // Make sure the shell allows us to drag each selected file/folder ...
  180. attr := AllowedAttribMask;
  181. CurrentShellFolder.GetAttributesOf(1,RelativePIDL,attr);
  182. // If not allowed to copy and move the quit drag...
  183. if (attr and AllowedAttribMask) = 0 then
  184. exit;
  185. DropPIDLSource1.CopyFilePidlToList(RelativePIDL);
  186. if DraggingFromRecycle then
  187. DropPIDLSource1.MappedNames.add(Listview1.items.item[i].Caption);
  188. end;
  189. //Let Listview1 draw the drag image for us ...
  190. tmpImageList := TImageList.Create(Self);
  191. try
  192. tmpImageList.handle :=
  193. ListView_CreateDragImage(Listview1.Handle, Listview1.Selected.Index, dummyPt);
  194. DropPIDLSource1.Images := tmpImageList;
  195. DropPIDLSource1.ShowImage := True;
  196. statusbar1.SimpleText := 'Dragging ...';
  197. // DropPIDLTarget1.dragtypes := [];
  198. // the above line has been commented out to
  199. // allow dropping onto self if a subfolder is the droptarget...
  200. // see DropPIDLTarget1DragOver()
  201. //Do the dragdrop...
  202. FIsDragging := True;
  203. try
  204. res := DropPIDLSource1.Execute;
  205. finally
  206. FIsDragging := False;
  207. end;
  208. finally
  209. tmpImageList.Free;
  210. end;
  211. //DropPIDLTarget1.dragtypes := [dtCopy,dtMove];
  212. if res in [drDropCopy, drDropMove] then
  213. statusbar1.simpletext := 'Drag and Drop succeeded.'
  214. else
  215. statusbar1.simpletext := 'Drag and Drop cancelled.';
  216. if (res <> drDropMove) then
  217. exit;
  218. // This is a real kludge, which also may not be long enough...
  219. // See detailed demo for a much better solution.
  220. sleep(1000);
  221. RefreshListNames;
  222. end;
  223. end;
  224. //---------------------------------------------------------------------
  225. // DropPIDLTarget1 Methods ...
  226. //---------------------------------------------------------------------
  227. // If the Listview's droptarget is a system folder then
  228. // make sure the target highlighting is done 'cleanly'...
  229. // otherwise don't allow the drop.
  230. procedure TForm1.DropPIDLTarget1DragOver(Sender: TObject;
  231. ShiftState: TShiftState; Point: TPoint; var Effect: Integer);
  232. var
  233. NewTargetListItem: TListItem;
  234. begin
  235. NewTargetListItem := Listview1.GetItemAt(Point.X,Point.Y);
  236. if (NewTargetListItem = nil) then
  237. begin
  238. // If a folder was previously a droptarget cancel its droptarget status...
  239. if (Listview1.DropTarget <> nil) then
  240. begin
  241. // Hide the drag image.
  242. DropPIDLTarget1.ShowImage := False;
  243. try
  244. // cancel current droptarget folder as droptarget...
  245. Listview1.DropTarget := nil;
  246. Listview1.Update;
  247. finally
  248. // Windows must have time to repaint the invalidated listview
  249. // items before we show the drag image again.
  250. DropPIDLTarget1.ShowImage := True;
  251. end;
  252. end;
  253. // Only allow a drop into current folder if we ourself are not the source,
  254. // and the destination folder isn't virtual...
  255. if (FIsDragging) or (PathCombobox.IsVirtualPath) then
  256. Effect := DROPEFFECT_NONE;
  257. end else
  258. if (Listview1.DropTarget = NewTargetListItem) then
  259. //Effect := Effect //ie: don't fiddle with Effect
  260. else
  261. if (TLVItemData(NewTargetListItem.data).sortstr[1] = '1') then
  262. begin
  263. // only allow file system folders to be targets...
  264. // Hide the drag image...
  265. DropPIDLTarget1.ShowImage := false;
  266. try
  267. // Cancel current droptarget folder as droptarget...
  268. Listview1.DropTarget := nil;
  269. // set the new droptarget folder...
  270. Listview1.DropTarget := NewTargetListItem;
  271. Listview1.Update;
  272. finally
  273. // windows must have time to repaint the invalidated listview
  274. // items before we show the drag image again.
  275. DropPIDLTarget1.ShowImage := True;
  276. end;
  277. end else
  278. Effect := DROPEFFECT_NONE;
  279. end;
  280. //---------------------------------------------------------------------
  281. procedure TForm1.DropPIDLTarget1Drop(Sender: TObject;
  282. ShiftState: TShiftState; Point: TPoint; var Effect: Integer);
  283. var
  284. i: integer;
  285. fos: TShFileOpStruct;
  286. strFrom, strTo, DestPath: string;
  287. Operation: integer;
  288. begin
  289. //first, where are we dropping TO...
  290. strTo := '';
  291. if (Listview1.DropTarget <> nil) then
  292. //dropping into a subfolder...
  293. with TLVItemData(Listview1.DropTarget.data) do
  294. begin
  295. if sortstr[1] = '1' then
  296. strTo := copy(sortstr,2,MAX_PATH)+#0#0
  297. else
  298. Effect := DROPEFFECT_NONE; //subfolder must be a system folder!
  299. end
  300. else if PathComboBox.path <> '' then
  301. //OK, dropping into current folder...
  302. strTo := PathComboBox.path +#0#0
  303. else
  304. Effect := DROPEFFECT_NONE; //current folder must be a system folder!
  305. Operation := 0;
  306. case Effect of
  307. DROPEFFECT_COPY: Operation := FO_COPY;
  308. DROPEFFECT_MOVE: Operation := FO_MOVE;
  309. else
  310. Effect := DROPEFFECT_NONE;
  311. end;
  312. // Only allow a Copy or Move operation...
  313. // otherwise stop and signal to source that no drop occured.
  314. if Effect = DROPEFFECT_NONE then
  315. exit;
  316. // now, where are we dropping FROM...
  317. strFrom := '';
  318. with DropPIDLTarget1 do
  319. begin
  320. for i := 0 to Filenames.count-1 do
  321. if Filenames[i] = '' then
  322. exit
  323. else // quit if 'virtual'
  324. strFrom := strFrom + Filenames[i]+#0;
  325. end;
  326. if strFrom = '' then
  327. begin
  328. //signal to source something wrong...
  329. Effect := DROPEFFECT_NONE;
  330. exit;
  331. end;
  332. with fos do
  333. begin
  334. wnd := self.handle;
  335. wFunc := Operation;
  336. pFrom := PChar(strFrom);
  337. pTo := PChar(strTo);
  338. fFlags := FOF_ALLOWUNDO;
  339. hNameMappings:= nil;
  340. end;
  341. try
  342. // Copy or move the files
  343. SHFileOperation(fos);
  344. except
  345. // Avoid that an exception interrupts the drag/drop prematurely.
  346. on E: Exception do
  347. begin
  348. Application.ShowException(E);
  349. Effect := DROPEFFECT_NONE;
  350. exit;
  351. end;
  352. end;
  353. //if dropped files need to be renamed -
  354. //(eg if they have been dragged from the recycle bin) ...
  355. with DropPIDLTarget1 do
  356. if MappedNames.count > 0 then
  357. begin
  358. if PathComboBox.path[length(PathComboBox.path)] <> '\' then
  359. DestPath := PathComboBox.path + '\' else
  360. DestPath := PathComboBox.path;
  361. for i := 0 to MappedNames.count-1 do
  362. begin
  363. if fileexists(DestPath+ extractfilename(filenames[i])) then
  364. renamefile(DestPath+ extractfilename(filenames[i]),
  365. DestPath+MappedNames[i]);
  366. end;
  367. end;
  368. RefreshListNames;
  369. end;
  370. //---------------------------------------------------------------------
  371. procedure TForm1.SetCurrentFolder;
  372. var
  373. sfi: tshfileinfo;
  374. begin
  375. if PathComboBox.Pidl <> nil then
  376. begin
  377. //Get CurrentShellFolder...
  378. //nb: DesktopShellFolder is a Global Variable declared in PathComboBox.
  379. if PathComboBox.itemindex = 0 then //Desktop folder
  380. CurrentShellFolder := DesktopShellFolder else
  381. DesktopShellFolder.BindToObject(PathComboBox.Pidl,
  382. nil, IID_IShellFolder, pointer(CurrentShellFolder));
  383. //Get CurrentFolder's ImageIndex...
  384. shgetfileinfo(pChar(PathComboBox.Pidl),
  385. 0,sfi,sizeof(tshfileinfo), SHGFI_PIDL or SHGFI_ICON);
  386. CurrentFolderImageIndex := sfi.iIcon;
  387. RefreshListNames;
  388. end;
  389. // Don't allow a drop onto a virtual folder...
  390. if PathComboBox.path <> '' then
  391. DropPIDLTarget1.DragTypes := [dtCopy,dtMove]
  392. else
  393. DropPIDLTarget1.DragTypes := [];
  394. sbUpLevel.Enabled := (PathComboBox.ItemIndex <> 0);
  395. end;
  396. //---------------------------------------------------------------------
  397. procedure TForm1.RefreshListNames;
  398. var
  399. i: integer;
  400. begin
  401. with Listview1.items do
  402. begin
  403. beginupdate;
  404. for i := 0 to Count-1 do
  405. TLVItemData(Item[i].data).free;
  406. clear;
  407. screen.cursor := crHourglass;
  408. PopulateListview;
  409. screen.cursor := crDefault;
  410. endupdate;
  411. end;
  412. end;
  413. //---------------------------------------------------------------------
  414. procedure TForm1.PopulateListview;
  415. var
  416. EnumIdList: IEnumIdList;
  417. tmpPIDL: pItemIDList;
  418. NewItem: TListItem;
  419. ItemData: TLVItemData;
  420. sfi: TShFileInfo;
  421. Flags, dummy: DWORD;
  422. begin
  423. if CurrentShellFolder = nil then
  424. exit;
  425. with Listview1.items do
  426. begin
  427. //get files and folders...
  428. Flags := SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN;
  429. if FAILED(CurrentShellFolder.EnumObjects(0,Flags,EnumIdList)) then exit;
  430. while (EnumIdList.Next(1,tmpPIDL,dummy) = NOERROR) do
  431. begin
  432. NewItem := Add;
  433. NewItem.caption := GetPIDLDisplayName(CurrentShellFolder,tmpPIDL);
  434. ItemData := TLVItemData.create;
  435. NewItem.data := ItemData;
  436. ItemData.RelativePIDL := tmpPIDL;
  437. ItemData.AbsolutePIDL := ILCombine(PathComboBox.Pidl,tmpPIDL);
  438. shgetfileinfo(pChar(ItemData.AbsolutePIDL),
  439. 0,sfi,sizeof(tshfileinfo), SHGFI_PIDL or SHGFI_ICON or SHGFI_ATTRIBUTES);
  440. NewItem.ImageIndex := sfi.iIcon;
  441. //get sort order...
  442. if (sfi.dwAttributes and SFGAO_FOLDER)<>0 then
  443. begin
  444. if (sfi.dwAttributes and SFGAO_FILESYSTEM)<>0 then
  445. //file system folder
  446. ItemData.SortStr := '1'+ GetPathName(CurrentShellFolder,tmpPIDL)
  447. else
  448. //virtual folder
  449. ItemData.SortStr := '2'+ GetPathName(CurrentShellFolder,tmpPIDL);
  450. end
  451. else
  452. //files
  453. ItemData.SortStr := '9'+ GetPathName(CurrentShellFolder,tmpPIDL);
  454. end;
  455. end;
  456. ListView1.CustomSort(@ListviewSort, 0);
  457. if Listview1.items.count > 0 then
  458. Listview1.items[0].focused := true;
  459. end;
  460. //---------------------------------------------------------------------
  461. procedure TForm1.PathComboBoxChange(Sender: TObject);
  462. begin
  463. SetCurrentFolder;
  464. caption := PathComboBox.path;
  465. end;
  466. //---------------------------------------------------------------------
  467. //If a folder double-clicked - open that folder...
  468. procedure TForm1.ListView1DblClick(Sender: TObject);
  469. var
  470. SelItem: TListItem;
  471. begin
  472. SelItem := Listview1.Selected;
  473. if SelItem = nil then exit;
  474. with TLVItemData(SelItem.data) do
  475. if (sortstr[1] < '9') then //if a folder...
  476. PathComboBox.Pidl := AbsolutePIDL;
  477. end;
  478. //---------------------------------------------------------------------
  479. //If a folder selected - open that folder...
  480. procedure TForm1.ListView1KeyPress(Sender: TObject; var Key: Char);
  481. var
  482. SelItem: TListItem;
  483. begin
  484. SelItem := Listview1.Selected;
  485. if (SelItem = nil) or (ord(Key) <> VK_RETURN) then exit;
  486. with TLVItemData(SelItem.data) do
  487. if (sortstr[1] < '9') then //if a folder...
  488. PathComboBox.Pidl := AbsolutePIDL;
  489. end;
  490. //---------------------------------------------------------------------
  491. procedure TForm1.sbUpLevelClick(Sender: TObject);
  492. var
  493. tmpPidl: pItemIdList;
  494. begin
  495. if PathComboBox.ItemIndex > 0 then
  496. begin
  497. tmpPidl := ILClone(PathComboBox.Pidl);
  498. ILRemoveLastID(tmpPidl);
  499. PathComboBox.Pidl := tmpPidl;
  500. ShellMalloc.Free(tmpPidl);
  501. end;
  502. end;
  503. //---------------------------------------------------------------------
  504. procedure TForm1.Button1Click(Sender: TObject);
  505. begin
  506. Close;
  507. end;
  508. //---------------------------------------------------------------------
  509. //---------------------------------------------------------------------
  510. end.