2
0

FolderTreeView.pas 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149
  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, Themes;
  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 style colors }
  361. var LStyle := StyleServices;
  362. if not LStyle.Enabled or LStyle.IsSystemStyle then
  363. LStyle := nil;
  364. if (LStyle <> nil) and (seClient in StyleElements) then begin
  365. TreeView_SetBkColor(Handle, ColorToRGB(LStyle.GetStyleColor(scTreeview)));
  366. TreeView_SetTextColor(Handle, ColorToRGB(LStyle.GetStyleFontColor(sfTreeItemTextNormal)));
  367. end;
  368. { Initialize the image list }
  369. ImageList := SHGetFileInfo('', 0, FileInfo, SizeOf(FileInfo),
  370. SHGFI_USEFILEATTRIBUTES or SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  371. TreeView_SetImageList(Handle, ImageList, TVSIL_NORMAL);
  372. { Add the root items }
  373. SaveCursor := SetCursor(LoadCursor(0, IDC_WAIT));
  374. try
  375. ItemChildrenNeeded(nil);
  376. finally
  377. SetCursor(SaveCursor);
  378. end;
  379. end;
  380. procedure TCustomFolderTreeView.WMDestroy(var Message: TWMDestroy);
  381. begin
  382. { Work around bug in pre-v6 COMCTL32: If we have the TVS_SINGLEEXPAND style
  383. and there is a selected item when the window is destroyed, we end up
  384. getting a bunch of TVN_SINGLEEXPAND messages because it keeps moving the
  385. selection as it's destroying items, resulting in a stream of "Please
  386. insert a disk in drive X:" message boxes as the selection moves across
  387. removable drives.
  388. Currently, however, this problem isn't seen in practice because we don't
  389. use TVS_SINGLEEXPAND on pre-XP Windows. }
  390. FDestroyingHandle := True; { disables our TVN_SELCHANGED handling }
  391. SelectItem(nil);
  392. inherited;
  393. end;
  394. procedure TCustomFolderTreeView.KeyDown(var Key: Word; Shift: TShiftState);
  395. var
  396. Item: HTREEITEM;
  397. begin
  398. inherited;
  399. if (Key = VK_F2) and (Shift * [ssShift, ssAlt, ssCtrl] = []) then begin
  400. Key := 0;
  401. Item := TreeView_GetSelection(Handle);
  402. if Assigned(Item) then
  403. TreeView_EditLabel(Handle, Item);
  404. end;
  405. end;
  406. procedure TCustomFolderTreeView.CNKeyDown(var Message: TWMKeyDown);
  407. var
  408. FocusWnd: HWND;
  409. begin
  410. { On Delphi 5+, if a non-VCL control is focused, TApplication.IsKeyMsg will
  411. send the CN_KEYDOWN message to the nearest VCL control. This means that
  412. when the edit control is focused, the tree view itself gets CN_KEYDOWN
  413. messages. Don't let the VCL handle Enter and Escape; if we're on a dialog,
  414. those keys will close the window. }
  415. FocusWnd := GetFocus;
  416. if (FocusWnd <> 0) and (TreeView_GetEditControl(Handle) = FocusWnd) then
  417. if (Message.CharCode = VK_RETURN) or (Message.CharCode = VK_ESCAPE) then
  418. Exit;
  419. inherited;
  420. end;
  421. procedure TCustomFolderTreeView.WMEraseBkgnd(var Message: TWMEraseBkgnd);
  422. begin
  423. { For TVS_EX_DOUBLEBUFFER to be truly flicker-free, we must use
  424. comctl32's default WM_ERASEBKGND handling, not the VCL's (which calls
  425. FillRect). }
  426. DefaultHandler(Message);
  427. end;
  428. procedure TCustomFolderTreeView.WMCtlColorEdit(var Message: TMessage);
  429. begin
  430. { We can't let TWinControl.DefaultHandler handle this message. It tries to
  431. send a CN_CTLCOLOREDIT message to the tree view's internally-created edit
  432. control, which it won't understand because it's not a VCL control. Without
  433. this special handling, the border is painted incorrectly on Windows XP
  434. with themes enabled. }
  435. Message.Result := DefWindowProc(Handle, Message.Msg, Message.WParam,
  436. Message.LParam);
  437. end;
  438. function TCustomFolderTreeView.GetItemFullPath(Item: HTREEITEM): String;
  439. var
  440. TVItem: TTVItem;
  441. begin
  442. Result := '';
  443. while Assigned(Item) do begin
  444. TVItem.mask := TVIF_PARAM;
  445. TVItem.hItem := Item;
  446. if not TreeView_GetItem(Handle, TVItem) then begin
  447. Result := '';
  448. Exit;
  449. end;
  450. if Result = '' then
  451. Result := PItemData(TVItem.lParam).Name
  452. else
  453. Insert(AddBackslash(PItemData(TVItem.lParam).Name), Result, 1);
  454. Item := TreeView_GetParent(Handle, Item);
  455. end;
  456. end;
  457. procedure TCustomFolderTreeView.Change;
  458. var
  459. Item: HTREEITEM;
  460. begin
  461. Item := TreeView_GetSelection(Handle);
  462. if Assigned(Item) then
  463. FDirectory := GetItemFullPath(Item)
  464. else
  465. FDirectory := '';
  466. if Assigned(FOnChange) then
  467. FOnChange(Self);
  468. end;
  469. procedure TCustomFolderTreeView.CNNotify(var Message: TWMNotify);
  470. const
  471. TVN_SINGLEEXPAND = (TVN_FIRST-15);
  472. TVNRET_SKIPOLD = 1;
  473. TVNRET_SKIPNEW = 2;
  474. procedure HandleClick;
  475. var
  476. Item: HTREEITEM;
  477. HitTestInfo: TTVHitTestInfo;
  478. begin
  479. HitTestInfo.pt := ScreenToClient(SmallPointToPoint(TSmallPoint(GetMessagePos())));
  480. Item := TreeView_HitTest(Handle, HitTestInfo);
  481. if Assigned(Item) then begin
  482. if HitTestInfo.flags and TVHT_ONITEMBUTTON <> 0 then
  483. TreeView_Expand(Handle, Item, TVE_TOGGLE)
  484. else if TreeView_GetSelection(Handle) <> Item then
  485. SelectItem(Item);
  486. end;
  487. end;
  488. var
  489. Hdr: PNMTreeView;
  490. SaveCursor: HCURSOR;
  491. DispItem: PTVItem;
  492. TVItem: TTVItem;
  493. S: String;
  494. Accept: Boolean;
  495. begin
  496. inherited;
  497. case Message.NMHdr.code of
  498. TVN_DELETEITEM:
  499. begin
  500. Dispose(PItemData(PNMTreeView(Message.NMHdr).itemOld.lParam));
  501. end;
  502. TVN_ITEMEXPANDING:
  503. begin
  504. { Sanity check: Make sure this message isn't sent recursively.
  505. (See top of source code for details.) }
  506. if FItemExpanding then
  507. raise Exception.Create('Internal error: Item already expanding');
  508. FItemExpanding := True;
  509. try
  510. Hdr := PNMTreeView(Message.NMHdr);
  511. if (Hdr.action = TVE_EXPAND) and
  512. not PItemData(Hdr.itemNew.lParam).ChildrenAdded and
  513. not PItemData(Hdr.itemNew.lParam).NewItem then begin
  514. PItemData(Hdr.itemNew.lParam).ChildrenAdded := True;
  515. SaveCursor := SetCursor(LoadCursor(0, IDC_WAIT));
  516. try
  517. if ItemChildrenNeeded(Hdr.itemNew.hItem) then begin
  518. { If no subfolders were found, and there are no 'new' items
  519. underneath the parent item, remove the '+' sign }
  520. if TreeView_GetChild(Handle, Hdr.itemNew.hItem) = nil then
  521. SetItemHasChildren(Hdr.itemNew.hItem, False);
  522. end
  523. else begin
  524. { A result of False means no children were added due to a
  525. temporary error and that it should try again next time }
  526. PItemData(Hdr.itemNew.lParam).ChildrenAdded := False;
  527. { Return 1 to cancel the expansion process (although it seems
  528. to do that anyway when it sees no children were added) }
  529. Message.Result := 1;
  530. end;
  531. finally
  532. SetCursor(SaveCursor);
  533. end;
  534. end;
  535. finally
  536. FItemExpanding := False;
  537. end;
  538. end;
  539. TVN_GETDISPINFO:
  540. begin
  541. DispItem := @PTVDispInfo(Message.NMHdr).item;
  542. if DispItem.mask and TVIF_IMAGE <> 0 then begin
  543. DispItem.iImage := GetItemImageIndex(DispItem.hItem,
  544. PItemData(DispItem.lParam).NewItem, False);
  545. end;
  546. if DispItem.mask and TVIF_SELECTEDIMAGE <> 0 then begin
  547. DispItem.iSelectedImage := GetItemImageIndex(DispItem.hItem,
  548. PItemData(DispItem.lParam).NewItem, True);
  549. end;
  550. if DispItem.mask and TVIF_CHILDREN <> 0 then begin
  551. DispItem.cChildren := Ord(Assigned(TreeView_GetChild(Handle, DispItem.hItem)));
  552. if (DispItem.cChildren = 0) and not PItemData(DispItem.lParam).NewItem then
  553. DispItem.cChildren := Ord(ItemHasChildren(DispItem.hItem));
  554. end;
  555. { Store the values with the item so the callback isn't called again }
  556. DispItem.mask := DispItem.mask or TVIF_DI_SETITEM;
  557. end;
  558. TVN_SELCHANGED:
  559. begin
  560. if not FDestroyingHandle then
  561. Change;
  562. end;
  563. TVN_BEGINLABELEDIT:
  564. begin
  565. DispItem := @PTVDispInfo(Message.NMHdr).item;
  566. { Only 'new' items may be renamed }
  567. if not PItemData(DispItem.lParam).NewItem then
  568. Message.Result := 1;
  569. end;
  570. TVN_ENDLABELEDIT:
  571. begin
  572. DispItem := @PTVDispInfo(Message.NMHdr).item;
  573. { Only 'new' items may be renamed }
  574. if PItemData(DispItem.lParam).NewItem and
  575. Assigned(DispItem.pszText) then begin
  576. S := DispItem.pszText;
  577. Accept := True;
  578. if Assigned(FOnRename) then
  579. FOnRename(Self, S, Accept);
  580. if Accept then begin
  581. PItemData(DispItem.lParam).Name := S;
  582. { Instead of returning 1 to let the tree view update the text,
  583. set the text ourself. This will downconvert any Unicode
  584. characters to ANSI (if we're compiled as an ANSI app). }
  585. TVItem.mask := TVIF_TEXT;
  586. TVItem.hItem := DispItem.hItem;
  587. TVItem.pszText := PChar(S);
  588. TreeView_SetItem(Handle, TVItem);
  589. TreeView_SortChildren(Handle, TreeView_GetParent(Handle, DispItem.hItem), False);
  590. Change;
  591. end;
  592. end;
  593. end;
  594. NM_CLICK:
  595. begin
  596. { Use custom click handler to work more like Windows XP Explorer:
  597. - Items can be selected by clicking anywhere on their respective
  598. rows, except for the button.
  599. - In 'friendly tree' mode, clicking an item's icon or caption causes
  600. the item to expand, but never to collapse. }
  601. HandleClick;
  602. Message.Result := 1;
  603. end;
  604. end;
  605. end;
  606. procedure TCustomFolderTreeView.SetItemHasChildren(const Item: HTREEITEM;
  607. const AHasChildren: Boolean);
  608. var
  609. TVItem: TTVItem;
  610. begin
  611. TVItem.mask := TVIF_CHILDREN;
  612. TVItem.hItem := Item;
  613. TVItem.cChildren := Ord(AHasChildren);
  614. TreeView_SetItem(Handle, TVItem);
  615. end;
  616. procedure TCustomFolderTreeView.DeleteObsoleteNewItems(const ParentItem,
  617. ItemToKeep: HTREEITEM);
  618. { Destroys all 'new' items except for ItemToKeep and its parents. (ItemToKeep
  619. doesn't necessarily have to be a 'new' item.) Pass nil in the ParentItem
  620. parameter when calling this method. }
  621. function EqualsOrContains(const AParent: HTREEITEM; AChild: HTREEITEM): Boolean;
  622. begin
  623. Result := False;
  624. repeat
  625. if AChild = AParent then begin
  626. Result := True;
  627. Break;
  628. end;
  629. AChild := TreeView_GetParent(Handle, AChild);
  630. until AChild = nil;
  631. end;
  632. var
  633. Item, NextItem: HTREEITEM;
  634. TVItem: TTVItem;
  635. begin
  636. Item := TreeView_GetChild(Handle, ParentItem);
  637. while Assigned(Item) do begin
  638. { Determine the next item in advance since Item might get deleted }
  639. NextItem := TreeView_GetNextSibling(Handle, Item);
  640. TVItem.mask := TVIF_PARAM;
  641. TVItem.hItem := Item;
  642. if TreeView_GetItem(Handle, TVItem) then begin
  643. if PItemData(TVItem.lParam).NewItem and not EqualsOrContains(Item, ItemToKeep) then begin
  644. TreeView_DeleteItem(Handle, Item);
  645. { If there are no children left on the parent, remove its '+' sign }
  646. if TreeView_GetChild(Handle, ParentItem) = nil then
  647. SetItemHasChildren(ParentItem, False);
  648. end
  649. else
  650. DeleteObsoleteNewItems(Item, ItemToKeep);
  651. end;
  652. Item := NextItem;
  653. end;
  654. end;
  655. function TCustomFolderTreeView.InsertItem(const ParentItem: HTREEITEM;
  656. const AName, ACustomDisplayName: String; const ANewItem: Boolean): HTREEITEM;
  657. var
  658. InsertStruct: TTVInsertStruct;
  659. ItemData: PItemData;
  660. begin
  661. if ANewItem then
  662. DeleteObsoleteNewItems(nil, ParentItem);
  663. InsertStruct.hParent := ParentItem;
  664. if ANewItem then
  665. InsertStruct.hInsertAfter := TVI_SORT
  666. else
  667. InsertStruct.hInsertAfter := TVI_LAST;
  668. InsertStruct.item.mask := TVIF_TEXT or TVIF_IMAGE or
  669. TVIF_SELECTEDIMAGE or TVIF_CHILDREN or TVIF_PARAM;
  670. InsertStruct.item.hItem := nil; { not used }
  671. if ANewItem then begin
  672. InsertStruct.item.mask := InsertStruct.item.mask or TVIF_STATE;
  673. InsertStruct.item.stateMask := TVIS_CUT;
  674. InsertStruct.item.state := TVIS_CUT;
  675. end;
  676. { Note: There's no performance advantage in using a callback for the text.
  677. During a TreeView_InsertItem call, the tree view will try to read the
  678. new item's text in order to update the horizontal scroll bar range.
  679. (It doesn't wait until the item is painted.)
  680. In addition, the caller may sort newly-inserted subitems, which obviously
  681. requires reading their text. }
  682. if ACustomDisplayName = '' then
  683. InsertStruct.item.pszText := PChar(AName)
  684. else
  685. InsertStruct.item.pszText := PChar(ACustomDisplayName);
  686. InsertStruct.item.iImage := I_IMAGECALLBACK;
  687. InsertStruct.item.iSelectedImage := I_IMAGECALLBACK;
  688. if ANewItem then
  689. InsertStruct.item.cChildren := 0
  690. else begin
  691. if ParentItem = nil then
  692. InsertStruct.item.cChildren := 1
  693. else
  694. InsertStruct.item.cChildren := I_CHILDRENCALLBACK;
  695. end;
  696. InsertStruct.item.lParam := 0;
  697. New(ItemData);
  698. ItemData.Name := AName;
  699. ItemData.NewItem := ANewItem;
  700. ItemData.ChildrenAdded := False;
  701. Pointer(InsertStruct.item.lParam) := ItemData;
  702. Result := TreeView_InsertItem(Handle, InsertStruct);
  703. end;
  704. function TCustomFolderTreeView.FindItem(const ParentItem: HTREEITEM;
  705. const AName: String): HTREEITEM;
  706. var
  707. TVItem: TTVItem;
  708. begin
  709. Result := TreeView_GetChild(Handle, ParentItem);
  710. while Assigned(Result) do begin
  711. TVItem.mask := TVIF_PARAM;
  712. TVItem.hItem := Result;
  713. if TreeView_GetItem(Handle, TVItem) then
  714. if PathCompare(PItemData(TVItem.lParam).Name, AName) = 0 then
  715. Break;
  716. Result := TreeView_GetNextSibling(Handle, Result);
  717. end;
  718. end;
  719. function TCustomFolderTreeView.FindOrCreateItem(const ParentItem: HTREEITEM;
  720. const AName: String): HTREEITEM;
  721. begin
  722. Result := FindItem(ParentItem, AName);
  723. if Result = nil then begin
  724. if Assigned(ParentItem) then
  725. SetItemHasChildren(ParentItem, True);
  726. Result := InsertItem(ParentItem, AName, '', True);
  727. end;
  728. end;
  729. function TCustomFolderTreeView.GetRootItem: HTREEITEM;
  730. begin
  731. Result := nil;
  732. end;
  733. procedure TCustomFolderTreeView.SelectItem(const Item: HTREEITEM);
  734. procedure ExpandParents(Item: HTREEITEM);
  735. begin
  736. Item := TreeView_GetParent(Handle, Item);
  737. if Assigned(Item) then begin
  738. ExpandParents(Item);
  739. TreeView_Expand(Handle, Item, TVE_EXPAND);
  740. end;
  741. end;
  742. begin
  743. { Must manually expand parents prior to calling TreeView_SelectItem;
  744. see top of source code for details }
  745. if Assigned(Item) then
  746. ExpandParents(Item);
  747. TreeView_SelectItem(Handle, Item);
  748. end;
  749. function TCustomFolderTreeView.TryExpandItem(const Item: HTREEITEM): Boolean;
  750. { Tries to expand the specified item. Returns True if the item's children were
  751. initialized (if any), or False if the initialization failed due to a
  752. temporary error (i.e. ItemChildrenNeeded returned False). }
  753. var
  754. TVItem: TTVItem;
  755. begin
  756. TreeView_Expand(Handle, Item, TVE_EXPAND);
  757. TVItem.mask := TVIF_CHILDREN or TVIF_PARAM;
  758. TVItem.hItem := Item;
  759. Result := TreeView_GetItem(Handle, TVItem) and
  760. (PItemData(TVItem.lParam).ChildrenAdded or (TVItem.cChildren = 0));
  761. end;
  762. procedure TCustomFolderTreeView.ChangeDirectory(const Value: String;
  763. const CreateNewItems: Boolean);
  764. { Changes to the specified directory. Value must begin with a drive letter
  765. (e.g. "C:\directory"); relative paths and UNC paths are not allowed.
  766. If CreateNewItems is True, new items will be created if one or more elements
  767. of the path do not exist. }
  768. var
  769. PStart, PEnd: PChar;
  770. S: String;
  771. ParentItem, Item: HTREEITEM;
  772. begin
  773. SelectItem(nil);
  774. ParentItem := GetRootItem;
  775. PStart := PChar(Value);
  776. while PStart^ <> #0 do begin
  777. if Assigned(ParentItem) then
  778. if not TryExpandItem(ParentItem) then
  779. Break;
  780. { Extract a single path component }
  781. PEnd := PStart;
  782. while (PEnd^ <> #0) and not PathCharIsSlash(PEnd^) do
  783. PEnd := PathStrNextChar(PEnd);
  784. SetString(S, PStart, PEnd - PStart);
  785. { Find that component under ParentItem }
  786. if CreateNewItems and Assigned(ParentItem) then
  787. Item := FindOrCreateItem(ParentItem, S)
  788. else
  789. Item := FindItem(ParentItem, S);
  790. if Item = nil then
  791. Break;
  792. ParentItem := Item;
  793. PStart := PEnd;
  794. while PathCharIsSlash(PStart^) do
  795. Inc(PStart);
  796. end;
  797. if Assigned(ParentItem) then
  798. SelectItem(ParentItem);
  799. end;
  800. procedure TCustomFolderTreeView.SetDirectory(const Value: String);
  801. begin
  802. ChangeDirectory(Value, False);
  803. end;
  804. procedure TCustomFolderTreeView.CreateNewDirectory(const ADefaultName: String);
  805. { Creates a new node named AName underneath the selected node. Does nothing
  806. if there is no selected node. }
  807. var
  808. ParentItem, Item: HTREEITEM;
  809. I: Integer;
  810. S: String;
  811. begin
  812. ParentItem := TreeView_GetSelection(Handle);
  813. if ParentItem = nil then
  814. Exit;
  815. DeleteObsoleteNewItems(nil, ParentItem);
  816. { Expand and find a unique name }
  817. if not TryExpandItem(ParentItem) then
  818. Exit;
  819. I := 0;
  820. repeat
  821. Inc(I);
  822. if I = 1 then
  823. S := ADefaultName
  824. else
  825. S := ADefaultName + Format(' (%d)', [I]);
  826. until FindItem(ParentItem, S) = nil;
  827. SetItemHasChildren(ParentItem, True);
  828. Item := InsertItem(ParentItem, S, '', True);
  829. SelectItem(Item);
  830. if CanFocus then
  831. SetFocus;
  832. TreeView_EditLabel(Handle, Item);
  833. end;
  834. { TFolderTreeView }
  835. function TFolderTreeView.ItemChildrenNeeded(const Item: HTREEITEM): Boolean;
  836. procedure AddDrives;
  837. var
  838. Drives: DWORD;
  839. Drive: Char;
  840. begin
  841. Drives := GetLogicalDrives;
  842. for Drive := 'A' to 'Z' do begin
  843. if (Drives and 1 <> 0) or IsNetworkDrive(Drive) then
  844. InsertItem(nil, Drive + ':', GetFileDisplayName(Drive + ':\'), False);
  845. Drives := Drives shr 1;
  846. end;
  847. end;
  848. function AddSubdirectories(const ParentItem: HTREEITEM;
  849. const Path: String): Boolean;
  850. var
  851. OldErrorMode: UINT;
  852. H: THandle;
  853. FindData: TWin32FindData;
  854. S: String;
  855. begin
  856. OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  857. try
  858. { The path might be on a disconnected network drive. Ensure it's
  859. connected before attempting to enumerate subdirectories. }
  860. if Length(Path) = 3 then begin { ...only do this on the root }
  861. if not EnsurePathIsAccessible(Path) then begin
  862. Result := False;
  863. Exit;
  864. end;
  865. { Refresh the icon and text in case the drive was indeed reconnected }
  866. RefreshDriveItem(ParentItem, GetFileDisplayName(Path));
  867. end;
  868. Result := True;
  869. H := FindFirstFile(PChar(AddBackslash(Path) + '*'), FindData);
  870. if H <> INVALID_HANDLE_VALUE then begin
  871. try
  872. repeat
  873. if IsListableDirectory(FindData) then begin
  874. S := FindData.cFileName;
  875. InsertItem(ParentItem, S, GetFileDisplayName(AddBackslash(Path) + S),
  876. False);
  877. end;
  878. until not FindNextFile(H, FindData);
  879. finally
  880. Windows.FindClose(H);
  881. end;
  882. end;
  883. finally
  884. SetErrorMode(OldErrorMode);
  885. end;
  886. end;
  887. begin
  888. if Item = nil then begin
  889. AddDrives;
  890. Result := True;
  891. end
  892. else begin
  893. Result := AddSubdirectories(Item, GetItemFullPath(Item));
  894. if Result then begin
  895. { When a text callback is used, sorting after all items are inserted is
  896. exponentially faster than using hInsertAfter=TVI_SORT }
  897. TreeView_SortChildren(Handle, Item, False);
  898. end;
  899. end;
  900. end;
  901. function TFolderTreeView.GetItemFullPath(Item: HTREEITEM): String;
  902. begin
  903. Result := inherited GetItemFullPath(Item);
  904. if (Length(Result) = 2) and (Result[2] = ':') then
  905. Result := Result + '\';
  906. end;
  907. function TFolderTreeView.GetItemImageIndex(const Item: HTREEITEM;
  908. const NewItem, SelectedImage: Boolean): Integer;
  909. begin
  910. if NewItem then
  911. Result := GetDefFolderImageIndex(SelectedImage)
  912. else
  913. Result := GetFileImageIndex(GetItemFullPath(Item), SelectedImage);
  914. end;
  915. function TFolderTreeView.ItemHasChildren(const Item: HTREEITEM): Boolean;
  916. var
  917. Path: String;
  918. OldErrorMode: UINT;
  919. begin
  920. Path := GetItemFullPath(Item);
  921. OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  922. try
  923. Result := (GetDriveType(PChar(AddBackslash(PathExtractDrive(Path)))) = DRIVE_REMOTE) or
  924. HasSubfolders(Path);
  925. finally
  926. SetErrorMode(OldErrorMode);
  927. end;
  928. end;
  929. procedure TFolderTreeView.RefreshDriveItem(const Item: HTREEITEM;
  930. const ANewDisplayName: String);
  931. var
  932. TVItem: TTVItem;
  933. begin
  934. TVItem.mask := TVIF_IMAGE or TVIF_SELECTEDIMAGE;
  935. TVItem.hItem := Item;
  936. TVItem.iImage := I_IMAGECALLBACK;
  937. TVItem.iSelectedImage := I_IMAGECALLBACK;
  938. if ANewDisplayName <> '' then begin
  939. TVItem.mask := TVItem.mask or TVIF_TEXT;
  940. TVItem.pszText := PChar(ANewDisplayName);
  941. end;
  942. TreeView_SetItem(Handle, TVItem);
  943. end;
  944. { TStartMenuFolderTreeView }
  945. procedure TStartMenuFolderTreeView.CreateParams(var Params: TCreateParams);
  946. begin
  947. inherited;
  948. Params.Style := Params.Style and not TVS_LINESATROOT;
  949. end;
  950. function TStartMenuFolderTreeView.GetItemImageIndex(const Item: HTREEITEM;
  951. const NewItem, SelectedImage: Boolean): Integer;
  952. begin
  953. Result := FImageIndexes[SelectedImage];
  954. end;
  955. function TStartMenuFolderTreeView.GetRootItem: HTREEITEM;
  956. begin
  957. { The top item ('Programs') is considered the root }
  958. Result := TreeView_GetRoot(Handle);
  959. end;
  960. function TStartMenuFolderTreeView.ItemChildrenNeeded(const Item: HTREEITEM): Boolean;
  961. procedure AddSubfolders(const ParentItem: HTREEITEM; const Path, StartupPath: String);
  962. var
  963. StartupName: String;
  964. OldErrorMode: UINT;
  965. H: THandle;
  966. FindData: TWin32FindData;
  967. S: String;
  968. begin
  969. { Determine the name of the Startup folder so that we can hide it from the
  970. list }
  971. if StartupPath <> '' then
  972. if PathCompare(AddBackslash(Path), PathExtractPath(StartupPath)) = 0 then
  973. StartupName := PathExtractName(StartupPath);
  974. OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  975. try
  976. H := FindFirstFile(PChar(AddBackslash(Path) + '*'), FindData);
  977. if H <> INVALID_HANDLE_VALUE then begin
  978. try
  979. repeat
  980. if IsListableDirectory(FindData) then begin
  981. S := FindData.cFileName;
  982. if PathCompare(S, StartupName) <> 0 then
  983. if FindItem(ParentItem, S) = nil then
  984. InsertItem(ParentItem, S, GetFileDisplayName(AddBackslash(Path) + S), False);
  985. end;
  986. until not FindNextFile(H, FindData);
  987. finally
  988. Windows.FindClose(H);
  989. end;
  990. end;
  991. finally
  992. SetErrorMode(OldErrorMode);
  993. end;
  994. end;
  995. var
  996. Root, S: String;
  997. NewItem: HTREEITEM;
  998. Path: String;
  999. begin
  1000. Result := True;
  1001. if Item = nil then begin
  1002. Root := FUserPrograms;
  1003. if Root = '' then begin
  1004. { User programs folder doesn't exist for some reason? }
  1005. Root := FCommonPrograms;
  1006. if Root = '' then
  1007. Exit;
  1008. end;
  1009. FImageIndexes[False] := GetFileImageIndex(Root, False);
  1010. FImageIndexes[True] := FImageIndexes[False];
  1011. S := GetFileDisplayName(Root);
  1012. if S = '' then
  1013. S := PathExtractName(Root);
  1014. NewItem := InsertItem(nil, '', S, False);
  1015. TreeView_Expand(Handle, NewItem, TVE_EXPAND);
  1016. end
  1017. else begin
  1018. Path := GetItemFullPath(Item);
  1019. if FCommonPrograms <> '' then
  1020. AddSubfolders(Item, AddBackslash(FCommonPrograms) + Path, FCommonStartup);
  1021. if FUserPrograms <> '' then
  1022. AddSubfolders(Item, AddBackslash(FUserPrograms) + Path, FUserStartup);
  1023. TreeView_SortChildren(Handle, Item, False);
  1024. end;
  1025. end;
  1026. function TStartMenuFolderTreeView.ItemHasChildren(const Item: HTREEITEM): Boolean;
  1027. var
  1028. Path: String;
  1029. begin
  1030. Path := GetItemFullPath(Item);
  1031. if (FCommonPrograms <> '') and HasSubfolders(AddBackslash(FCommonPrograms) + Path) then
  1032. Result := True
  1033. else if (FUserPrograms <> '') and HasSubfolders(AddBackslash(FUserPrograms) + Path) then
  1034. Result := True
  1035. else
  1036. Result := False;
  1037. end;
  1038. procedure TStartMenuFolderTreeView.SetPaths(const AUserPrograms, ACommonPrograms,
  1039. AUserStartup, ACommonStartup: String);
  1040. begin
  1041. FUserPrograms := AUserPrograms;
  1042. FCommonPrograms := ACommonPrograms;
  1043. FUserStartup := AUserStartup;
  1044. FCommonStartup := ACommonStartup;
  1045. RecreateWnd;
  1046. end;
  1047. function GetSystemDir: String;
  1048. var
  1049. Buf: array[0..MAX_PATH-1] of Char;
  1050. begin
  1051. GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  1052. Result := StrPas(Buf);
  1053. end;
  1054. initialization
  1055. InitThemeLibrary;
  1056. SHPathPrepareForWriteFunc := GetProcAddress(LoadLibrary(PChar(AddBackslash(GetSystemDir) + shell32)),
  1057. 'SHPathPrepareForWriteW');
  1058. end.