FolderTreeView.pas 39 KB

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