FolderTreeView.pas 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354
  1. unit FolderTreeView;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2026 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. TFolderTreeView component
  8. }
  9. interface
  10. uses
  11. Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, CommCtrl;
  12. type
  13. TCustomFolderTreeView = class;
  14. TFolderRenameEvent = procedure(Sender: TCustomFolderTreeView;
  15. var NewName: String; var Accept: Boolean) of object;
  16. TCustomFolderTreeView = class(TWinControl)
  17. private
  18. FDestroyingHandle: Boolean;
  19. FDirectory: String;
  20. FItemExpanding: Boolean;
  21. FOnChange: TNotifyEvent;
  22. FOnRename: TFolderRenameEvent;
  23. class constructor Create;
  24. class destructor Destroy;
  25. procedure Change;
  26. procedure DeleteObsoleteNewItems(const ParentItem, ItemToKeep: HTREEITEM);
  27. function FindItem(const ParentItem: HTREEITEM; const AName: String): HTREEITEM;
  28. function FindOrCreateItem(const ParentItem: HTREEITEM; const AName: String): HTREEITEM;
  29. function GetItemFullPath(Item: HTREEITEM): String; virtual;
  30. function InsertItem(const ParentItem: HTREEITEM; const AName, ACustomDisplayName: String;
  31. const ANewItem: Boolean): HTREEITEM;
  32. procedure SelectItem(const Item: HTREEITEM);
  33. procedure SetItemHasChildren(const Item: HTREEITEM; const AHasChildren: Boolean);
  34. procedure SetDirectory(const Value: String);
  35. function TryExpandItem(const Item: HTREEITEM): Boolean;
  36. procedure CNKeyDown(var Message: TWMKeyDown); message CN_KEYDOWN;
  37. procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
  38. procedure WMCtlColorEdit(var Message: TMessage); message WM_CTLCOLOREDIT;
  39. procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY;
  40. procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
  41. protected
  42. function ItemChildrenNeeded(const Item: HTREEITEM): Boolean; virtual; abstract;
  43. procedure CreateParams(var Params: TCreateParams); override;
  44. procedure CreateWnd; override;
  45. function GetItemImageIndex(const Item: HTREEITEM;
  46. const NewItem, SelectedImage: Boolean): Integer; virtual; abstract;
  47. function GetRootItem: HTREEITEM; virtual;
  48. function ItemHasChildren(const Item: HTREEITEM): Boolean; virtual; abstract;
  49. procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  50. property OnChange: TNotifyEvent read FOnChange write FOnChange;
  51. property OnRename: TFolderRenameEvent read FOnRename write FOnRename;
  52. public
  53. constructor Create(AOwner: TComponent); override;
  54. procedure ChangeDirectory(const Value: String; const CreateNewItems: Boolean);
  55. procedure CreateNewDirectory(const ADefaultName: String);
  56. property Directory: String read FDirectory write SetDirectory;
  57. end;
  58. TFolderTreeView = class(TCustomFolderTreeView)
  59. private
  60. procedure RefreshDriveItem(const Item: HTREEITEM; const ANewDisplayName: String);
  61. protected
  62. function ItemChildrenNeeded(const Item: HTREEITEM): Boolean; override;
  63. function ItemHasChildren(const Item: HTREEITEM): Boolean; override;
  64. function GetItemFullPath(Item: HTREEITEM): String; override;
  65. function GetItemImageIndex(const Item: HTREEITEM;
  66. const NewItem, SelectedImage: Boolean): Integer; override;
  67. published
  68. property Anchors;
  69. property TabOrder;
  70. property TabStop default True;
  71. property Visible;
  72. property OnChange;
  73. property OnRename;
  74. end;
  75. TStartMenuFolderTreeView = class(TCustomFolderTreeView)
  76. private
  77. FUserPrograms, FCommonPrograms: String;
  78. FUserStartup, FCommonStartup: String;
  79. FImageIndexes: array[Boolean] of Integer;
  80. protected
  81. procedure CreateParams(var Params: TCreateParams); override;
  82. function GetRootItem: HTREEITEM; override;
  83. function ItemChildrenNeeded(const Item: HTREEITEM): Boolean; override;
  84. function ItemHasChildren(const Item: HTREEITEM): Boolean; override;
  85. function GetItemImageIndex(const Item: HTREEITEM;
  86. const NewItem, SelectedImage: Boolean): Integer; override;
  87. public
  88. procedure SetPaths(const AUserPrograms, ACommonPrograms,
  89. AUserStartup, ACommonStartup: String);
  90. published
  91. property Anchors;
  92. property TabOrder;
  93. property TabStop default True;
  94. property Visible;
  95. property OnChange;
  96. property OnRename;
  97. end;
  98. TFolderTreeViewStyleHook = class(TScrollingStyleHook)
  99. {$IFDEF VCLSTYLES}
  100. strict private
  101. procedure UpdateBrushColor;
  102. procedure UpdateFontColor;
  103. procedure TVMSetBkColor(var Message: TMessage); message TVM_SETBKCOLOR;
  104. procedure TVMSetTextColor(var Message: TMessage); message TVM_SETTEXTCOLOR;
  105. procedure WMMouseMove(var Msg: TWMMouse); message WM_MOUSEMOVE;
  106. public
  107. constructor Create(AControl: TWinControl); override;
  108. {$ENDIF}
  109. end;
  110. procedure Register;
  111. implementation
  112. {
  113. Notes:
  114. 1. Don't call TreeView_SelectItem without calling TreeView_Expand on the
  115. item's parents first. Otherwise infinite recursion can occur:
  116. a. TreeView_SelectItem will first set the selected item. It will then try
  117. to expand the parent node, causing a TVN_ITEMEXPANDING message to be
  118. sent.
  119. b. If the TVN_ITEMEXPANDING handler calls TreeView_SortChildren, TV_SortCB
  120. will call TV_EnsureVisible if the selected item was one of the items
  121. affected by the sorting (which will always be the case).
  122. c. TV_EnsureVisible will expand parent nodes if necessary. However, since
  123. we haven't yet returned from the original TVN_ITEMEXPANDING message
  124. handler, the parent node doesn't yet have the TVIS_EXPANDED state,
  125. thus it thinks the node still needs expanding.
  126. d. Another, nested TVN_ITEMEXPANDING message is sent, bringing us back to
  127. step b.
  128. (Reproducible on Windows 95 and 2000.)
  129. The recursion can be seen if you comment out the ExpandParents call in
  130. the SelectItem method, then click "New Folder" on a folder with no
  131. children.
  132. (Note, however, that because of the ChildrenAdded check in our
  133. TVN_ITEMEXPANDING handler, it can only recurse once. That won't cause a
  134. fatal stack overflow (like it did before the ChildrenAdded check was
  135. added), but it's still wrong to allow that to happen.)
  136. }
  137. uses
  138. ShellAPI, Types, GraphUtil,
  139. {$IFDEF VCLSTYLES} Vcl.Themes, UITypes, {$ELSE} Themes, {$ENDIF}
  140. PathFunc, NewUxTheme, UnsignedFunc;
  141. const
  142. SHPPFW_NONE = $00000000;
  143. var
  144. SHPathPrepareForWriteFunc: function(hwnd: HWND; punkEnableModless: Pointer;
  145. pszPath: PChar; dwFlags: DWORD): HRESULT; stdcall;
  146. const
  147. TVM_SETEXTENDEDSTYLE = TV_FIRST + 44;
  148. TVS_EX_DOUBLEBUFFER = $0004;
  149. procedure Register;
  150. begin
  151. RegisterComponents('JR', [TFolderTreeView, TStartMenuFolderTreeView]);
  152. end;
  153. function GetSystemDir: String;
  154. var
  155. Buf: array[0..MAX_PATH-1] of Char;
  156. begin
  157. GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  158. Result := StrPas(Buf);
  159. end;
  160. var
  161. LoadedUrlmonDLL: Boolean;
  162. procedure PreloadUrlmonDLL;
  163. begin
  164. { Pre-load urlmon.dll and its dependencies.
  165. Without this, on Windows 11 25H2, urlmon.dll + 3 other DLLs are loaded and
  166. unloaded on each call to SHGetFileInfo for most folders (those with
  167. desktop.ini?). This causes a 10-second delay when opening Setup's Browse
  168. dialogs while running under the Delphi debugger. Outside the debugger,
  169. there's still a noticeable delay of about half a second, and some
  170. sluggishness when scrolling.
  171. This was first noticed in 2026-01, but it's not due to any change in our
  172. code because the half-second delay also appears in IS 6.2.2 from 2023.
  173. I suspect Windows 11 25H2 is to blame; I never saw this on 23H2. }
  174. if not LoadedUrlmonDLL then begin
  175. LoadLibrary(PChar(AddBackslash(GetSystemDir) + 'urlmon.dll'));
  176. LoadedUrlmonDLL := True;
  177. end;
  178. end;
  179. function IsListableDirectory(const FindData: TWin32FindData): Boolean;
  180. begin
  181. Result := (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0) and
  182. (FindData.dwFileAttributes and (FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM) <>
  183. (FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM)) and
  184. (StrComp(FindData.cFileName, '.') <> 0) and
  185. (StrComp(FindData.cFileName, '..') <> 0);
  186. end;
  187. function HasSubfolders(const Path: String): Boolean;
  188. var
  189. H: THandle;
  190. FindData: TWin32FindData;
  191. begin
  192. Result := False;
  193. H := FindFirstFile(PChar(AddBackslash(Path) + '*'), FindData);
  194. if H <> INVALID_HANDLE_VALUE then begin
  195. try
  196. repeat
  197. if IsListableDirectory(FindData) then begin
  198. Result := True;
  199. Break;
  200. end;
  201. until not FindNextFile(H, FindData);
  202. finally
  203. Windows.FindClose(H);
  204. end;
  205. end;
  206. end;
  207. function GetFileDisplayName(const Filename: String): String;
  208. var
  209. FileInfo: TSHFileInfo;
  210. begin
  211. if SHGetFileInfo(PChar(Filename), 0, FileInfo, SizeOf(FileInfo),
  212. SHGFI_DISPLAYNAME) <> 0 then
  213. Result := FileInfo.szDisplayName
  214. else
  215. Result := '';
  216. end;
  217. function GetFileImageIndex(const Filename: String; const OpenIcon: Boolean): Integer;
  218. const
  219. OpenFlags: array[Boolean] of UINT = (0, SHGFI_OPENICON);
  220. var
  221. FileInfo: TSHFileInfo;
  222. begin
  223. if SHGetFileInfo(PChar(Filename), 0, FileInfo, SizeOf(FileInfo),
  224. SHGFI_SYSICONINDEX or SHGFI_SMALLICON or OpenFlags[OpenIcon]) <> 0 then
  225. Result := FileInfo.iIcon
  226. else
  227. Result := 0;
  228. end;
  229. function GetDefFolderImageIndex(const OpenIcon: Boolean): Integer;
  230. const
  231. OpenFlags: array[Boolean] of UINT = (0, SHGFI_OPENICON);
  232. var
  233. FileInfo: TSHFileInfo;
  234. begin
  235. if SHGetFileInfo('c:\directory', FILE_ATTRIBUTE_DIRECTORY, FileInfo, SizeOf(FileInfo),
  236. SHGFI_USEFILEATTRIBUTES or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or OpenFlags[OpenIcon]) <> 0 then
  237. Result := FileInfo.iIcon
  238. else
  239. Result := 0;
  240. end;
  241. function IsNetworkDrive(const Drive: Char): Boolean;
  242. { Returns True if Drive is a network drive. Unlike GetLogicalDrives and
  243. GetDriveType, this will find the drive even if it's currently in an
  244. unavailable/disconnected state (i.e. showing a red "X" on the drive icon
  245. in Windows Explorer). }
  246. var
  247. LocalName: String;
  248. RemoteName: array[0..MAX_PATH-1] of Char;
  249. RemoteNameLen, ErrorCode: DWORD;
  250. begin
  251. LocalName := Drive + ':';
  252. RemoteNameLen := SizeOf(RemoteName) div SizeOf(RemoteName[0]);
  253. ErrorCode := WNetGetConnection(PChar(LocalName), RemoteName, RemoteNameLen);
  254. Result := (ErrorCode = NO_ERROR) or (ErrorCode = ERROR_CONNECTION_UNAVAIL);
  255. end;
  256. function MoveAppWindowToActiveWindowMonitor(var OldRect: TRect): Boolean;
  257. { This moves the application window (Application.Handle) to the same monitor
  258. as the active window, so that a subsequent Windows dialog will display on
  259. the same monitor. Based on code from D4+'s TApplication.MessageBox.
  260. NOTE: This function was copied from CmnFunc.pas. }
  261. type
  262. HMONITOR = type THandle;
  263. TMonitorInfo = record
  264. cbSize: DWORD;
  265. rcMonitor: TRect;
  266. rcWork: TRect;
  267. dwFlags: DWORD;
  268. end;
  269. const
  270. MONITOR_DEFAULTTONEAREST = $00000002;
  271. var
  272. ActiveWindow: HWND;
  273. Module: HMODULE;
  274. MonitorFromWindow: function(hwnd: HWND; dwFlags: DWORD): HMONITOR; stdcall;
  275. GetMonitorInfo: function(hMonitor: HMONITOR; var lpmi: TMonitorInfo): BOOL; stdcall;
  276. MBMonitor, AppMonitor: HMONITOR;
  277. Info: TMonitorInfo;
  278. begin
  279. Result := False;
  280. ActiveWindow := GetActiveWindow;
  281. if ActiveWindow = 0 then Exit;
  282. Module := GetModuleHandle(user32);
  283. MonitorFromWindow := GetProcAddress(Module, 'MonitorFromWindow');
  284. GetMonitorInfo := GetProcAddress(Module, 'GetMonitorInfoA');
  285. if Assigned(MonitorFromWindow) and Assigned(GetMonitorInfo) then begin
  286. MBMonitor := MonitorFromWindow(ActiveWindow, MONITOR_DEFAULTTONEAREST);
  287. AppMonitor := MonitorFromWindow(Application.Handle, MONITOR_DEFAULTTONEAREST);
  288. if MBMonitor <> AppMonitor then begin
  289. Info.cbSize := SizeOf(Info);
  290. if GetMonitorInfo(MBMonitor, Info) then begin
  291. GetWindowRect(Application.Handle, OldRect);
  292. SetWindowPos(Application.Handle, 0,
  293. Info.rcMonitor.Left + ((Info.rcMonitor.Right - Info.rcMonitor.Left) div 2),
  294. Info.rcMonitor.Top + ((Info.rcMonitor.Bottom - Info.rcMonitor.Top) div 2),
  295. 0, 0, SWP_NOACTIVATE or SWP_NOREDRAW or SWP_NOSIZE or SWP_NOZORDER);
  296. Result := True;
  297. end;
  298. end;
  299. end;
  300. end;
  301. procedure MoveAppWindowBack(const OldRect: TRect);
  302. { Moves the application window back to its previous position after a
  303. successful call to MoveAppWindowToActiveWindowMonitor }
  304. begin
  305. SetWindowPos(Application.Handle, 0,
  306. OldRect.Left + ((OldRect.Right - OldRect.Left) div 2),
  307. OldRect.Top + ((OldRect.Bottom - OldRect.Top) div 2),
  308. 0, 0, SWP_NOACTIVATE or SWP_NOREDRAW or SWP_NOSIZE or SWP_NOZORDER);
  309. end;
  310. function EnsurePathIsAccessible(const Path: String): Boolean;
  311. { Calls SHPathPrepareForWrite which ensures the specified path is accessible by
  312. reconnecting network drives (important) and prompting for media on removable
  313. drives (not so important for our purposes). (Note that despite its name,
  314. the function does not test for write access.) }
  315. var
  316. ActiveWindow: HWND;
  317. DidMove: Boolean;
  318. OldRect: TRect;
  319. WindowList: Pointer;
  320. begin
  321. { SHPathPrepareForWrite only exists on Windows 2000, Me, and later.
  322. Do nothing on older versions of Windows. }
  323. if @SHPathPrepareForWriteFunc = nil then begin
  324. Result := True;
  325. Exit;
  326. end;
  327. { Note: The SHPathPrepareForWrite documentation claims that "user interface
  328. windows will not be created" when hwnd is NULL, however I found that on
  329. Windows 2000, it would still display message boxes for network errors.
  330. (To reproduce: Disable your Local Area Connection and try expanding a
  331. network drive.) So to avoid bugs from having unowned message boxes floating
  332. around, go ahead and pass a proper owner window. }
  333. ActiveWindow := GetActiveWindow;
  334. DidMove := MoveAppWindowToActiveWindowMonitor(OldRect);
  335. WindowList := DisableTaskWindows(0);
  336. try
  337. Result := SUCCEEDED(SHPathPrepareForWriteFunc(Application.Handle, nil,
  338. PChar(Path), SHPPFW_NONE));
  339. finally
  340. if DidMove then
  341. MoveAppWindowBack(OldRect);
  342. EnableTaskWindows(WindowList);
  343. SetActiveWindow(ActiveWindow);
  344. end;
  345. end;
  346. { TCustomFolderTreeView }
  347. type
  348. PItemData = ^TItemData;
  349. TItemData = record
  350. Name: String;
  351. NewItem: Boolean;
  352. ChildrenAdded: Boolean;
  353. end;
  354. class constructor TCustomFolderTreeView.Create;
  355. begin
  356. TCustomStyleEngine.RegisterStyleHook(TCustomFolderTreeView, TFolderTreeViewStyleHook);
  357. end;
  358. constructor TCustomFolderTreeView.Create(AOwner: TComponent);
  359. var
  360. LogFont: TLogFont;
  361. begin
  362. inherited;
  363. ControlStyle := ControlStyle - [csCaptureMouse];
  364. Width := 121;
  365. Height := 97;
  366. ParentColor := False;
  367. TabStop := True;
  368. if SystemParametersInfo(SPI_GETICONTITLELOGFONT, SizeOf(LogFont), @LogFont, 0) then
  369. Font.Handle := CreateFontIndirect(LogFont);
  370. PreloadUrlmonDLL;
  371. end;
  372. procedure TCustomFolderTreeView.CreateParams(var Params: TCreateParams);
  373. const
  374. TVS_TRACKSELECT = $0200;
  375. TVS_SINGLEEXPAND = $0400;
  376. begin
  377. InitCommonControls;
  378. inherited;
  379. CreateSubClass(Params, WC_TREEVIEW);
  380. with Params do begin
  381. Style := Style or WS_CLIPCHILDREN or WS_CLIPSIBLINGS or TVS_LINESATROOT or
  382. TVS_HASBUTTONS or TVS_SHOWSELALWAYS or TVS_EDITLABELS;
  383. Style := Style or TVS_TRACKSELECT;
  384. ExStyle := ExStyle or WS_EX_CLIENTEDGE;
  385. WindowClass.style := WindowClass.style and not (CS_HREDRAW or CS_VREDRAW);
  386. end;
  387. end;
  388. procedure TCustomFolderTreeView.CreateWnd;
  389. var
  390. ImageList: HIMAGELIST;
  391. FileInfo: TSHFileInfo;
  392. SaveCursor: HCURSOR;
  393. begin
  394. FDestroyingHandle := False;
  395. inherited;
  396. TreeView_SetBkColor(Handle, UColorToRGB(Color));
  397. TreeView_SetTextColor(Handle, UColorToRGB(Font.Color));
  398. FDirectory := '';
  399. if csDesigning in ComponentState then
  400. Exit;
  401. { Enable the new Explorer-style look }
  402. if Assigned(SetWindowTheme) then begin
  403. var LStyle := StyleServices(Self);
  404. if not LStyle.Enabled or LStyle.IsSystemStyle then
  405. LStyle := nil;
  406. if (LStyle = nil) or ColorIsBright(LStyle.GetSystemColor(clWindow)) then
  407. SetWindowTheme(Handle, 'Explorer', nil)
  408. else
  409. SetWindowTheme(Handle, 'DarkMode_Explorer', nil);
  410. { Like Explorer, enable double buffering to avoid flicker when the mouse
  411. is moved across the items }
  412. SendMessage(Handle, TVM_SETEXTENDEDSTYLE, TVS_EX_DOUBLEBUFFER,
  413. TVS_EX_DOUBLEBUFFER);
  414. end;
  415. { Initialize the image list }
  416. ImageList := SHGetFileInfo('', 0, FileInfo, SizeOf(FileInfo),
  417. SHGFI_USEFILEATTRIBUTES or SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  418. TreeView_SetImageList(Handle, ImageList, TVSIL_NORMAL);
  419. { Add the root items }
  420. SaveCursor := SetCursor(LoadCursor(0, IDC_WAIT));
  421. try
  422. ItemChildrenNeeded(nil);
  423. finally
  424. SetCursor(SaveCursor);
  425. end;
  426. end;
  427. class destructor TCustomFolderTreeView.Destroy;
  428. begin
  429. TCustomStyleEngine.UnRegisterStyleHook(TCustomFolderTreeView, TFolderTreeViewStyleHook);
  430. end;
  431. procedure TCustomFolderTreeView.WMDestroy(var Message: TWMDestroy);
  432. begin
  433. { Work around bug in pre-v6 COMCTL32: If we have the TVS_SINGLEEXPAND style
  434. and there is a selected item when the window is destroyed, we end up
  435. getting a bunch of TVN_SINGLEEXPAND messages because it keeps moving the
  436. selection as it's destroying items, resulting in a stream of "Please
  437. insert a disk in drive X:" message boxes as the selection moves across
  438. removable drives.
  439. Currently, however, this problem isn't seen in practice because we don't
  440. use TVS_SINGLEEXPAND on pre-XP Windows. }
  441. FDestroyingHandle := True; { disables our TVN_SELCHANGED handling }
  442. SelectItem(nil);
  443. inherited;
  444. end;
  445. procedure TCustomFolderTreeView.KeyDown(var Key: Word; Shift: TShiftState);
  446. var
  447. Item: HTREEITEM;
  448. begin
  449. inherited;
  450. if (Key = VK_F2) and (Shift * [ssShift, ssAlt, ssCtrl] = []) then begin
  451. Key := 0;
  452. Item := TreeView_GetSelection(Handle);
  453. if Assigned(Item) then
  454. TreeView_EditLabel(Handle, Item);
  455. end;
  456. end;
  457. procedure TCustomFolderTreeView.CNKeyDown(var Message: TWMKeyDown);
  458. var
  459. FocusWnd: HWND;
  460. begin
  461. { On Delphi 5+, if a non-VCL control is focused, TApplication.IsKeyMsg will
  462. send the CN_KEYDOWN message to the nearest VCL control. This means that
  463. when the edit control is focused, the tree view itself gets CN_KEYDOWN
  464. messages. Don't let the VCL handle Enter and Escape; if we're on a dialog,
  465. those keys will close the window. }
  466. FocusWnd := GetFocus;
  467. if (FocusWnd <> 0) and (TreeView_GetEditControl(Handle) = FocusWnd) then
  468. if (Message.CharCode = VK_RETURN) or (Message.CharCode = VK_ESCAPE) then
  469. Exit;
  470. inherited;
  471. end;
  472. procedure TCustomFolderTreeView.WMEraseBkgnd(var Message: TWMEraseBkgnd);
  473. begin
  474. { For TVS_EX_DOUBLEBUFFER to be truly flicker-free, we must use
  475. comctl32's default WM_ERASEBKGND handling, not the VCL's (which calls
  476. FillRect). }
  477. DefaultHandler(Message);
  478. end;
  479. procedure TCustomFolderTreeView.WMCtlColorEdit(var Message: TMessage);
  480. begin
  481. { We can't let TWinControl.DefaultHandler handle this message. It tries to
  482. send a CN_CTLCOLOREDIT message to the tree view's internally-created edit
  483. control, which it won't understand because it's not a VCL control. Without
  484. this special handling, the border is painted incorrectly on Windows XP
  485. with themes enabled. }
  486. Message.Result := DefWindowProc(Handle, Message.Msg, Message.WParam,
  487. Message.LParam);
  488. end;
  489. function TCustomFolderTreeView.GetItemFullPath(Item: HTREEITEM): String;
  490. var
  491. TVItem: TTVItem;
  492. begin
  493. Result := '';
  494. while Assigned(Item) do begin
  495. TVItem.mask := TVIF_PARAM;
  496. TVItem.hItem := Item;
  497. if not TreeView_GetItem(Handle, TVItem) then begin
  498. Result := '';
  499. Exit;
  500. end;
  501. if Result = '' then
  502. Result := PItemData(TVItem.lParam).Name
  503. else
  504. Insert(AddBackslash(PItemData(TVItem.lParam).Name), Result, 1);
  505. Item := TreeView_GetParent(Handle, Item);
  506. end;
  507. end;
  508. procedure TCustomFolderTreeView.Change;
  509. var
  510. Item: HTREEITEM;
  511. begin
  512. Item := TreeView_GetSelection(Handle);
  513. if Assigned(Item) then
  514. FDirectory := GetItemFullPath(Item)
  515. else
  516. FDirectory := '';
  517. if Assigned(FOnChange) then
  518. FOnChange(Self);
  519. end;
  520. procedure TCustomFolderTreeView.CNNotify(var Message: TWMNotify);
  521. const
  522. TVN_SINGLEEXPAND = (TVN_FIRST-15);
  523. TVNRET_SKIPOLD = 1;
  524. TVNRET_SKIPNEW = 2;
  525. procedure HandleClick;
  526. var
  527. Item: HTREEITEM;
  528. HitTestInfo: TTVHitTestInfo;
  529. begin
  530. HitTestInfo.pt := ScreenToClient(SmallPointToPoint(TSmallPoint(GetMessagePos())));
  531. Item := TreeView_HitTest(Handle, HitTestInfo);
  532. if Assigned(Item) then begin
  533. if HitTestInfo.flags and TVHT_ONITEMBUTTON <> 0 then
  534. TreeView_Expand(Handle, Item, TVE_TOGGLE)
  535. else if TreeView_GetSelection(Handle) <> Item then
  536. SelectItem(Item);
  537. end;
  538. end;
  539. var
  540. Hdr: PNMTreeView;
  541. SaveCursor: HCURSOR;
  542. DispItem: PTVItem;
  543. TVItem: TTVItem;
  544. S: String;
  545. Accept: Boolean;
  546. begin
  547. inherited;
  548. case Message.NMHdr.code of
  549. TVN_DELETEITEM:
  550. begin
  551. Dispose(PItemData(PNMTreeView(Message.NMHdr).itemOld.lParam));
  552. end;
  553. TVN_ITEMEXPANDING:
  554. begin
  555. { Sanity check: Make sure this message isn't sent recursively.
  556. (See top of source code for details.) }
  557. if FItemExpanding then
  558. raise Exception.Create('Internal error: Item already expanding');
  559. FItemExpanding := True;
  560. try
  561. Hdr := PNMTreeView(Message.NMHdr);
  562. if (Hdr.action = TVE_EXPAND) and
  563. not PItemData(Hdr.itemNew.lParam).ChildrenAdded and
  564. not PItemData(Hdr.itemNew.lParam).NewItem then begin
  565. PItemData(Hdr.itemNew.lParam).ChildrenAdded := True;
  566. SaveCursor := SetCursor(LoadCursor(0, IDC_WAIT));
  567. try
  568. if ItemChildrenNeeded(Hdr.itemNew.hItem) then begin
  569. { If no subfolders were found, and there are no 'new' items
  570. underneath the parent item, remove the '+' sign }
  571. if TreeView_GetChild(Handle, Hdr.itemNew.hItem) = nil then
  572. SetItemHasChildren(Hdr.itemNew.hItem, False);
  573. end
  574. else begin
  575. { A result of False means no children were added due to a
  576. temporary error and that it should try again next time }
  577. PItemData(Hdr.itemNew.lParam).ChildrenAdded := False;
  578. { Return 1 to cancel the expansion process (although it seems
  579. to do that anyway when it sees no children were added) }
  580. Message.Result := 1;
  581. end;
  582. finally
  583. SetCursor(SaveCursor);
  584. end;
  585. end;
  586. finally
  587. FItemExpanding := False;
  588. end;
  589. end;
  590. TVN_GETDISPINFO:
  591. begin
  592. DispItem := @PTVDispInfo(Message.NMHdr).item;
  593. if DispItem.mask and TVIF_IMAGE <> 0 then begin
  594. DispItem.iImage := GetItemImageIndex(DispItem.hItem,
  595. PItemData(DispItem.lParam).NewItem, False);
  596. end;
  597. if DispItem.mask and TVIF_SELECTEDIMAGE <> 0 then begin
  598. DispItem.iSelectedImage := GetItemImageIndex(DispItem.hItem,
  599. PItemData(DispItem.lParam).NewItem, True);
  600. end;
  601. if DispItem.mask and TVIF_CHILDREN <> 0 then begin
  602. DispItem.cChildren := Ord(Assigned(TreeView_GetChild(Handle, DispItem.hItem)));
  603. if (DispItem.cChildren = 0) and not PItemData(DispItem.lParam).NewItem then
  604. DispItem.cChildren := Ord(ItemHasChildren(DispItem.hItem));
  605. end;
  606. { Store the values with the item so the callback isn't called again }
  607. DispItem.mask := DispItem.mask or TVIF_DI_SETITEM;
  608. end;
  609. TVN_SELCHANGED:
  610. begin
  611. if not FDestroyingHandle then
  612. Change;
  613. end;
  614. TVN_BEGINLABELEDIT:
  615. begin
  616. DispItem := @PTVDispInfo(Message.NMHdr).item;
  617. { Only 'new' items may be renamed }
  618. if not PItemData(DispItem.lParam).NewItem then
  619. Message.Result := 1;
  620. end;
  621. TVN_ENDLABELEDIT:
  622. begin
  623. DispItem := @PTVDispInfo(Message.NMHdr).item;
  624. { Only 'new' items may be renamed }
  625. if PItemData(DispItem.lParam).NewItem and
  626. Assigned(DispItem.pszText) then begin
  627. S := DispItem.pszText;
  628. Accept := True;
  629. if Assigned(FOnRename) then
  630. FOnRename(Self, S, Accept);
  631. if Accept then begin
  632. PItemData(DispItem.lParam).Name := S;
  633. { Instead of returning 1 to let the tree view update the text,
  634. set the text ourself. This will downconvert any Unicode
  635. characters to ANSI (if we're compiled as an ANSI app). }
  636. TVItem.mask := TVIF_TEXT;
  637. TVItem.hItem := DispItem.hItem;
  638. TVItem.pszText := PChar(S);
  639. TreeView_SetItem(Handle, TVItem);
  640. TreeView_SortChildren(Handle, TreeView_GetParent(Handle, DispItem.hItem), False);
  641. Change;
  642. end;
  643. end;
  644. end;
  645. NM_CLICK:
  646. begin
  647. { Use custom click handler to work more like Windows XP Explorer:
  648. - Items can be selected by clicking anywhere on their respective
  649. rows, except for the button.
  650. - In 'friendly tree' mode, clicking an item's icon or caption causes
  651. the item to expand, but never to collapse. }
  652. HandleClick;
  653. Message.Result := 1;
  654. end;
  655. end;
  656. end;
  657. procedure TCustomFolderTreeView.SetItemHasChildren(const Item: HTREEITEM;
  658. const AHasChildren: Boolean);
  659. var
  660. TVItem: TTVItem;
  661. begin
  662. TVItem.mask := TVIF_CHILDREN;
  663. TVItem.hItem := Item;
  664. TVItem.cChildren := Ord(AHasChildren);
  665. TreeView_SetItem(Handle, TVItem);
  666. end;
  667. procedure TCustomFolderTreeView.DeleteObsoleteNewItems(const ParentItem,
  668. ItemToKeep: HTREEITEM);
  669. { Destroys all 'new' items except for ItemToKeep and its parents. (ItemToKeep
  670. doesn't necessarily have to be a 'new' item.) Pass nil in the ParentItem
  671. parameter when calling this method. }
  672. function EqualsOrContains(const AParent: HTREEITEM; AChild: HTREEITEM): Boolean;
  673. begin
  674. Result := False;
  675. repeat
  676. if AChild = AParent then begin
  677. Result := True;
  678. Break;
  679. end;
  680. AChild := TreeView_GetParent(Handle, AChild);
  681. until AChild = nil;
  682. end;
  683. var
  684. Item, NextItem: HTREEITEM;
  685. TVItem: TTVItem;
  686. begin
  687. Item := TreeView_GetChild(Handle, ParentItem);
  688. while Assigned(Item) do begin
  689. { Determine the next item in advance since Item might get deleted }
  690. NextItem := TreeView_GetNextSibling(Handle, Item);
  691. TVItem.mask := TVIF_PARAM;
  692. TVItem.hItem := Item;
  693. if TreeView_GetItem(Handle, TVItem) then begin
  694. if PItemData(TVItem.lParam).NewItem and not EqualsOrContains(Item, ItemToKeep) then begin
  695. TreeView_DeleteItem(Handle, Item);
  696. { If there are no children left on the parent, remove its '+' sign }
  697. if TreeView_GetChild(Handle, ParentItem) = nil then
  698. SetItemHasChildren(ParentItem, False);
  699. end
  700. else
  701. DeleteObsoleteNewItems(Item, ItemToKeep);
  702. end;
  703. Item := NextItem;
  704. end;
  705. end;
  706. function TCustomFolderTreeView.InsertItem(const ParentItem: HTREEITEM;
  707. const AName, ACustomDisplayName: String; const ANewItem: Boolean): HTREEITEM;
  708. var
  709. InsertStruct: TTVInsertStruct;
  710. ItemData: PItemData;
  711. begin
  712. if ANewItem then
  713. DeleteObsoleteNewItems(nil, ParentItem);
  714. InsertStruct.hParent := ParentItem;
  715. if ANewItem then
  716. InsertStruct.hInsertAfter := TVI_SORT
  717. else
  718. InsertStruct.hInsertAfter := TVI_LAST;
  719. InsertStruct.item.mask := TVIF_TEXT or TVIF_IMAGE or
  720. TVIF_SELECTEDIMAGE or TVIF_CHILDREN or TVIF_PARAM;
  721. InsertStruct.item.hItem := nil; { not used }
  722. if ANewItem then begin
  723. InsertStruct.item.mask := InsertStruct.item.mask or TVIF_STATE;
  724. InsertStruct.item.stateMask := TVIS_CUT;
  725. InsertStruct.item.state := TVIS_CUT;
  726. end;
  727. { Note: There's no performance advantage in using a callback for the text.
  728. During a TreeView_InsertItem call, the tree view will try to read the
  729. new item's text in order to update the horizontal scroll bar range.
  730. (It doesn't wait until the item is painted.)
  731. In addition, the caller may sort newly-inserted subitems, which obviously
  732. requires reading their text. }
  733. if ACustomDisplayName = '' then
  734. InsertStruct.item.pszText := PChar(AName)
  735. else
  736. InsertStruct.item.pszText := PChar(ACustomDisplayName);
  737. InsertStruct.item.iImage := I_IMAGECALLBACK;
  738. InsertStruct.item.iSelectedImage := I_IMAGECALLBACK;
  739. if ANewItem then
  740. InsertStruct.item.cChildren := 0
  741. else begin
  742. if ParentItem = nil then
  743. InsertStruct.item.cChildren := 1
  744. else
  745. InsertStruct.item.cChildren := I_CHILDRENCALLBACK;
  746. end;
  747. InsertStruct.item.lParam := 0;
  748. New(ItemData);
  749. ItemData.Name := AName;
  750. ItemData.NewItem := ANewItem;
  751. ItemData.ChildrenAdded := False;
  752. Pointer(InsertStruct.item.lParam) := ItemData;
  753. Result := TreeView_InsertItem(Handle, InsertStruct);
  754. end;
  755. function TCustomFolderTreeView.FindItem(const ParentItem: HTREEITEM;
  756. const AName: String): HTREEITEM;
  757. var
  758. TVItem: TTVItem;
  759. begin
  760. Result := TreeView_GetChild(Handle, ParentItem);
  761. while Assigned(Result) do begin
  762. TVItem.mask := TVIF_PARAM;
  763. TVItem.hItem := Result;
  764. if TreeView_GetItem(Handle, TVItem) then
  765. if PathCompare(PItemData(TVItem.lParam).Name, AName) = 0 then
  766. Break;
  767. Result := TreeView_GetNextSibling(Handle, Result);
  768. end;
  769. end;
  770. function TCustomFolderTreeView.FindOrCreateItem(const ParentItem: HTREEITEM;
  771. const AName: String): HTREEITEM;
  772. begin
  773. Result := FindItem(ParentItem, AName);
  774. if Result = nil then begin
  775. if Assigned(ParentItem) then
  776. SetItemHasChildren(ParentItem, True);
  777. Result := InsertItem(ParentItem, AName, '', True);
  778. end;
  779. end;
  780. function TCustomFolderTreeView.GetRootItem: HTREEITEM;
  781. begin
  782. Result := nil;
  783. end;
  784. procedure TCustomFolderTreeView.SelectItem(const Item: HTREEITEM);
  785. procedure ExpandParents(Item: HTREEITEM);
  786. begin
  787. Item := TreeView_GetParent(Handle, Item);
  788. if Assigned(Item) then begin
  789. ExpandParents(Item);
  790. TreeView_Expand(Handle, Item, TVE_EXPAND);
  791. end;
  792. end;
  793. begin
  794. { Must manually expand parents prior to calling TreeView_SelectItem;
  795. see top of source code for details }
  796. if Assigned(Item) then
  797. ExpandParents(Item);
  798. TreeView_SelectItem(Handle, Item);
  799. end;
  800. function TCustomFolderTreeView.TryExpandItem(const Item: HTREEITEM): Boolean;
  801. { Tries to expand the specified item. Returns True if the item's children were
  802. initialized (if any), or False if the initialization failed due to a
  803. temporary error (i.e. ItemChildrenNeeded returned False). }
  804. var
  805. TVItem: TTVItem;
  806. begin
  807. TreeView_Expand(Handle, Item, TVE_EXPAND);
  808. TVItem.mask := TVIF_CHILDREN or TVIF_PARAM;
  809. TVItem.hItem := Item;
  810. Result := TreeView_GetItem(Handle, TVItem) and
  811. (PItemData(TVItem.lParam).ChildrenAdded or (TVItem.cChildren = 0));
  812. end;
  813. procedure TCustomFolderTreeView.ChangeDirectory(const Value: String;
  814. const CreateNewItems: Boolean);
  815. { Changes to the specified directory. Value must begin with a drive letter
  816. (e.g. "C:\directory"); relative paths and UNC paths are not allowed.
  817. If CreateNewItems is True, new items will be created if one or more elements
  818. of the path do not exist. }
  819. var
  820. PStart, PEnd: PChar;
  821. S: String;
  822. ParentItem, Item: HTREEITEM;
  823. begin
  824. SelectItem(nil);
  825. ParentItem := GetRootItem;
  826. PStart := PChar(Value);
  827. while PStart^ <> #0 do begin
  828. if Assigned(ParentItem) then
  829. if not TryExpandItem(ParentItem) then
  830. Break;
  831. { Extract a single path component }
  832. PEnd := PStart;
  833. while (PEnd^ <> #0) and not PathCharIsSlash(PEnd^) do
  834. PEnd := PathStrNextChar(PEnd);
  835. SetString(S, PStart, PEnd - PStart);
  836. { Find that component under ParentItem }
  837. if CreateNewItems and Assigned(ParentItem) then
  838. Item := FindOrCreateItem(ParentItem, S)
  839. else
  840. Item := FindItem(ParentItem, S);
  841. if Item = nil then
  842. Break;
  843. ParentItem := Item;
  844. PStart := PEnd;
  845. while PathCharIsSlash(PStart^) do
  846. Inc(PStart);
  847. end;
  848. if Assigned(ParentItem) then
  849. SelectItem(ParentItem);
  850. end;
  851. procedure TCustomFolderTreeView.SetDirectory(const Value: String);
  852. begin
  853. ChangeDirectory(Value, False);
  854. end;
  855. procedure TCustomFolderTreeView.CreateNewDirectory(const ADefaultName: String);
  856. { Creates a new node named AName underneath the selected node. Does nothing
  857. if there is no selected node. }
  858. var
  859. ParentItem, Item: HTREEITEM;
  860. I: Integer;
  861. S: String;
  862. begin
  863. ParentItem := TreeView_GetSelection(Handle);
  864. if ParentItem = nil then
  865. Exit;
  866. DeleteObsoleteNewItems(nil, ParentItem);
  867. { Expand and find a unique name }
  868. if not TryExpandItem(ParentItem) then
  869. Exit;
  870. I := 0;
  871. repeat
  872. Inc(I);
  873. if I = 1 then
  874. S := ADefaultName
  875. else
  876. S := ADefaultName + Format(' (%d)', [I]);
  877. until FindItem(ParentItem, S) = nil;
  878. SetItemHasChildren(ParentItem, True);
  879. Item := InsertItem(ParentItem, S, '', True);
  880. SelectItem(Item);
  881. if CanFocus then
  882. SetFocus;
  883. TreeView_EditLabel(Handle, Item);
  884. end;
  885. { TFolderTreeView }
  886. function TFolderTreeView.ItemChildrenNeeded(const Item: HTREEITEM): Boolean;
  887. procedure AddDrives;
  888. var
  889. Drives: DWORD;
  890. Drive: Char;
  891. begin
  892. Drives := GetLogicalDrives;
  893. for Drive := 'A' to 'Z' do begin
  894. if (Drives and 1 <> 0) or IsNetworkDrive(Drive) then
  895. InsertItem(nil, Drive + ':', GetFileDisplayName(Drive + ':\'), False);
  896. Drives := Drives shr 1;
  897. end;
  898. end;
  899. function AddSubdirectories(const ParentItem: HTREEITEM;
  900. const Path: String): Boolean;
  901. var
  902. OldErrorMode: UINT;
  903. H: THandle;
  904. FindData: TWin32FindData;
  905. S: String;
  906. begin
  907. OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  908. try
  909. { The path might be on a disconnected network drive. Ensure it's
  910. connected before attempting to enumerate subdirectories. }
  911. if Length(Path) = 3 then begin { ...only do this on the root }
  912. if not EnsurePathIsAccessible(Path) then begin
  913. Result := False;
  914. Exit;
  915. end;
  916. { Refresh the icon and text in case the drive was indeed reconnected }
  917. RefreshDriveItem(ParentItem, GetFileDisplayName(Path));
  918. end;
  919. Result := True;
  920. H := FindFirstFile(PChar(AddBackslash(Path) + '*'), FindData);
  921. if H <> INVALID_HANDLE_VALUE then begin
  922. try
  923. repeat
  924. if IsListableDirectory(FindData) then begin
  925. S := FindData.cFileName;
  926. InsertItem(ParentItem, S, GetFileDisplayName(AddBackslash(Path) + S),
  927. False);
  928. end;
  929. until not FindNextFile(H, FindData);
  930. finally
  931. Windows.FindClose(H);
  932. end;
  933. end;
  934. finally
  935. SetErrorMode(OldErrorMode);
  936. end;
  937. end;
  938. begin
  939. if Item = nil then begin
  940. AddDrives;
  941. Result := True;
  942. end
  943. else begin
  944. Result := AddSubdirectories(Item, GetItemFullPath(Item));
  945. if Result then begin
  946. { When a text callback is used, sorting after all items are inserted is
  947. exponentially faster than using hInsertAfter=TVI_SORT }
  948. TreeView_SortChildren(Handle, Item, False);
  949. end;
  950. end;
  951. end;
  952. function TFolderTreeView.GetItemFullPath(Item: HTREEITEM): String;
  953. begin
  954. Result := inherited GetItemFullPath(Item);
  955. if (Length(Result) = 2) and (Result[2] = ':') then
  956. Result := Result + '\';
  957. end;
  958. function TFolderTreeView.GetItemImageIndex(const Item: HTREEITEM;
  959. const NewItem, SelectedImage: Boolean): Integer;
  960. begin
  961. if NewItem then
  962. Result := GetDefFolderImageIndex(SelectedImage)
  963. else
  964. Result := GetFileImageIndex(GetItemFullPath(Item), SelectedImage);
  965. end;
  966. function TFolderTreeView.ItemHasChildren(const Item: HTREEITEM): Boolean;
  967. var
  968. Path: String;
  969. OldErrorMode: UINT;
  970. begin
  971. Path := GetItemFullPath(Item);
  972. OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  973. try
  974. Result := (GetDriveType(PChar(AddBackslash(PathExtractDrive(Path)))) = DRIVE_REMOTE) or
  975. HasSubfolders(Path);
  976. finally
  977. SetErrorMode(OldErrorMode);
  978. end;
  979. end;
  980. procedure TFolderTreeView.RefreshDriveItem(const Item: HTREEITEM;
  981. const ANewDisplayName: String);
  982. var
  983. TVItem: TTVItem;
  984. begin
  985. TVItem.mask := TVIF_IMAGE or TVIF_SELECTEDIMAGE;
  986. TVItem.hItem := Item;
  987. TVItem.iImage := I_IMAGECALLBACK;
  988. TVItem.iSelectedImage := I_IMAGECALLBACK;
  989. if ANewDisplayName <> '' then begin
  990. TVItem.mask := TVItem.mask or TVIF_TEXT;
  991. TVItem.pszText := PChar(ANewDisplayName);
  992. end;
  993. TreeView_SetItem(Handle, TVItem);
  994. end;
  995. { TStartMenuFolderTreeView }
  996. procedure TStartMenuFolderTreeView.CreateParams(var Params: TCreateParams);
  997. begin
  998. inherited;
  999. Params.Style := Params.Style and not TVS_LINESATROOT;
  1000. end;
  1001. function TStartMenuFolderTreeView.GetItemImageIndex(const Item: HTREEITEM;
  1002. const NewItem, SelectedImage: Boolean): Integer;
  1003. begin
  1004. Result := FImageIndexes[SelectedImage];
  1005. end;
  1006. function TStartMenuFolderTreeView.GetRootItem: HTREEITEM;
  1007. begin
  1008. { The top item ('Programs') is considered the root }
  1009. Result := TreeView_GetRoot(Handle);
  1010. end;
  1011. function TStartMenuFolderTreeView.ItemChildrenNeeded(const Item: HTREEITEM): Boolean;
  1012. procedure AddSubfolders(const ParentItem: HTREEITEM; const Path, StartupPath: String);
  1013. var
  1014. StartupName: String;
  1015. OldErrorMode: UINT;
  1016. H: THandle;
  1017. FindData: TWin32FindData;
  1018. S: String;
  1019. begin
  1020. { Determine the name of the Startup folder so that we can hide it from the
  1021. list }
  1022. if StartupPath <> '' then
  1023. if PathCompare(AddBackslash(Path), PathExtractPath(StartupPath)) = 0 then
  1024. StartupName := PathExtractName(StartupPath);
  1025. OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  1026. try
  1027. H := FindFirstFile(PChar(AddBackslash(Path) + '*'), FindData);
  1028. if H <> INVALID_HANDLE_VALUE then begin
  1029. try
  1030. repeat
  1031. if IsListableDirectory(FindData) then begin
  1032. S := FindData.cFileName;
  1033. if PathCompare(S, StartupName) <> 0 then
  1034. if FindItem(ParentItem, S) = nil then
  1035. InsertItem(ParentItem, S, GetFileDisplayName(AddBackslash(Path) + S), False);
  1036. end;
  1037. until not FindNextFile(H, FindData);
  1038. finally
  1039. Windows.FindClose(H);
  1040. end;
  1041. end;
  1042. finally
  1043. SetErrorMode(OldErrorMode);
  1044. end;
  1045. end;
  1046. var
  1047. Root, S: String;
  1048. NewItem: HTREEITEM;
  1049. Path: String;
  1050. begin
  1051. Result := True;
  1052. if Item = nil then begin
  1053. Root := FUserPrograms;
  1054. if Root = '' then begin
  1055. { User programs folder doesn't exist for some reason? }
  1056. Root := FCommonPrograms;
  1057. if Root = '' then
  1058. Exit;
  1059. end;
  1060. FImageIndexes[False] := GetFileImageIndex(Root, False);
  1061. FImageIndexes[True] := FImageIndexes[False];
  1062. S := GetFileDisplayName(Root);
  1063. if S = '' then
  1064. S := PathExtractName(Root);
  1065. NewItem := InsertItem(nil, '', S, False);
  1066. TreeView_Expand(Handle, NewItem, TVE_EXPAND);
  1067. end
  1068. else begin
  1069. Path := GetItemFullPath(Item);
  1070. if FCommonPrograms <> '' then
  1071. AddSubfolders(Item, AddBackslash(FCommonPrograms) + Path, FCommonStartup);
  1072. if FUserPrograms <> '' then
  1073. AddSubfolders(Item, AddBackslash(FUserPrograms) + Path, FUserStartup);
  1074. TreeView_SortChildren(Handle, Item, False);
  1075. end;
  1076. end;
  1077. function TStartMenuFolderTreeView.ItemHasChildren(const Item: HTREEITEM): Boolean;
  1078. var
  1079. Path: String;
  1080. begin
  1081. Path := GetItemFullPath(Item);
  1082. if (FCommonPrograms <> '') and HasSubfolders(AddBackslash(FCommonPrograms) + Path) then
  1083. Result := True
  1084. else if (FUserPrograms <> '') and HasSubfolders(AddBackslash(FUserPrograms) + Path) then
  1085. Result := True
  1086. else
  1087. Result := False;
  1088. end;
  1089. procedure TStartMenuFolderTreeView.SetPaths(const AUserPrograms, ACommonPrograms,
  1090. AUserStartup, ACommonStartup: String);
  1091. begin
  1092. FUserPrograms := AUserPrograms;
  1093. FCommonPrograms := ACommonPrograms;
  1094. FUserStartup := AUserStartup;
  1095. FCommonStartup := ACommonStartup;
  1096. RecreateWnd;
  1097. end;
  1098. {$IFDEF VCLSTYLES}
  1099. { TFolderTreeViewStyleHook - same as Vcl.ComCtrls' TTreeViewStyleHook
  1100. except that it accesses the Control property as a TCustomFolderTreeView instead
  1101. of a TCustomTreeView }
  1102. type
  1103. TWinControlClass = class(TWinControl);
  1104. constructor TFolderTreeViewStyleHook.Create(AControl: TWinControl);
  1105. begin
  1106. inherited;
  1107. OverrideEraseBkgnd := True;
  1108. UpdateFontColor;
  1109. UpdateBrushColor;
  1110. end;
  1111. procedure TFolderTreeViewStyleHook.TVMSetBkColor(var Message: TMessage);
  1112. begin
  1113. UpdateBrushColor;
  1114. Message.LParam := LPARAM(ColorToRGB(Brush.Color));
  1115. Handled := False;
  1116. end;
  1117. procedure TFolderTreeViewStyleHook.TVMSetTextColor(var Message: TMessage);
  1118. begin
  1119. UpdateFontColor;
  1120. Message.LParam := LPARAM(ColorToRGB(FontColor));
  1121. Handled := False;
  1122. end;
  1123. procedure TFolderTreeViewStyleHook.UpdateBrushColor;
  1124. begin
  1125. if seClient in Control.StyleElements then
  1126. Brush.Color := StyleServices.GetStyleColor(scTreeView)
  1127. else
  1128. Brush.Color := TWinControlClass(Control).Color;
  1129. end;
  1130. procedure TFolderTreeViewStyleHook.UpdateFontColor;
  1131. var
  1132. LColor : TColor;
  1133. begin
  1134. if (seFont in Control.StyleElements) then
  1135. begin
  1136. if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemNormal), ecTextColor, LColor) or
  1137. (LColor = clNone) then
  1138. LColor := StyleServices.GetSystemColor(clWindowText);
  1139. FontColor := LColor;
  1140. end else
  1141. FontColor := TWinControlClass(Control).Font.Color;
  1142. end;
  1143. procedure TFolderTreeViewStyleHook.WMMouseMove(var Msg: TWMMouse);
  1144. var
  1145. SF: TScrollInfo;
  1146. begin
  1147. if VertSliderState = tsThumbBtnVertPressed then
  1148. begin
  1149. SF.fMask := SIF_ALL;
  1150. SF.cbSize := SizeOf(SF);
  1151. GetScrollInfo(Handle, SB_VERT, SF);
  1152. ScrollPos := ScrollPos + (SF.nMax - SF.nMin) * ((Mouse.CursorPos.Y - PrevScrollPos) / VertTrackRect.Height);
  1153. if ScrollPos < SF.nMin then
  1154. ScrollPos := SF.nMin;
  1155. if ScrollPos > SF.nMax then
  1156. ScrollPos := SF.nMax;
  1157. PrevScrollPos := Mouse.CursorPos.Y;
  1158. const RoundedScrollPos = Integer(Round(ScrollPos));
  1159. if Control is TCustomFolderTreeView then
  1160. begin
  1161. PostMessage(Handle, WM_VSCROLL, Cardinal(SmallPoint(SB_THUMBTRACK, RoundedScrollPos)), 0);
  1162. SF.nPos := RoundedScrollPos;
  1163. SF.nTrackPos := RoundedScrollPos;
  1164. SetScrollInfo(Handle, SB_VERT, SF, True);
  1165. end
  1166. else
  1167. PostMessage(Handle, WM_VSCROLL, Cardinal(SmallPoint(SB_THUMBPOSITION, RoundedScrollPos)), 0);
  1168. PaintScroll;
  1169. Handled := True;
  1170. Exit;
  1171. end;
  1172. if HorzSliderState = tsThumbBtnHorzPressed then
  1173. begin
  1174. SF.fMask := SIF_ALL;
  1175. SF.cbSize := SizeOf(SF);
  1176. GetScrollInfo(Handle, SB_HORZ, SF);
  1177. ScrollPos := ScrollPos + (SF.nMax - SF.nMin) * ((Mouse.CursorPos.X - PrevScrollPos) / HorzTrackRect.Width);
  1178. if ScrollPos < SF.nMin then
  1179. ScrollPos := SF.nMin;
  1180. if ScrollPos > SF.nMax then
  1181. ScrollPos := SF.nMax;
  1182. PrevScrollPos := Mouse.CursorPos.X;
  1183. const RoundedScrollPos = Integer(Round(ScrollPos));
  1184. if Control is TCustomFolderTreeView then
  1185. begin
  1186. PostMessage(Handle, WM_HSCROLL, Cardinal(SmallPoint(SB_THUMBTRACK, RoundedScrollPos)), 0);
  1187. SF.nPos := RoundedScrollPos;
  1188. SF.nTrackPos := RoundedScrollPos;
  1189. SetScrollInfo(Handle, SB_HORZ, SF, True);
  1190. end
  1191. else
  1192. PostMessage(Handle, WM_HSCROLL, Cardinal(SmallPoint(SB_THUMBPOSITION, RoundedScrollPos)), 0);
  1193. PaintScroll;
  1194. Handled := True;
  1195. Exit;
  1196. end;
  1197. if (HorzSliderState <> tsThumbBtnHorzPressed) and (HorzSliderState = tsThumbBtnHorzHot) then
  1198. begin
  1199. HorzSliderState := tsThumbBtnHorzNormal;
  1200. PaintScroll;
  1201. end;
  1202. if (VertSliderState <> tsThumbBtnVertPressed) and (VertSliderState = tsThumbBtnVertHot) then
  1203. begin
  1204. VertSliderState := tsThumbBtnVertNormal;
  1205. PaintScroll;
  1206. end;
  1207. if (HorzUpState <> tsArrowBtnLeftPressed) and (HorzUpState = tsArrowBtnLeftHot) then
  1208. begin
  1209. HorzUpState := tsArrowBtnLeftNormal;
  1210. PaintScroll;
  1211. end;
  1212. if (HorzDownState <> tsArrowBtnRightPressed) and (HorzDownState =tsArrowBtnRightHot) then
  1213. begin
  1214. HorzDownState := tsArrowBtnRightNormal;
  1215. PaintScroll;
  1216. end;
  1217. if (VertUpState <> tsArrowBtnUpPressed) and (VertUpState = tsArrowBtnUpHot) then
  1218. begin
  1219. VertUpState := tsArrowBtnUpNormal;
  1220. PaintScroll;
  1221. end;
  1222. if (VertDownState <> tsArrowBtnDownPressed) and (VertDownState = tsArrowBtnDownHot) then
  1223. begin
  1224. VertDownState := tsArrowBtnDownNormal;
  1225. PaintScroll;
  1226. end;
  1227. CallDefaultProc(TMessage(Msg));
  1228. if LeftButtonDown then
  1229. PaintScroll;
  1230. Handled := True;
  1231. end;
  1232. {$ENDIF}
  1233. initialization
  1234. InitThemeLibrary;
  1235. SHPathPrepareForWriteFunc := GetProcAddress(LoadLibrary(PChar(AddBackslash(GetSystemDir) + shell32)),
  1236. 'SHPathPrepareForWriteW');
  1237. end.