DropFile.pas 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. unit DropFile;
  2. interface
  3. {$include DragDrop.inc}
  4. uses
  5. DragDrop,
  6. DropTarget,
  7. DropSource,
  8. DragDropFile,
  9. {$ifdef VER12_PLUS}
  10. ImgList,
  11. {$endif}
  12. Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  13. StdCtrls, Buttons, ExtCtrls, FileCtrl, Outline, DirOutln, CommCtrl,
  14. ComCtrls, Grids, ActiveX, ShlObj, ComObj, Menus;
  15. type
  16. // This thread is used to watch for and
  17. // display changes in DirectoryOutline.directory
  18. TDirectoryThread = class(TThread)
  19. private
  20. fListView: TListView;
  21. fDirectory: string;
  22. FWakeupEvent: THandle; //Used to signal change of directory or terminating
  23. FFiles: TStrings;
  24. protected
  25. procedure ScanDirectory;
  26. procedure UpdateListView;
  27. procedure SetDirectory(Value: string);
  28. procedure ProcessFilenameChanges(fcHandle: THandle);
  29. public
  30. constructor Create(ListView: TListView; Dir: string);
  31. procedure Execute; override;
  32. destructor Destroy; override;
  33. procedure WakeUp;
  34. property Directory: string read FDirectory write SetDirectory;
  35. end;
  36. TFormFile = class(TForm)
  37. DriveComboBox: TDriveComboBox;
  38. DirectoryOutline: TDirectoryOutline;
  39. Memo1: TMemo;
  40. ListView1: TListView;
  41. btnClose: TButton;
  42. StatusBar1: TStatusBar;
  43. DropFileTarget1: TDropFileTarget;
  44. Panel1: TPanel;
  45. DropSource1: TDropFileSource;
  46. ImageList1: TImageList;
  47. DropDummy1: TDropDummy;
  48. PopupMenu1: TPopupMenu;
  49. MenuCopy: TMenuItem;
  50. MenuCut: TMenuItem;
  51. N1: TMenuItem;
  52. MenuPaste: TMenuItem;
  53. procedure DriveComboBoxChange(Sender: TObject);
  54. procedure DirectoryOutlineChange(Sender: TObject);
  55. procedure btnCloseClick(Sender: TObject);
  56. procedure MenuCutOrCopyClick(Sender: TObject);
  57. procedure ListView1MouseDown(Sender: TObject; Button: TMouseButton;
  58. Shift: TShiftState; X, Y: Integer);
  59. procedure FormCreate(Sender: TObject);
  60. procedure FormDestroy(Sender: TObject);
  61. procedure DropSource1Feedback(Sender: TObject; Effect: Integer;
  62. var UseDefaultCursors: Boolean);
  63. procedure DropFileTarget1Enter(Sender: TObject;
  64. ShiftState: TShiftState; Point: TPoint; var Effect: Integer);
  65. procedure DropFileTarget1Drop(Sender: TObject; ShiftState: TShiftState;
  66. Point: TPoint; var Effect: Integer);
  67. procedure DropFileTarget1GetDropEffect(Sender: TObject;
  68. ShiftState: TShiftState; Point: TPoint; var Effect: Integer);
  69. procedure PopupMenu1Popup(Sender: TObject);
  70. procedure MenuPasteClick(Sender: TObject);
  71. procedure DropSource1Paste(Sender: TObject; Action: TDragResult;
  72. DeleteOnPaste: Boolean);
  73. procedure DropSource1AfterDrop(Sender: TObject;
  74. DragResult: TDragResult; Optimized: Boolean);
  75. procedure ListView1CustomDrawItem(Sender: TCustomListView;
  76. Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
  77. private
  78. SourcePath: string;
  79. IsEXEfile: boolean;
  80. DirectoryThread: TDirectoryThread;
  81. public
  82. end;
  83. var
  84. FormFile: TFormFile;
  85. implementation
  86. {$R *.DFM}
  87. // CUSTOM CURSORS:
  88. // The cursors in DropCursors.res are exactly the same as the default cursors.
  89. // Use DropCursors.res as a template if you wish to customise your own cursors.
  90. // For this demo we've created Cursors.res - some coloured cursors.
  91. {$R Cursors.res}
  92. const
  93. crCopy = 101;
  94. crMove = 102;
  95. crLink = 103;
  96. crCopyScroll = 104;
  97. crMoveScroll = 105;
  98. crLinkScroll = 106;
  99. //----------------------------------------------------------------------------
  100. // Miscellaneous utility functions
  101. //----------------------------------------------------------------------------
  102. function AddSlash(path: string): string;
  103. begin
  104. if (path = '') or (path[length(path)]='\') then
  105. Result := path
  106. else
  107. Result := path +'\';
  108. end;
  109. procedure CreateLink(SourceFile, ShortCutName: String);
  110. var
  111. IUnk: IUnknown;
  112. ShellLink: IShellLink;
  113. IPFile: IPersistFile;
  114. tmpShortCutName: string;
  115. WideStr: WideString;
  116. i: integer;
  117. begin
  118. IUnk := CreateComObject(CLSID_ShellLink);
  119. ShellLink := IUnk as IShellLink;
  120. IPFile := IUnk as IPersistFile;
  121. with ShellLink do
  122. begin
  123. SetPath(PChar(SourceFile));
  124. SetWorkingDirectory(PChar(ExtractFilePath(SourceFile)));
  125. end;
  126. ShortCutName := ChangeFileExt(ShortCutName,'.lnk');
  127. if FileExists(ShortCutName) then
  128. begin
  129. ShortCutName := copy(ShortCutName, 1, length(ShortCutName)-4);
  130. i := 1;
  131. repeat
  132. tmpShortCutName := ShortCutName +'(' + inttostr(i)+ ').lnk';
  133. inc(i);
  134. until not FileExists(tmpShortCutName);
  135. WideStr := tmpShortCutName;
  136. end
  137. else WideStr := ShortCutName;
  138. IPFile.Save(PWChar(WideStr), False);
  139. end;
  140. //----------------------------------------------------------------------------
  141. // TFormFile methods
  142. //----------------------------------------------------------------------------
  143. procedure TFormFile.FormCreate(Sender: TObject);
  144. begin
  145. // Load custom cursors...
  146. Screen.cursors[crCopy] := LoadCursor(hinstance, 'CUR_DRAG_COPY');
  147. Screen.cursors[crMove] := LoadCursor(hinstance, 'CUR_DRAG_MOVE');
  148. Screen.cursors[crLink] := LoadCursor(hinstance, 'CUR_DRAG_LINK');
  149. Screen.cursors[crCopyScroll] := LoadCursor(hinstance, 'CUR_DRAG_COPY_SCROLL');
  150. Screen.cursors[crMoveScroll] := LoadCursor(hinstance, 'CUR_DRAG_MOVE_SCROLL');
  151. Screen.cursors[crLinkScroll] := LoadCursor(hinstance, 'CUR_DRAG_LINK_SCROLL');
  152. end;
  153. procedure TFormFile.FormDestroy(Sender: TObject);
  154. begin
  155. if (DirectoryThread <> nil) then
  156. begin
  157. DirectoryThread.Terminate;
  158. DirectoryThread.WakeUp;
  159. end;
  160. end;
  161. procedure TFormFile.btnCloseClick(Sender: TObject);
  162. begin
  163. Close;
  164. end;
  165. procedure TFormFile.DriveComboBoxChange(Sender: TObject);
  166. begin
  167. // Manual synchronization to work around bug in TDirectoryOutline.
  168. DirectoryOutline.Drive := DriveComboBox.Drive;
  169. end;
  170. procedure TFormFile.DirectoryOutlineChange(Sender: TObject);
  171. begin
  172. if (DirectoryThread = nil) then
  173. DirectoryThread := TDirectoryThread.Create(ListView1, DirectoryOutline.Directory)
  174. else
  175. DirectoryThread.Directory := DirectoryOutline.Directory;
  176. end;
  177. procedure TFormFile.ListView1MouseDown(Sender: TObject;
  178. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  179. var
  180. i: integer;
  181. Filename: string;
  182. Res: TDragResult;
  183. begin
  184. // If no files selected then exit...
  185. if Listview1.SelCount = 0 then
  186. exit;
  187. // Wait for user to move cursor before we start the drag/drop.
  188. if (DragDetectPlus(TWinControl(Sender).Handle, Point(X,Y))) then
  189. begin
  190. Statusbar1.SimpleText := '';
  191. DropSource1.Files.Clear;
  192. // DropSource1.MappedNames.Clear;
  193. // Fill DropSource1.Files with selected files in ListView1
  194. for i := 0 to Listview1.Items.Count-1 do
  195. if (Listview1.Items[i].Selected) then
  196. begin
  197. Filename :=
  198. AddSlash(DirectoryOutline.Directory)+Listview1.Items[i].Caption;
  199. DropSource1.Files.Add(Filename);
  200. // The TDropFileSource.MappedNames list can be used to indicate to the
  201. // drop target, that the files should be renamed once theuy have been
  202. // copied. This is the technique used, when dragging files from the
  203. // recycle bin.
  204. // DropSource1.MappedNames.Add('NewFileName'+inttostr(i+1));
  205. end;
  206. // Temporarily disable the list view as a drop target.
  207. DropFileTarget1.Dragtypes := [];
  208. try
  209. // OK, now we are all set to go. Let's start the drag...
  210. res := DropSource1.Execute;
  211. finally
  212. // Enable the list view as a drop target again.
  213. DropFileTarget1.Dragtypes := [dtCopy,dtMove,dtLink];
  214. end;
  215. // Note:
  216. // The target is responsible, from this point on, for the
  217. // copying/moving/linking of the file but the target feeds
  218. // back to the source what (should have) happened via the
  219. // returned value of Execute.
  220. // Feedback in Statusbar1 what happened...
  221. case Res of
  222. drDropCopy: StatusBar1.SimpleText := 'Copied successfully';
  223. drDropMove: StatusBar1.SimpleText := 'Moved successfully';
  224. drDropLink: StatusBar1.SimpleText := 'Linked successfully';
  225. drCancel: StatusBar1.SimpleText := 'Drop was cancelled';
  226. drOutMemory: StatusBar1.SimpleText := 'Drop cancelled - out of memory';
  227. else
  228. StatusBar1.SimpleText := 'Drop cancelled - unknown reason';
  229. end;
  230. end;
  231. end;
  232. procedure TFormFile.ListView1CustomDrawItem(Sender: TCustomListView;
  233. Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
  234. begin
  235. // Items which have been "cut to clipboard" are drawn differently.
  236. if boolean(Item.Data) then
  237. Sender.Canvas.Font.Style := [fsStrikeOut];
  238. end;
  239. procedure TFormFile.PopupMenu1Popup(Sender: TObject);
  240. var
  241. DataObject: IDataObject;
  242. begin
  243. MenuCopy.Enabled := (Listview1.SelCount > 0);
  244. MenuCut.Enabled := MenuCopy.Enabled;
  245. // Open the clipboard as an IDataObject
  246. OleCheck(OleGetClipboard(DataObject));
  247. try
  248. // Enable paste menu if the clipboard contains data in any of
  249. // the supported formats
  250. MenuPaste.Enabled := DropFileTarget1.HasValidFormats(DataObject);
  251. finally
  252. DataObject := nil;
  253. end;
  254. end;
  255. // Demonstrates CopyToClipboard and CutToClipboard methods.
  256. procedure TFormFile.MenuCutOrCopyClick(Sender: TObject);
  257. var
  258. i: integer;
  259. Filename: string;
  260. Status: boolean;
  261. Operation: string;
  262. begin
  263. if Listview1.SelCount = 0 then
  264. begin
  265. StatusBar1.SimpleText := 'No files have been selected!';
  266. exit;
  267. end;
  268. DropSource1.Files.clear;
  269. for i := 0 to Listview1.Items.Count-1 do
  270. if (Listview1.Items[i].Selected) then
  271. begin
  272. Filename :=
  273. AddSlash(DirectoryOutline.Directory)+Listview1.Items[i].Caption;
  274. DropSource1.Files.Add(Filename);
  275. // Flag item as "cut" so it can be drawn differently.
  276. if (Sender = MenuCut) then
  277. Listview1.items.Item[i].Data := pointer(True)
  278. else
  279. Listview1.items.Item[i].Data := pointer(False);
  280. end else
  281. Listview1.items.Item[i].Data := pointer(False);
  282. Listview1.Invalidate;
  283. // Transfer data to clipboard.
  284. if (Sender = MenuCopy) then
  285. begin
  286. Status := DropSource1.CopyToClipboard;
  287. Operation := 'copied';
  288. end else
  289. if (Sender = MenuCut) then
  290. begin
  291. Status := DropSource1.CutToClipboard;
  292. Operation := 'cut';
  293. end else
  294. Status := False;
  295. if (Status) then
  296. StatusBar1.SimpleText :=
  297. Format('%d file(s) %s to clipboard.',[DropSource1.Files.Count, Operation]);
  298. end;
  299. procedure TFormFile.MenuPasteClick(Sender: TObject);
  300. begin
  301. // PasteFromClipboard fires an OnDrop event, so we don't need to do
  302. // anything special here.
  303. DropFileTarget1.PasteFromClipboard;
  304. end;
  305. //--------------------------
  306. // SOURCE events...
  307. //--------------------------
  308. procedure TFormFile.DropSource1Feedback(Sender: TObject; Effect: Integer;
  309. var UseDefaultCursors: Boolean);
  310. begin
  311. UseDefaultCursors := False; // We want to use our own.
  312. case DWORD(Effect) of
  313. DROPEFFECT_COPY:
  314. Windows.SetCursor(Screen.Cursors[crCopy]);
  315. DROPEFFECT_MOVE:
  316. Windows.SetCursor(Screen.Cursors[crMove]);
  317. DROPEFFECT_LINK:
  318. Windows.SetCursor(Screen.Cursors[crLink]);
  319. DROPEFFECT_SCROLL OR DROPEFFECT_COPY:
  320. Windows.SetCursor(Screen.Cursors[crCopyScroll]);
  321. DROPEFFECT_SCROLL OR DROPEFFECT_MOVE:
  322. Windows.SetCursor(Screen.Cursors[crMoveScroll]);
  323. DROPEFFECT_SCROLL OR DROPEFFECT_LINK:
  324. Windows.SetCursor(Screen.Cursors[crLinkScroll]);
  325. else
  326. UseDefaultCursors := True; // Use default NoDrop
  327. end;
  328. end;
  329. procedure TFormFile.DropSource1AfterDrop(Sender: TObject;
  330. DragResult: TDragResult; Optimized: Boolean);
  331. var
  332. i : integer;
  333. begin
  334. // Delete source files if target performed an unoptimized drag/move
  335. // operation (target copies files, source deletes them).
  336. if (DragResult = drDropMove) and (not Optimized) then
  337. for i := 0 to DropSource1.Files.Count-1 do
  338. DeleteFile(DropSource1.Files[i]);
  339. end;
  340. procedure TFormFile.DropSource1Paste(Sender: TObject; Action: TDragResult;
  341. DeleteOnPaste: Boolean);
  342. var
  343. i : integer;
  344. begin
  345. StatusBar1.SimpleText := 'Target pasted file(s)';
  346. // Delete source files if target performed a paste/move operation and
  347. // requested the source to "Delete on paste".
  348. if (DeleteOnPaste) then
  349. for i := 0 to DropSource1.Files.Count-1 do
  350. DeleteFile(DropSource1.Files[i]);
  351. end;
  352. //--------------------------
  353. // TARGET events...
  354. //--------------------------
  355. procedure TFormFile.DropFileTarget1Enter(Sender: TObject;
  356. ShiftState: TShiftState; Point: TPoint; var Effect: Integer);
  357. begin
  358. //Note: GetDataOnEnter = true ...
  359. // Save the location (path) of the files being dragged.
  360. // Also flags if an EXE file is being dragged.
  361. // This info will be used to set the default (ie. no Shift or Ctrl Keys
  362. // pressed) drag behaviour (COPY, MOVE or LINK).
  363. if (DropFileTarget1.Files.count > 0) then
  364. begin
  365. SourcePath := ExtractFilePath(DropFileTarget1.Files[0]);
  366. IsEXEfile := (DropFileTarget1.Files.count = 1) and
  367. (AnsiCompareText(ExtractFileExt(DropFileTarget1.Files[0]), '.exe') = 0);
  368. end;
  369. end;
  370. procedure TFormFile.DropFileTarget1Drop(Sender: TObject;
  371. ShiftState: TShiftState; Point: TPoint; var Effect: Integer);
  372. var
  373. i, SuccessCnt: integer;
  374. NewFilename: string;
  375. newPath: string;
  376. begin
  377. SuccessCnt := 0;
  378. NewPath := AddSlash(DirectoryOutline.Directory);
  379. // Filter out the DROPEFFECT_SCROLL flag if set...
  380. // (ie: when dropping a file while the target window is scrolling)
  381. Effect := Effect and not DROPEFFECT_SCROLL;
  382. // Now, 'Effect' should equal one of the following:
  383. // DROPEFFECT_COPY, DROPEFFECT_MOVE or DROPEFFECT_LINK
  384. // Note however, that if we call TDropTarget.PasteFromClipboard, Effect
  385. // can be a combination of the above drop effects.
  386. for i := 0 to DropFileTarget1.Files.count-1 do
  387. begin
  388. // Name mapping occurs when dragging files from Recycled Bin...
  389. // In most situations Name Mapping can be ignored entirely.
  390. if (i < DropFileTarget1.MappedNames.Count) then
  391. NewFilename := NewPath+DropFileTarget1.MappedNames[i]
  392. else
  393. NewFilename := NewPath+ExtractFilename(DropFileTarget1.Files[i]);
  394. if not FileExists(NewFilename) then
  395. begin
  396. if NewFilename = DropFileTarget1.Files[i] then
  397. begin
  398. Windows.MessageBox(Handle,
  399. 'The destination folder is the same as the source!',
  400. 'Drag/Drop Demo', mb_iconStop or mb_OK);
  401. Break;
  402. end;
  403. try
  404. if (Effect and DROPEFFECT_COPY <> 0) then
  405. begin
  406. Effect := DROPEFFECT_COPY;
  407. // Copy the file.
  408. if CopyFile(PChar(DropFileTarget1.Files[i]), PChar(NewFilename), True) then
  409. inc(SuccessCnt);
  410. end else
  411. if (Effect and DROPEFFECT_MOVE <> 0) then
  412. begin
  413. Effect := DROPEFFECT_MOVE;
  414. // Move the file.
  415. if RenameFile(DropFileTarget1.Files[i], NewFilename) then
  416. inc(SuccessCnt)
  417. end;
  418. except
  419. // Ignore errors.
  420. end;
  421. end;
  422. if (Effect and DROPEFFECT_LINK <> 0) then
  423. begin
  424. Effect := DROPEFFECT_LINK;
  425. // Create a shell link to the file.
  426. CreateLink(DropFileTarget1.Files[i], NewFilename);
  427. inc(SuccessCnt);
  428. end;
  429. end;
  430. if (Effect = DROPEFFECT_MOVE) then
  431. StatusBar1.SimpleText :=
  432. Format('%d file(s) were moved. Files dropped at point (%d,%d).',
  433. [SuccessCnt, Point.x, Point.y])
  434. else if (Effect = DROPEFFECT_COPY) then
  435. StatusBar1.SimpleText :=
  436. Format('%d file(s) were copied. Files dropped at point (%d,%d).',
  437. [SuccessCnt, Point.x, Point.y])
  438. else
  439. StatusBar1.SimpleText :=
  440. Format('%d file(s) were linked. Files dropped at point (%d,%d).',
  441. [SuccessCnt, Point.x, Point.y]);
  442. end;
  443. procedure TFormFile.DropFileTarget1GetDropEffect(Sender: TObject;
  444. ShiftState: TShiftState; Point: TPoint; var Effect: Integer);
  445. var
  446. Scroll: DWORD;
  447. begin
  448. // Note: The 'Effect' parameter (on event entry) is the
  449. // set of effects allowed by both the source and target.
  450. // Use this event when you wish to override the Default behaviour...
  451. // Save the value of the auto scroll flag.
  452. // As an alternative we could implement our own auto scroll logic here.
  453. Scroll := DWORD(Effect) and DROPEFFECT_SCROLL;
  454. // We're only interested in ssShift & ssCtrl here so
  455. // mouse buttons states are screened out ...
  456. ShiftState := ([ssShift, ssCtrl] * ShiftState);
  457. // Reject the drop if source and target paths are the same (DROPEFFECT_NONE).
  458. if (AddSlash(DirectoryOutline.Directory) = SourcePath) then
  459. Effect := DROPEFFECT_NONE
  460. // else if Ctrl+Shift are pressed then create a link (DROPEFFECT_LINK).
  461. else if (ShiftState = [ssShift, ssCtrl]) and
  462. (Effect and DROPEFFECT_LINK <> 0) then Effect := DROPEFFECT_LINK
  463. // else if Shift is pressed then move (DROPEFFECT_MOVE).
  464. else if (ShiftState = [ssShift]) and
  465. (Effect and DROPEFFECT_MOVE<>0) then Effect := DROPEFFECT_MOVE
  466. // else if Ctrl is pressed then copy (DROPEFFECT_COPY).
  467. else if (ShiftState = [ssCtrl]) and
  468. (Effect and DROPEFFECT_COPY<>0) then Effect := DROPEFFECT_COPY
  469. // else if dragging a single EXE file then default to link (DROPEFFECT_LINK).
  470. else if IsEXEfile and (Effect and DROPEFFECT_LINK<>0) then
  471. Effect := DROPEFFECT_LINK
  472. // else if source and target drives are the same then default to MOVE (DROPEFFECT_MOVE).
  473. else if (SourcePath <> '') and (DirectoryOutline.Directory[1] = SourcePath[1]) and
  474. (Effect and DROPEFFECT_MOVE<>0) then Effect := DROPEFFECT_MOVE
  475. // otherwise just use whatever we can get away with.
  476. else if (Effect and DROPEFFECT_COPY<>0) then Effect := DROPEFFECT_COPY
  477. else if (Effect and DROPEFFECT_MOVE<>0) then Effect := DROPEFFECT_MOVE
  478. else if (Effect and DROPEFFECT_LINK<>0) then Effect := DROPEFFECT_LINK
  479. else Effect := DROPEFFECT_NONE;
  480. // Restore auto scroll flag.
  481. Effect := Effect or integer(Scroll);
  482. end;
  483. //----------------------------------------------------------------------------
  484. // TDirectoryThread
  485. // This thread monitors the current directory for changes and updates the
  486. // listview whenever the directory is changed (files added, renamed or deleted).
  487. //----------------------------------------------------------------------------
  488. // OK, we're showing off... This is a little overkill for a demo,
  489. // but still you can see what can be done.
  490. constructor TDirectoryThread.Create(ListView: TListView; Dir: string);
  491. begin
  492. inherited Create(True);
  493. fListView := ListView;
  494. FreeOnTerminate := True;
  495. Priority := tpLowest;
  496. fDirectory := Dir;
  497. FWakeupEvent := Windows.CreateEvent(nil, False, False, nil);
  498. FFiles := TStringList.Create;
  499. Resume;
  500. end;
  501. destructor TDirectoryThread.Destroy;
  502. begin
  503. CloseHandle(FWakeupEvent);
  504. FFiles.Free;
  505. inherited Destroy;
  506. end;
  507. procedure TDirectoryThread.WakeUp;
  508. begin
  509. SetEvent(FWakeupEvent);
  510. end;
  511. procedure TDirectoryThread.SetDirectory(Value: string);
  512. begin
  513. if (Value = FDirectory) then
  514. exit;
  515. FDirectory := Value;
  516. WakeUp;
  517. end;
  518. procedure TDirectoryThread.ScanDirectory;
  519. var
  520. sr: TSearchRec;
  521. res: integer;
  522. begin
  523. FFiles.Clear;
  524. res := FindFirst(AddSlash(fDirectory)+'*.*', 0, sr);
  525. try
  526. while (res = 0) and (not Terminated) do
  527. begin
  528. if (sr.Name <> '.') and (sr.Name <> '..') then
  529. FFiles.Add(lowercase(sr.Name));
  530. res := FindNext(sr);
  531. end;
  532. finally
  533. FindClose(sr);
  534. end;
  535. end;
  536. procedure TDirectoryThread.UpdateListView;
  537. var
  538. NewItem : TListItem;
  539. i: integer;
  540. begin
  541. fListView.Items.BeginUpdate;
  542. try
  543. fListView.Items.clear;
  544. for i := 0 to FFiles.Count-1 do
  545. begin
  546. NewItem := fListView.Items.Add;
  547. NewItem.Caption := FFiles[i];
  548. end;
  549. if fListView.Items.Count > 0 then
  550. fListView.ItemFocused := fListView.Items[0];
  551. finally
  552. fListView.Items.EndUpdate;
  553. end;
  554. FFiles.Clear;
  555. end;
  556. procedure TDirectoryThread.Execute;
  557. var
  558. fFileChangeHandle : THandle;
  559. begin
  560. // OUTER LOOP - which will exit only when terminated ...
  561. // directory changes will be processed within this OUTER loop
  562. // (file changes will be processed within the INNER loop)
  563. while (not Terminated) do
  564. begin
  565. ScanDirectory;
  566. Synchronize(UpdateListView);
  567. //Monitor directory for file changes
  568. fFileChangeHandle :=
  569. FindFirstChangeNotification(PChar(fDirectory), False,
  570. FILE_NOTIFY_CHANGE_FILE_NAME);
  571. if (fFileChangeHandle = INVALID_HANDLE_VALUE) then
  572. //Can't monitor filename changes! Just wait for change of directory or terminate
  573. WaitForSingleObject(FWakeupEvent, INFINITE)
  574. else
  575. try
  576. //This function performs an INNER loop...
  577. ProcessFilenameChanges(fFileChangeHandle);
  578. finally
  579. FindCloseChangeNotification(fFileChangeHandle);
  580. end;
  581. end;
  582. end;
  583. procedure TDirectoryThread.ProcessFilenameChanges(fcHandle: THandle);
  584. var
  585. WaitResult : DWORD;
  586. HandleArray : array[0..1] of THandle;
  587. begin
  588. HandleArray[0] := FWakeupEvent;
  589. HandleArray[1] := fcHandle;
  590. // INNER LOOP -
  591. // which will exit only if terminated or the directory is changed
  592. // filename changes will be processed within this loop
  593. while (not Terminated) do
  594. begin
  595. //wait for either filename or directory change, or terminate...
  596. WaitResult := WaitForMultipleObjects(2, PWOHandleArray(@HandleArray), False,
  597. INFINITE);
  598. if (WaitResult = WAIT_OBJECT_0 + 1) then //filename has changed
  599. begin
  600. repeat //collect all immediate filename changes...
  601. FindNextChangeNotification(fcHandle);
  602. until Terminated or (WaitForSingleObject(fcHandle, 0) <> WAIT_OBJECT_0);
  603. if Terminated then
  604. Break;
  605. // OK, now update (before restarting inner loop)...
  606. ScanDirectory;
  607. Synchronize(UpdateListView);
  608. end else
  609. begin // Either directory changed or terminated ...
  610. //collect all (almost) immediate directory changes before exiting...
  611. while (not Terminated) and
  612. (WaitForSingleObject(FWakeupEvent, 100) = WAIT_OBJECT_0) do {nothing};
  613. break;
  614. end;
  615. end;
  616. end;
  617. end.