FolderTreeView.pas 41 KB

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