| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331 |
- unit FolderTreeView;
- {
- Inno Setup
- Copyright (C) 1997-2025 Jordan Russell
- Portions by Martijn Laan
- For conditions of distribution and use, see LICENSE.TXT.
- TFolderTreeView component
- }
- interface
- uses
- Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, CommCtrl;
- type
- TCustomFolderTreeView = class;
- TFolderRenameEvent = procedure(Sender: TCustomFolderTreeView;
- var NewName: String; var Accept: Boolean) of object;
- TCustomFolderTreeView = class(TWinControl)
- private
- FDestroyingHandle: Boolean;
- FDirectory: String;
- FItemExpanding: Boolean;
- FOnChange: TNotifyEvent;
- FOnRename: TFolderRenameEvent;
- class constructor Create;
- class destructor Destroy;
- procedure Change;
- procedure DeleteObsoleteNewItems(const ParentItem, ItemToKeep: HTREEITEM);
- function FindItem(const ParentItem: HTREEITEM; const AName: String): HTREEITEM;
- function FindOrCreateItem(const ParentItem: HTREEITEM; const AName: String): HTREEITEM;
- function GetItemFullPath(Item: HTREEITEM): String; virtual;
- function InsertItem(const ParentItem: HTREEITEM; const AName, ACustomDisplayName: String;
- const ANewItem: Boolean): HTREEITEM;
- procedure SelectItem(const Item: HTREEITEM);
- procedure SetItemHasChildren(const Item: HTREEITEM; const AHasChildren: Boolean);
- procedure SetDirectory(const Value: String);
- function TryExpandItem(const Item: HTREEITEM): Boolean;
- procedure CNKeyDown(var Message: TWMKeyDown); message CN_KEYDOWN;
- procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
- procedure WMCtlColorEdit(var Message: TMessage); message WM_CTLCOLOREDIT;
- procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY;
- procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
- protected
- function ItemChildrenNeeded(const Item: HTREEITEM): Boolean; virtual; abstract;
- procedure CreateParams(var Params: TCreateParams); override;
- procedure CreateWnd; override;
- function GetItemImageIndex(const Item: HTREEITEM;
- const NewItem, SelectedImage: Boolean): Integer; virtual; abstract;
- function GetRootItem: HTREEITEM; virtual;
- function ItemHasChildren(const Item: HTREEITEM): Boolean; virtual; abstract;
- procedure KeyDown(var Key: Word; Shift: TShiftState); override;
- property OnChange: TNotifyEvent read FOnChange write FOnChange;
- property OnRename: TFolderRenameEvent read FOnRename write FOnRename;
- public
- constructor Create(AOwner: TComponent); override;
- procedure ChangeDirectory(const Value: String; const CreateNewItems: Boolean);
- procedure CreateNewDirectory(const ADefaultName: String);
- property Directory: String read FDirectory write SetDirectory;
- end;
- TFolderTreeView = class(TCustomFolderTreeView)
- private
- procedure RefreshDriveItem(const Item: HTREEITEM; const ANewDisplayName: String);
- protected
- function ItemChildrenNeeded(const Item: HTREEITEM): Boolean; override;
- function ItemHasChildren(const Item: HTREEITEM): Boolean; override;
- function GetItemFullPath(Item: HTREEITEM): String; override;
- function GetItemImageIndex(const Item: HTREEITEM;
- const NewItem, SelectedImage: Boolean): Integer; override;
- published
- property Anchors;
- property TabOrder;
- property TabStop default True;
- property Visible;
- property OnChange;
- property OnRename;
- end;
- TStartMenuFolderTreeView = class(TCustomFolderTreeView)
- private
- FUserPrograms, FCommonPrograms: String;
- FUserStartup, FCommonStartup: String;
- FImageIndexes: array[Boolean] of Integer;
- protected
- procedure CreateParams(var Params: TCreateParams); override;
- function GetRootItem: HTREEITEM; override;
- function ItemChildrenNeeded(const Item: HTREEITEM): Boolean; override;
- function ItemHasChildren(const Item: HTREEITEM): Boolean; override;
- function GetItemImageIndex(const Item: HTREEITEM;
- const NewItem, SelectedImage: Boolean): Integer; override;
- public
- procedure SetPaths(const AUserPrograms, ACommonPrograms,
- AUserStartup, ACommonStartup: String);
- published
- property Anchors;
- property TabOrder;
- property TabStop default True;
- property Visible;
- property OnChange;
- property OnRename;
- end;
- TFolderTreeViewStyleHook = class(TScrollingStyleHook)
- {$IFDEF VCLSTYLES}
- strict private
- procedure UpdateBrushColor;
- procedure UpdateFontColor;
- procedure TVMSetBkColor(var Message: TMessage); message TVM_SETBKCOLOR;
- procedure TVMSetTextColor(var Message: TMessage); message TVM_SETTEXTCOLOR;
- procedure WMMouseMove(var Msg: TWMMouse); message WM_MOUSEMOVE;
- public
- constructor Create(AControl: TWinControl); override;
- {$ENDIF}
- end;
- procedure Register;
- implementation
- {
- Notes:
- 1. Don't call TreeView_SelectItem without calling TreeView_Expand on the
- item's parents first. Otherwise infinite recursion can occur:
- a. TreeView_SelectItem will first set the selected item. It will then try
- to expand the parent node, causing a TVN_ITEMEXPANDING message to be
- sent.
- b. If the TVN_ITEMEXPANDING handler calls TreeView_SortChildren, TV_SortCB
- will call TV_EnsureVisible if the selected item was one of the items
- affected by the sorting (which will always be the case).
- c. TV_EnsureVisible will expand parent nodes if necessary. However, since
- we haven't yet returned from the original TVN_ITEMEXPANDING message
- handler, the parent node doesn't yet have the TVIS_EXPANDED state,
- thus it thinks the node still needs expanding.
- d. Another, nested TVN_ITEMEXPANDING message is sent, bringing us back to
- step b.
- (Reproducible on Windows 95 and 2000.)
- The recursion can be seen if you comment out the ExpandParents call in
- the SelectItem method, then click "New Folder" on a folder with no
- children.
- (Note, however, that because of the ChildrenAdded check in our
- TVN_ITEMEXPANDING handler, it can only recurse once. That won't cause a
- fatal stack overflow (like it did before the ChildrenAdded check was
- added), but it's still wrong to allow that to happen.)
- }
- uses
- ShellAPI, Types, GraphUtil,
- {$IFDEF VCLSTYLES} Vcl.Themes, UITypes, {$ELSE} Themes, {$ENDIF}
- PathFunc, NewUxTheme, UnsignedFunc;
- const
- SHPPFW_NONE = $00000000;
- var
- SHPathPrepareForWriteFunc: function(hwnd: HWND; punkEnableModless: Pointer;
- pszPath: PChar; dwFlags: DWORD): HRESULT; stdcall;
- const
- TVM_SETEXTENDEDSTYLE = TV_FIRST + 44;
- TVS_EX_DOUBLEBUFFER = $0004;
- procedure Register;
- begin
- RegisterComponents('JR', [TFolderTreeView, TStartMenuFolderTreeView]);
- end;
- function IsListableDirectory(const FindData: TWin32FindData): Boolean;
- begin
- Result := (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0) and
- (FindData.dwFileAttributes and (FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM) <>
- (FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM)) and
- (StrComp(FindData.cFileName, '.') <> 0) and
- (StrComp(FindData.cFileName, '..') <> 0);
- end;
- function HasSubfolders(const Path: String): Boolean;
- var
- H: THandle;
- FindData: TWin32FindData;
- begin
- Result := False;
- H := FindFirstFile(PChar(AddBackslash(Path) + '*'), FindData);
- if H <> INVALID_HANDLE_VALUE then begin
- try
- repeat
- if IsListableDirectory(FindData) then begin
- Result := True;
- Break;
- end;
- until not FindNextFile(H, FindData);
- finally
- Windows.FindClose(H);
- end;
- end;
- end;
- function GetFileDisplayName(const Filename: String): String;
- var
- FileInfo: TSHFileInfo;
- begin
- if SHGetFileInfo(PChar(Filename), 0, FileInfo, SizeOf(FileInfo),
- SHGFI_DISPLAYNAME) <> 0 then
- Result := FileInfo.szDisplayName
- else
- Result := '';
- end;
- function GetFileImageIndex(const Filename: String; const OpenIcon: Boolean): Integer;
- const
- OpenFlags: array[Boolean] of UINT = (0, SHGFI_OPENICON);
- var
- FileInfo: TSHFileInfo;
- begin
- if SHGetFileInfo(PChar(Filename), 0, FileInfo, SizeOf(FileInfo),
- SHGFI_SYSICONINDEX or SHGFI_SMALLICON or OpenFlags[OpenIcon]) <> 0 then
- Result := FileInfo.iIcon
- else
- Result := 0;
- end;
- function GetDefFolderImageIndex(const OpenIcon: Boolean): Integer;
- const
- OpenFlags: array[Boolean] of UINT = (0, SHGFI_OPENICON);
- var
- FileInfo: TSHFileInfo;
- begin
- if SHGetFileInfo('c:\directory', FILE_ATTRIBUTE_DIRECTORY, FileInfo, SizeOf(FileInfo),
- SHGFI_USEFILEATTRIBUTES or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or OpenFlags[OpenIcon]) <> 0 then
- Result := FileInfo.iIcon
- else
- Result := 0;
- end;
- function IsNetworkDrive(const Drive: Char): Boolean;
- { Returns True if Drive is a network drive. Unlike GetLogicalDrives and
- GetDriveType, this will find the drive even if it's currently in an
- unavailable/disconnected state (i.e. showing a red "X" on the drive icon
- in Windows Explorer). }
- var
- LocalName: String;
- RemoteName: array[0..MAX_PATH-1] of Char;
- RemoteNameLen, ErrorCode: DWORD;
- begin
- LocalName := Drive + ':';
- RemoteNameLen := SizeOf(RemoteName) div SizeOf(RemoteName[0]);
- ErrorCode := WNetGetConnection(PChar(LocalName), RemoteName, RemoteNameLen);
- Result := (ErrorCode = NO_ERROR) or (ErrorCode = ERROR_CONNECTION_UNAVAIL);
- end;
- function MoveAppWindowToActiveWindowMonitor(var OldRect: TRect): Boolean;
- { This moves the application window (Application.Handle) to the same monitor
- as the active window, so that a subsequent Windows dialog will display on
- the same monitor. Based on code from D4+'s TApplication.MessageBox.
- NOTE: This function was copied from CmnFunc.pas. }
- type
- HMONITOR = type THandle;
- TMonitorInfo = record
- cbSize: DWORD;
- rcMonitor: TRect;
- rcWork: TRect;
- dwFlags: DWORD;
- end;
- const
- MONITOR_DEFAULTTONEAREST = $00000002;
- var
- ActiveWindow: HWND;
- Module: HMODULE;
- MonitorFromWindow: function(hwnd: HWND; dwFlags: DWORD): HMONITOR; stdcall;
- GetMonitorInfo: function(hMonitor: HMONITOR; var lpmi: TMonitorInfo): BOOL; stdcall;
- MBMonitor, AppMonitor: HMONITOR;
- Info: TMonitorInfo;
- begin
- Result := False;
- ActiveWindow := GetActiveWindow;
- if ActiveWindow = 0 then Exit;
- Module := GetModuleHandle(user32);
- MonitorFromWindow := GetProcAddress(Module, 'MonitorFromWindow');
- GetMonitorInfo := GetProcAddress(Module, 'GetMonitorInfoA');
- if Assigned(MonitorFromWindow) and Assigned(GetMonitorInfo) then begin
- MBMonitor := MonitorFromWindow(ActiveWindow, MONITOR_DEFAULTTONEAREST);
- AppMonitor := MonitorFromWindow(Application.Handle, MONITOR_DEFAULTTONEAREST);
- if MBMonitor <> AppMonitor then begin
- Info.cbSize := SizeOf(Info);
- if GetMonitorInfo(MBMonitor, Info) then begin
- GetWindowRect(Application.Handle, OldRect);
- SetWindowPos(Application.Handle, 0,
- Info.rcMonitor.Left + ((Info.rcMonitor.Right - Info.rcMonitor.Left) div 2),
- Info.rcMonitor.Top + ((Info.rcMonitor.Bottom - Info.rcMonitor.Top) div 2),
- 0, 0, SWP_NOACTIVATE or SWP_NOREDRAW or SWP_NOSIZE or SWP_NOZORDER);
- Result := True;
- end;
- end;
- end;
- end;
- procedure MoveAppWindowBack(const OldRect: TRect);
- { Moves the application window back to its previous position after a
- successful call to MoveAppWindowToActiveWindowMonitor }
- begin
- SetWindowPos(Application.Handle, 0,
- OldRect.Left + ((OldRect.Right - OldRect.Left) div 2),
- OldRect.Top + ((OldRect.Bottom - OldRect.Top) div 2),
- 0, 0, SWP_NOACTIVATE or SWP_NOREDRAW or SWP_NOSIZE or SWP_NOZORDER);
- end;
- function EnsurePathIsAccessible(const Path: String): Boolean;
- { Calls SHPathPrepareForWrite which ensures the specified path is accessible by
- reconnecting network drives (important) and prompting for media on removable
- drives (not so important for our purposes). (Note that despite its name,
- the function does not test for write access.) }
- var
- ActiveWindow: HWND;
- DidMove: Boolean;
- OldRect: TRect;
- WindowList: Pointer;
- begin
- { SHPathPrepareForWrite only exists on Windows 2000, Me, and later.
- Do nothing on older versions of Windows. }
- if @SHPathPrepareForWriteFunc = nil then begin
- Result := True;
- Exit;
- end;
- { Note: The SHPathPrepareForWrite documentation claims that "user interface
- windows will not be created" when hwnd is NULL, however I found that on
- Windows 2000, it would still display message boxes for network errors.
- (To reproduce: Disable your Local Area Connection and try expanding a
- network drive.) So to avoid bugs from having unowned message boxes floating
- around, go ahead and pass a proper owner window. }
- ActiveWindow := GetActiveWindow;
- DidMove := MoveAppWindowToActiveWindowMonitor(OldRect);
- WindowList := DisableTaskWindows(0);
- try
- Result := SUCCEEDED(SHPathPrepareForWriteFunc(Application.Handle, nil,
- PChar(Path), SHPPFW_NONE));
- finally
- if DidMove then
- MoveAppWindowBack(OldRect);
- EnableTaskWindows(WindowList);
- SetActiveWindow(ActiveWindow);
- end;
- end;
- { TCustomFolderTreeView }
- type
- PItemData = ^TItemData;
- TItemData = record
- Name: String;
- NewItem: Boolean;
- ChildrenAdded: Boolean;
- end;
- class constructor TCustomFolderTreeView.Create;
- begin
- TCustomStyleEngine.RegisterStyleHook(TCustomFolderTreeView, TFolderTreeViewStyleHook);
- end;
- constructor TCustomFolderTreeView.Create(AOwner: TComponent);
- var
- LogFont: TLogFont;
- begin
- inherited;
- ControlStyle := ControlStyle - [csCaptureMouse];
- Width := 121;
- Height := 97;
- ParentColor := False;
- TabStop := True;
- if SystemParametersInfo(SPI_GETICONTITLELOGFONT, SizeOf(LogFont), @LogFont, 0) then
- Font.Handle := CreateFontIndirect(LogFont);
- end;
- procedure TCustomFolderTreeView.CreateParams(var Params: TCreateParams);
- const
- TVS_TRACKSELECT = $0200;
- TVS_SINGLEEXPAND = $0400;
- begin
- InitCommonControls;
- inherited;
- CreateSubClass(Params, WC_TREEVIEW);
- with Params do begin
- Style := Style or WS_CLIPCHILDREN or WS_CLIPSIBLINGS or TVS_LINESATROOT or
- TVS_HASBUTTONS or TVS_SHOWSELALWAYS or TVS_EDITLABELS;
- Style := Style or TVS_TRACKSELECT;
- ExStyle := ExStyle or WS_EX_CLIENTEDGE;
- WindowClass.style := WindowClass.style and not (CS_HREDRAW or CS_VREDRAW);
- end;
- end;
- procedure TCustomFolderTreeView.CreateWnd;
- var
- ImageList: HIMAGELIST;
- FileInfo: TSHFileInfo;
- SaveCursor: HCURSOR;
- begin
- FDestroyingHandle := False;
- inherited;
- TreeView_SetBkColor(Handle, UColorToRGB(Color));
- TreeView_SetTextColor(Handle, UColorToRGB(Font.Color));
- FDirectory := '';
- if csDesigning in ComponentState then
- Exit;
- { Enable the new Explorer-style look }
- if Assigned(SetWindowTheme) then begin
- var LStyle := StyleServices(Self);
- if not LStyle.Enabled or LStyle.IsSystemStyle then
- LStyle := nil;
- if (LStyle = nil) or ColorIsBright(LStyle.GetSystemColor(clWindow)) then
- SetWindowTheme(Handle, 'Explorer', nil)
- else
- SetWindowTheme(Handle, 'DarkMode_Explorer', nil);
- { Like Explorer, enable double buffering to avoid flicker when the mouse
- is moved across the items }
- SendMessage(Handle, TVM_SETEXTENDEDSTYLE, TVS_EX_DOUBLEBUFFER,
- TVS_EX_DOUBLEBUFFER);
- end;
- { Initialize the image list }
- ImageList := SHGetFileInfo('', 0, FileInfo, SizeOf(FileInfo),
- SHGFI_USEFILEATTRIBUTES or SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
- TreeView_SetImageList(Handle, ImageList, TVSIL_NORMAL);
- { Add the root items }
- SaveCursor := SetCursor(LoadCursor(0, IDC_WAIT));
- try
- ItemChildrenNeeded(nil);
- finally
- SetCursor(SaveCursor);
- end;
- end;
- class destructor TCustomFolderTreeView.Destroy;
- begin
- TCustomStyleEngine.UnRegisterStyleHook(TCustomFolderTreeView, TFolderTreeViewStyleHook);
- end;
- procedure TCustomFolderTreeView.WMDestroy(var Message: TWMDestroy);
- begin
- { Work around bug in pre-v6 COMCTL32: If we have the TVS_SINGLEEXPAND style
- and there is a selected item when the window is destroyed, we end up
- getting a bunch of TVN_SINGLEEXPAND messages because it keeps moving the
- selection as it's destroying items, resulting in a stream of "Please
- insert a disk in drive X:" message boxes as the selection moves across
- removable drives.
- Currently, however, this problem isn't seen in practice because we don't
- use TVS_SINGLEEXPAND on pre-XP Windows. }
- FDestroyingHandle := True; { disables our TVN_SELCHANGED handling }
- SelectItem(nil);
- inherited;
- end;
- procedure TCustomFolderTreeView.KeyDown(var Key: Word; Shift: TShiftState);
- var
- Item: HTREEITEM;
- begin
- inherited;
- if (Key = VK_F2) and (Shift * [ssShift, ssAlt, ssCtrl] = []) then begin
- Key := 0;
- Item := TreeView_GetSelection(Handle);
- if Assigned(Item) then
- TreeView_EditLabel(Handle, Item);
- end;
- end;
- procedure TCustomFolderTreeView.CNKeyDown(var Message: TWMKeyDown);
- var
- FocusWnd: HWND;
- begin
- { On Delphi 5+, if a non-VCL control is focused, TApplication.IsKeyMsg will
- send the CN_KEYDOWN message to the nearest VCL control. This means that
- when the edit control is focused, the tree view itself gets CN_KEYDOWN
- messages. Don't let the VCL handle Enter and Escape; if we're on a dialog,
- those keys will close the window. }
- FocusWnd := GetFocus;
- if (FocusWnd <> 0) and (TreeView_GetEditControl(Handle) = FocusWnd) then
- if (Message.CharCode = VK_RETURN) or (Message.CharCode = VK_ESCAPE) then
- Exit;
- inherited;
- end;
- procedure TCustomFolderTreeView.WMEraseBkgnd(var Message: TWMEraseBkgnd);
- begin
- { For TVS_EX_DOUBLEBUFFER to be truly flicker-free, we must use
- comctl32's default WM_ERASEBKGND handling, not the VCL's (which calls
- FillRect). }
- DefaultHandler(Message);
- end;
- procedure TCustomFolderTreeView.WMCtlColorEdit(var Message: TMessage);
- begin
- { We can't let TWinControl.DefaultHandler handle this message. It tries to
- send a CN_CTLCOLOREDIT message to the tree view's internally-created edit
- control, which it won't understand because it's not a VCL control. Without
- this special handling, the border is painted incorrectly on Windows XP
- with themes enabled. }
- Message.Result := DefWindowProc(Handle, Message.Msg, Message.WParam,
- Message.LParam);
- end;
- function TCustomFolderTreeView.GetItemFullPath(Item: HTREEITEM): String;
- var
- TVItem: TTVItem;
- begin
- Result := '';
- while Assigned(Item) do begin
- TVItem.mask := TVIF_PARAM;
- TVItem.hItem := Item;
- if not TreeView_GetItem(Handle, TVItem) then begin
- Result := '';
- Exit;
- end;
- if Result = '' then
- Result := PItemData(TVItem.lParam).Name
- else
- Insert(AddBackslash(PItemData(TVItem.lParam).Name), Result, 1);
- Item := TreeView_GetParent(Handle, Item);
- end;
- end;
- procedure TCustomFolderTreeView.Change;
- var
- Item: HTREEITEM;
- begin
- Item := TreeView_GetSelection(Handle);
- if Assigned(Item) then
- FDirectory := GetItemFullPath(Item)
- else
- FDirectory := '';
- if Assigned(FOnChange) then
- FOnChange(Self);
- end;
- procedure TCustomFolderTreeView.CNNotify(var Message: TWMNotify);
- const
- TVN_SINGLEEXPAND = (TVN_FIRST-15);
- TVNRET_SKIPOLD = 1;
- TVNRET_SKIPNEW = 2;
- procedure HandleClick;
- var
- Item: HTREEITEM;
- HitTestInfo: TTVHitTestInfo;
- begin
- HitTestInfo.pt := ScreenToClient(SmallPointToPoint(TSmallPoint(GetMessagePos())));
- Item := TreeView_HitTest(Handle, HitTestInfo);
- if Assigned(Item) then begin
- if HitTestInfo.flags and TVHT_ONITEMBUTTON <> 0 then
- TreeView_Expand(Handle, Item, TVE_TOGGLE)
- else if TreeView_GetSelection(Handle) <> Item then
- SelectItem(Item);
- end;
- end;
- var
- Hdr: PNMTreeView;
- SaveCursor: HCURSOR;
- DispItem: PTVItem;
- TVItem: TTVItem;
- S: String;
- Accept: Boolean;
- begin
- inherited;
- case Message.NMHdr.code of
- TVN_DELETEITEM:
- begin
- Dispose(PItemData(PNMTreeView(Message.NMHdr).itemOld.lParam));
- end;
- TVN_ITEMEXPANDING:
- begin
- { Sanity check: Make sure this message isn't sent recursively.
- (See top of source code for details.) }
- if FItemExpanding then
- raise Exception.Create('Internal error: Item already expanding');
- FItemExpanding := True;
- try
- Hdr := PNMTreeView(Message.NMHdr);
- if (Hdr.action = TVE_EXPAND) and
- not PItemData(Hdr.itemNew.lParam).ChildrenAdded and
- not PItemData(Hdr.itemNew.lParam).NewItem then begin
- PItemData(Hdr.itemNew.lParam).ChildrenAdded := True;
- SaveCursor := SetCursor(LoadCursor(0, IDC_WAIT));
- try
- if ItemChildrenNeeded(Hdr.itemNew.hItem) then begin
- { If no subfolders were found, and there are no 'new' items
- underneath the parent item, remove the '+' sign }
- if TreeView_GetChild(Handle, Hdr.itemNew.hItem) = nil then
- SetItemHasChildren(Hdr.itemNew.hItem, False);
- end
- else begin
- { A result of False means no children were added due to a
- temporary error and that it should try again next time }
- PItemData(Hdr.itemNew.lParam).ChildrenAdded := False;
- { Return 1 to cancel the expansion process (although it seems
- to do that anyway when it sees no children were added) }
- Message.Result := 1;
- end;
- finally
- SetCursor(SaveCursor);
- end;
- end;
- finally
- FItemExpanding := False;
- end;
- end;
- TVN_GETDISPINFO:
- begin
- DispItem := @PTVDispInfo(Message.NMHdr).item;
- if DispItem.mask and TVIF_IMAGE <> 0 then begin
- DispItem.iImage := GetItemImageIndex(DispItem.hItem,
- PItemData(DispItem.lParam).NewItem, False);
- end;
- if DispItem.mask and TVIF_SELECTEDIMAGE <> 0 then begin
- DispItem.iSelectedImage := GetItemImageIndex(DispItem.hItem,
- PItemData(DispItem.lParam).NewItem, True);
- end;
- if DispItem.mask and TVIF_CHILDREN <> 0 then begin
- DispItem.cChildren := Ord(Assigned(TreeView_GetChild(Handle, DispItem.hItem)));
- if (DispItem.cChildren = 0) and not PItemData(DispItem.lParam).NewItem then
- DispItem.cChildren := Ord(ItemHasChildren(DispItem.hItem));
- end;
- { Store the values with the item so the callback isn't called again }
- DispItem.mask := DispItem.mask or TVIF_DI_SETITEM;
- end;
- TVN_SELCHANGED:
- begin
- if not FDestroyingHandle then
- Change;
- end;
- TVN_BEGINLABELEDIT:
- begin
- DispItem := @PTVDispInfo(Message.NMHdr).item;
- { Only 'new' items may be renamed }
- if not PItemData(DispItem.lParam).NewItem then
- Message.Result := 1;
- end;
- TVN_ENDLABELEDIT:
- begin
- DispItem := @PTVDispInfo(Message.NMHdr).item;
- { Only 'new' items may be renamed }
- if PItemData(DispItem.lParam).NewItem and
- Assigned(DispItem.pszText) then begin
- S := DispItem.pszText;
- Accept := True;
- if Assigned(FOnRename) then
- FOnRename(Self, S, Accept);
- if Accept then begin
- PItemData(DispItem.lParam).Name := S;
- { Instead of returning 1 to let the tree view update the text,
- set the text ourself. This will downconvert any Unicode
- characters to ANSI (if we're compiled as an ANSI app). }
- TVItem.mask := TVIF_TEXT;
- TVItem.hItem := DispItem.hItem;
- TVItem.pszText := PChar(S);
- TreeView_SetItem(Handle, TVItem);
- TreeView_SortChildren(Handle, TreeView_GetParent(Handle, DispItem.hItem), False);
- Change;
- end;
- end;
- end;
- NM_CLICK:
- begin
- { Use custom click handler to work more like Windows XP Explorer:
- - Items can be selected by clicking anywhere on their respective
- rows, except for the button.
- - In 'friendly tree' mode, clicking an item's icon or caption causes
- the item to expand, but never to collapse. }
- HandleClick;
- Message.Result := 1;
- end;
- end;
- end;
- procedure TCustomFolderTreeView.SetItemHasChildren(const Item: HTREEITEM;
- const AHasChildren: Boolean);
- var
- TVItem: TTVItem;
- begin
- TVItem.mask := TVIF_CHILDREN;
- TVItem.hItem := Item;
- TVItem.cChildren := Ord(AHasChildren);
- TreeView_SetItem(Handle, TVItem);
- end;
- procedure TCustomFolderTreeView.DeleteObsoleteNewItems(const ParentItem,
- ItemToKeep: HTREEITEM);
- { Destroys all 'new' items except for ItemToKeep and its parents. (ItemToKeep
- doesn't necessarily have to be a 'new' item.) Pass nil in the ParentItem
- parameter when calling this method. }
- function EqualsOrContains(const AParent: HTREEITEM; AChild: HTREEITEM): Boolean;
- begin
- Result := False;
- repeat
- if AChild = AParent then begin
- Result := True;
- Break;
- end;
- AChild := TreeView_GetParent(Handle, AChild);
- until AChild = nil;
- end;
- var
- Item, NextItem: HTREEITEM;
- TVItem: TTVItem;
- begin
- Item := TreeView_GetChild(Handle, ParentItem);
- while Assigned(Item) do begin
- { Determine the next item in advance since Item might get deleted }
- NextItem := TreeView_GetNextSibling(Handle, Item);
- TVItem.mask := TVIF_PARAM;
- TVItem.hItem := Item;
- if TreeView_GetItem(Handle, TVItem) then begin
- if PItemData(TVItem.lParam).NewItem and not EqualsOrContains(Item, ItemToKeep) then begin
- TreeView_DeleteItem(Handle, Item);
- { If there are no children left on the parent, remove its '+' sign }
- if TreeView_GetChild(Handle, ParentItem) = nil then
- SetItemHasChildren(ParentItem, False);
- end
- else
- DeleteObsoleteNewItems(Item, ItemToKeep);
- end;
- Item := NextItem;
- end;
- end;
- function TCustomFolderTreeView.InsertItem(const ParentItem: HTREEITEM;
- const AName, ACustomDisplayName: String; const ANewItem: Boolean): HTREEITEM;
- var
- InsertStruct: TTVInsertStruct;
- ItemData: PItemData;
- begin
- if ANewItem then
- DeleteObsoleteNewItems(nil, ParentItem);
- InsertStruct.hParent := ParentItem;
- if ANewItem then
- InsertStruct.hInsertAfter := TVI_SORT
- else
- InsertStruct.hInsertAfter := TVI_LAST;
- InsertStruct.item.mask := TVIF_TEXT or TVIF_IMAGE or
- TVIF_SELECTEDIMAGE or TVIF_CHILDREN or TVIF_PARAM;
- InsertStruct.item.hItem := nil; { not used }
- if ANewItem then begin
- InsertStruct.item.mask := InsertStruct.item.mask or TVIF_STATE;
- InsertStruct.item.stateMask := TVIS_CUT;
- InsertStruct.item.state := TVIS_CUT;
- end;
- { Note: There's no performance advantage in using a callback for the text.
- During a TreeView_InsertItem call, the tree view will try to read the
- new item's text in order to update the horizontal scroll bar range.
- (It doesn't wait until the item is painted.)
- In addition, the caller may sort newly-inserted subitems, which obviously
- requires reading their text. }
- if ACustomDisplayName = '' then
- InsertStruct.item.pszText := PChar(AName)
- else
- InsertStruct.item.pszText := PChar(ACustomDisplayName);
- InsertStruct.item.iImage := I_IMAGECALLBACK;
- InsertStruct.item.iSelectedImage := I_IMAGECALLBACK;
- if ANewItem then
- InsertStruct.item.cChildren := 0
- else begin
- if ParentItem = nil then
- InsertStruct.item.cChildren := 1
- else
- InsertStruct.item.cChildren := I_CHILDRENCALLBACK;
- end;
- InsertStruct.item.lParam := 0;
- New(ItemData);
- ItemData.Name := AName;
- ItemData.NewItem := ANewItem;
- ItemData.ChildrenAdded := False;
- Pointer(InsertStruct.item.lParam) := ItemData;
- Result := TreeView_InsertItem(Handle, InsertStruct);
- end;
- function TCustomFolderTreeView.FindItem(const ParentItem: HTREEITEM;
- const AName: String): HTREEITEM;
- var
- TVItem: TTVItem;
- begin
- Result := TreeView_GetChild(Handle, ParentItem);
- while Assigned(Result) do begin
- TVItem.mask := TVIF_PARAM;
- TVItem.hItem := Result;
- if TreeView_GetItem(Handle, TVItem) then
- if PathCompare(PItemData(TVItem.lParam).Name, AName) = 0 then
- Break;
- Result := TreeView_GetNextSibling(Handle, Result);
- end;
- end;
- function TCustomFolderTreeView.FindOrCreateItem(const ParentItem: HTREEITEM;
- const AName: String): HTREEITEM;
- begin
- Result := FindItem(ParentItem, AName);
- if Result = nil then begin
- if Assigned(ParentItem) then
- SetItemHasChildren(ParentItem, True);
- Result := InsertItem(ParentItem, AName, '', True);
- end;
- end;
- function TCustomFolderTreeView.GetRootItem: HTREEITEM;
- begin
- Result := nil;
- end;
- procedure TCustomFolderTreeView.SelectItem(const Item: HTREEITEM);
- procedure ExpandParents(Item: HTREEITEM);
- begin
- Item := TreeView_GetParent(Handle, Item);
- if Assigned(Item) then begin
- ExpandParents(Item);
- TreeView_Expand(Handle, Item, TVE_EXPAND);
- end;
- end;
- begin
- { Must manually expand parents prior to calling TreeView_SelectItem;
- see top of source code for details }
- if Assigned(Item) then
- ExpandParents(Item);
- TreeView_SelectItem(Handle, Item);
- end;
- function TCustomFolderTreeView.TryExpandItem(const Item: HTREEITEM): Boolean;
- { Tries to expand the specified item. Returns True if the item's children were
- initialized (if any), or False if the initialization failed due to a
- temporary error (i.e. ItemChildrenNeeded returned False). }
- var
- TVItem: TTVItem;
- begin
- TreeView_Expand(Handle, Item, TVE_EXPAND);
- TVItem.mask := TVIF_CHILDREN or TVIF_PARAM;
- TVItem.hItem := Item;
- Result := TreeView_GetItem(Handle, TVItem) and
- (PItemData(TVItem.lParam).ChildrenAdded or (TVItem.cChildren = 0));
- end;
- procedure TCustomFolderTreeView.ChangeDirectory(const Value: String;
- const CreateNewItems: Boolean);
- { Changes to the specified directory. Value must begin with a drive letter
- (e.g. "C:\directory"); relative paths and UNC paths are not allowed.
- If CreateNewItems is True, new items will be created if one or more elements
- of the path do not exist. }
- var
- PStart, PEnd: PChar;
- S: String;
- ParentItem, Item: HTREEITEM;
- begin
- SelectItem(nil);
- ParentItem := GetRootItem;
- PStart := PChar(Value);
- while PStart^ <> #0 do begin
- if Assigned(ParentItem) then
- if not TryExpandItem(ParentItem) then
- Break;
- { Extract a single path component }
- PEnd := PStart;
- while (PEnd^ <> #0) and not PathCharIsSlash(PEnd^) do
- PEnd := PathStrNextChar(PEnd);
- SetString(S, PStart, PEnd - PStart);
- { Find that component under ParentItem }
- if CreateNewItems and Assigned(ParentItem) then
- Item := FindOrCreateItem(ParentItem, S)
- else
- Item := FindItem(ParentItem, S);
- if Item = nil then
- Break;
- ParentItem := Item;
- PStart := PEnd;
- while PathCharIsSlash(PStart^) do
- Inc(PStart);
- end;
- if Assigned(ParentItem) then
- SelectItem(ParentItem);
- end;
- procedure TCustomFolderTreeView.SetDirectory(const Value: String);
- begin
- ChangeDirectory(Value, False);
- end;
- procedure TCustomFolderTreeView.CreateNewDirectory(const ADefaultName: String);
- { Creates a new node named AName underneath the selected node. Does nothing
- if there is no selected node. }
- var
- ParentItem, Item: HTREEITEM;
- I: Integer;
- S: String;
- begin
- ParentItem := TreeView_GetSelection(Handle);
- if ParentItem = nil then
- Exit;
- DeleteObsoleteNewItems(nil, ParentItem);
- { Expand and find a unique name }
- if not TryExpandItem(ParentItem) then
- Exit;
- I := 0;
- repeat
- Inc(I);
- if I = 1 then
- S := ADefaultName
- else
- S := ADefaultName + Format(' (%d)', [I]);
- until FindItem(ParentItem, S) = nil;
- SetItemHasChildren(ParentItem, True);
- Item := InsertItem(ParentItem, S, '', True);
- SelectItem(Item);
- if CanFocus then
- SetFocus;
- TreeView_EditLabel(Handle, Item);
- end;
- { TFolderTreeView }
- function TFolderTreeView.ItemChildrenNeeded(const Item: HTREEITEM): Boolean;
- procedure AddDrives;
- var
- Drives: DWORD;
- Drive: Char;
- begin
- Drives := GetLogicalDrives;
- for Drive := 'A' to 'Z' do begin
- if (Drives and 1 <> 0) or IsNetworkDrive(Drive) then
- InsertItem(nil, Drive + ':', GetFileDisplayName(Drive + ':\'), False);
- Drives := Drives shr 1;
- end;
- end;
- function AddSubdirectories(const ParentItem: HTREEITEM;
- const Path: String): Boolean;
- var
- OldErrorMode: UINT;
- H: THandle;
- FindData: TWin32FindData;
- S: String;
- begin
- OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
- try
- { The path might be on a disconnected network drive. Ensure it's
- connected before attempting to enumerate subdirectories. }
- if Length(Path) = 3 then begin { ...only do this on the root }
- if not EnsurePathIsAccessible(Path) then begin
- Result := False;
- Exit;
- end;
- { Refresh the icon and text in case the drive was indeed reconnected }
- RefreshDriveItem(ParentItem, GetFileDisplayName(Path));
- end;
- Result := True;
- H := FindFirstFile(PChar(AddBackslash(Path) + '*'), FindData);
- if H <> INVALID_HANDLE_VALUE then begin
- try
- repeat
- if IsListableDirectory(FindData) then begin
- S := FindData.cFileName;
- InsertItem(ParentItem, S, GetFileDisplayName(AddBackslash(Path) + S),
- False);
- end;
- until not FindNextFile(H, FindData);
- finally
- Windows.FindClose(H);
- end;
- end;
- finally
- SetErrorMode(OldErrorMode);
- end;
- end;
- begin
- if Item = nil then begin
- AddDrives;
- Result := True;
- end
- else begin
- Result := AddSubdirectories(Item, GetItemFullPath(Item));
- if Result then begin
- { When a text callback is used, sorting after all items are inserted is
- exponentially faster than using hInsertAfter=TVI_SORT }
- TreeView_SortChildren(Handle, Item, False);
- end;
- end;
- end;
- function TFolderTreeView.GetItemFullPath(Item: HTREEITEM): String;
- begin
- Result := inherited GetItemFullPath(Item);
- if (Length(Result) = 2) and (Result[2] = ':') then
- Result := Result + '\';
- end;
- function TFolderTreeView.GetItemImageIndex(const Item: HTREEITEM;
- const NewItem, SelectedImage: Boolean): Integer;
- begin
- if NewItem then
- Result := GetDefFolderImageIndex(SelectedImage)
- else
- Result := GetFileImageIndex(GetItemFullPath(Item), SelectedImage);
- end;
- function TFolderTreeView.ItemHasChildren(const Item: HTREEITEM): Boolean;
- var
- Path: String;
- OldErrorMode: UINT;
- begin
- Path := GetItemFullPath(Item);
- OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
- try
- Result := (GetDriveType(PChar(AddBackslash(PathExtractDrive(Path)))) = DRIVE_REMOTE) or
- HasSubfolders(Path);
- finally
- SetErrorMode(OldErrorMode);
- end;
- end;
- procedure TFolderTreeView.RefreshDriveItem(const Item: HTREEITEM;
- const ANewDisplayName: String);
- var
- TVItem: TTVItem;
- begin
- TVItem.mask := TVIF_IMAGE or TVIF_SELECTEDIMAGE;
- TVItem.hItem := Item;
- TVItem.iImage := I_IMAGECALLBACK;
- TVItem.iSelectedImage := I_IMAGECALLBACK;
- if ANewDisplayName <> '' then begin
- TVItem.mask := TVItem.mask or TVIF_TEXT;
- TVItem.pszText := PChar(ANewDisplayName);
- end;
- TreeView_SetItem(Handle, TVItem);
- end;
- { TStartMenuFolderTreeView }
- procedure TStartMenuFolderTreeView.CreateParams(var Params: TCreateParams);
- begin
- inherited;
- Params.Style := Params.Style and not TVS_LINESATROOT;
- end;
- function TStartMenuFolderTreeView.GetItemImageIndex(const Item: HTREEITEM;
- const NewItem, SelectedImage: Boolean): Integer;
- begin
- Result := FImageIndexes[SelectedImage];
- end;
- function TStartMenuFolderTreeView.GetRootItem: HTREEITEM;
- begin
- { The top item ('Programs') is considered the root }
- Result := TreeView_GetRoot(Handle);
- end;
- function TStartMenuFolderTreeView.ItemChildrenNeeded(const Item: HTREEITEM): Boolean;
- procedure AddSubfolders(const ParentItem: HTREEITEM; const Path, StartupPath: String);
- var
- StartupName: String;
- OldErrorMode: UINT;
- H: THandle;
- FindData: TWin32FindData;
- S: String;
- begin
- { Determine the name of the Startup folder so that we can hide it from the
- list }
- if StartupPath <> '' then
- if PathCompare(AddBackslash(Path), PathExtractPath(StartupPath)) = 0 then
- StartupName := PathExtractName(StartupPath);
- OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
- try
- H := FindFirstFile(PChar(AddBackslash(Path) + '*'), FindData);
- if H <> INVALID_HANDLE_VALUE then begin
- try
- repeat
- if IsListableDirectory(FindData) then begin
- S := FindData.cFileName;
- if PathCompare(S, StartupName) <> 0 then
- if FindItem(ParentItem, S) = nil then
- InsertItem(ParentItem, S, GetFileDisplayName(AddBackslash(Path) + S), False);
- end;
- until not FindNextFile(H, FindData);
- finally
- Windows.FindClose(H);
- end;
- end;
- finally
- SetErrorMode(OldErrorMode);
- end;
- end;
- var
- Root, S: String;
- NewItem: HTREEITEM;
- Path: String;
- begin
- Result := True;
- if Item = nil then begin
- Root := FUserPrograms;
- if Root = '' then begin
- { User programs folder doesn't exist for some reason? }
- Root := FCommonPrograms;
- if Root = '' then
- Exit;
- end;
- FImageIndexes[False] := GetFileImageIndex(Root, False);
- FImageIndexes[True] := FImageIndexes[False];
- S := GetFileDisplayName(Root);
- if S = '' then
- S := PathExtractName(Root);
- NewItem := InsertItem(nil, '', S, False);
- TreeView_Expand(Handle, NewItem, TVE_EXPAND);
- end
- else begin
- Path := GetItemFullPath(Item);
- if FCommonPrograms <> '' then
- AddSubfolders(Item, AddBackslash(FCommonPrograms) + Path, FCommonStartup);
- if FUserPrograms <> '' then
- AddSubfolders(Item, AddBackslash(FUserPrograms) + Path, FUserStartup);
- TreeView_SortChildren(Handle, Item, False);
- end;
- end;
- function TStartMenuFolderTreeView.ItemHasChildren(const Item: HTREEITEM): Boolean;
- var
- Path: String;
- begin
- Path := GetItemFullPath(Item);
- if (FCommonPrograms <> '') and HasSubfolders(AddBackslash(FCommonPrograms) + Path) then
- Result := True
- else if (FUserPrograms <> '') and HasSubfolders(AddBackslash(FUserPrograms) + Path) then
- Result := True
- else
- Result := False;
- end;
- procedure TStartMenuFolderTreeView.SetPaths(const AUserPrograms, ACommonPrograms,
- AUserStartup, ACommonStartup: String);
- begin
- FUserPrograms := AUserPrograms;
- FCommonPrograms := ACommonPrograms;
- FUserStartup := AUserStartup;
- FCommonStartup := ACommonStartup;
- RecreateWnd;
- end;
- {$IFDEF VCLSTYLES}
- { TFolderTreeViewStyleHook - same as Vcl.ComCtrls' TTreeViewStyleHook
- except that it accesses the Control property as a TCustomFolderTreeView instead
- of a TCustomTreeView }
- type
- TWinControlClass = class(TWinControl);
- constructor TFolderTreeViewStyleHook.Create(AControl: TWinControl);
- begin
- inherited;
- OverrideEraseBkgnd := True;
- UpdateFontColor;
- UpdateBrushColor;
- end;
- procedure TFolderTreeViewStyleHook.TVMSetBkColor(var Message: TMessage);
- begin
- UpdateBrushColor;
- Message.LParam := LPARAM(ColorToRGB(Brush.Color));
- Handled := False;
- end;
- procedure TFolderTreeViewStyleHook.TVMSetTextColor(var Message: TMessage);
- begin
- UpdateFontColor;
- Message.LParam := LPARAM(ColorToRGB(FontColor));
- Handled := False;
- end;
- procedure TFolderTreeViewStyleHook.UpdateBrushColor;
- begin
- if seClient in Control.StyleElements then
- Brush.Color := StyleServices.GetStyleColor(scTreeView)
- else
- Brush.Color := TWinControlClass(Control).Color;
- end;
- procedure TFolderTreeViewStyleHook.UpdateFontColor;
- var
- LColor : TColor;
- begin
- if (seFont in Control.StyleElements) then
- begin
- if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemNormal), ecTextColor, LColor) or
- (LColor = clNone) then
- LColor := StyleServices.GetSystemColor(clWindowText);
- FontColor := LColor;
- end else
- FontColor := TWinControlClass(Control).Font.Color;
- end;
- procedure TFolderTreeViewStyleHook.WMMouseMove(var Msg: TWMMouse);
- var
- SF: TScrollInfo;
- begin
- if VertSliderState = tsThumbBtnVertPressed then
- begin
- SF.fMask := SIF_ALL;
- SF.cbSize := SizeOf(SF);
- GetScrollInfo(Handle, SB_VERT, SF);
- ScrollPos := ScrollPos + (SF.nMax - SF.nMin) * ((Mouse.CursorPos.Y - PrevScrollPos) / VertTrackRect.Height);
- if ScrollPos < SF.nMin then
- ScrollPos := SF.nMin;
- if ScrollPos > SF.nMax then
- ScrollPos := SF.nMax;
- PrevScrollPos := Mouse.CursorPos.Y;
- const RoundedScrollPos = Integer(Round(ScrollPos));
- if Control is TCustomFolderTreeView then
- begin
- PostMessage(Handle, WM_VSCROLL, Cardinal(SmallPoint(SB_THUMBTRACK, RoundedScrollPos)), 0);
- SF.nPos := RoundedScrollPos;
- SF.nTrackPos := RoundedScrollPos;
- SetScrollInfo(Handle, SB_VERT, SF, True);
- end
- else
- PostMessage(Handle, WM_VSCROLL, Cardinal(SmallPoint(SB_THUMBPOSITION, RoundedScrollPos)), 0);
- PaintScroll;
- Handled := True;
- Exit;
- end;
- if HorzSliderState = tsThumbBtnHorzPressed then
- begin
- SF.fMask := SIF_ALL;
- SF.cbSize := SizeOf(SF);
- GetScrollInfo(Handle, SB_HORZ, SF);
- ScrollPos := ScrollPos + (SF.nMax - SF.nMin) * ((Mouse.CursorPos.X - PrevScrollPos) / HorzTrackRect.Width);
- if ScrollPos < SF.nMin then
- ScrollPos := SF.nMin;
- if ScrollPos > SF.nMax then
- ScrollPos := SF.nMax;
- PrevScrollPos := Mouse.CursorPos.X;
- const RoundedScrollPos = Integer(Round(ScrollPos));
- if Control is TCustomFolderTreeView then
- begin
- PostMessage(Handle, WM_HSCROLL, Cardinal(SmallPoint(SB_THUMBTRACK, RoundedScrollPos)), 0);
- SF.nPos := RoundedScrollPos;
- SF.nTrackPos := RoundedScrollPos;
- SetScrollInfo(Handle, SB_HORZ, SF, True);
- end
- else
- PostMessage(Handle, WM_HSCROLL, Cardinal(SmallPoint(SB_THUMBPOSITION, RoundedScrollPos)), 0);
- PaintScroll;
- Handled := True;
- Exit;
- end;
- if (HorzSliderState <> tsThumbBtnHorzPressed) and (HorzSliderState = tsThumbBtnHorzHot) then
- begin
- HorzSliderState := tsThumbBtnHorzNormal;
- PaintScroll;
- end;
- if (VertSliderState <> tsThumbBtnVertPressed) and (VertSliderState = tsThumbBtnVertHot) then
- begin
- VertSliderState := tsThumbBtnVertNormal;
- PaintScroll;
- end;
- if (HorzUpState <> tsArrowBtnLeftPressed) and (HorzUpState = tsArrowBtnLeftHot) then
- begin
- HorzUpState := tsArrowBtnLeftNormal;
- PaintScroll;
- end;
- if (HorzDownState <> tsArrowBtnRightPressed) and (HorzDownState =tsArrowBtnRightHot) then
- begin
- HorzDownState := tsArrowBtnRightNormal;
- PaintScroll;
- end;
- if (VertUpState <> tsArrowBtnUpPressed) and (VertUpState = tsArrowBtnUpHot) then
- begin
- VertUpState := tsArrowBtnUpNormal;
- PaintScroll;
- end;
- if (VertDownState <> tsArrowBtnDownPressed) and (VertDownState = tsArrowBtnDownHot) then
- begin
- VertDownState := tsArrowBtnDownNormal;
- PaintScroll;
- end;
- CallDefaultProc(TMessage(Msg));
- if LeftButtonDown then
- PaintScroll;
- Handled := True;
- end;
- {$ENDIF}
- function GetSystemDir: String;
- var
- Buf: array[0..MAX_PATH-1] of Char;
- begin
- GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
- Result := StrPas(Buf);
- end;
- initialization
- InitThemeLibrary;
- SHPathPrepareForWriteFunc := GetProcAddress(LoadLibrary(PChar(AddBackslash(GetSystemDir) + shell32)),
- 'SHPathPrepareForWriteW');
- end.
|