FolderTreeView.pas 38 KB

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