FolderTreeView.pas 44 KB

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