diff --git a/source/const.inc b/source/const.inc index 56ff1f8d..0e9388c4 100644 --- a/source/const.inc +++ b/source/const.inc @@ -28,9 +28,6 @@ const // Used by maskSQL and fixSQL: SQL_VERSION_ANSI = -1; - // Used for simulating a TTreeNode which has subnodes - DUMMY_NODE_TEXT : String = 'Dummy node, should never be visible'; - // Various names of registry variables // User-changable variables have a default value // Note: Color values are in HEX format: $00BBGGRR diff --git a/source/main.pas b/source/main.pas index 52ddff5e..d633fe70 100644 --- a/source/main.pas +++ b/source/main.pas @@ -1821,6 +1821,7 @@ begin FreeAndNil(DataGridHiddenColumns); SynMemoFilter.Clear; SetLength(DataGridSortColumns, 0); + FreeAndNil(SQLHelpForm); // Closing connection Connection.Active := False; diff --git a/source/mysql_connection.pas b/source/mysql_connection.pas index 15e110aa..5035ef1d 100644 --- a/source/mysql_connection.pas +++ b/source/mysql_connection.pas @@ -368,6 +368,7 @@ type property SQL: String read FSQL write SetSQL; property Connection: TMySQLConnection read FConnection write FConnection; end; + PMySQLQuery = ^TMySQLQuery; implementation diff --git a/source/sqlhelp.dfm b/source/sqlhelp.dfm index 06b690e5..f7de3006 100644 --- a/source/sqlhelp.dfm +++ b/source/sqlhelp.dfm @@ -13,7 +13,7 @@ object frmSQLhelp: TfrmSQLhelp FormStyle = fsStayOnTop OldCreateOrder = False OnCreate = FormCreate - OnShow = FormShow + OnDestroy = FormDestroy PixelsPerInch = 96 TextHeight = 13 object btnSearchOnline: TButton @@ -37,7 +37,6 @@ object frmSQLhelp: TfrmSQLhelp Cancel = True Caption = 'Close' Default = True - ModalResult = 1 TabOrder = 1 OnClick = ButtonCloseClick end @@ -70,25 +69,6 @@ object frmSQLhelp: TfrmSQLhelp Align = alLeft BevelOuter = bvNone TabOrder = 0 - object treeTopics: TTreeView - AlignWithMargins = True - Left = 0 - Top = 24 - Width = 153 - Height = 283 - Margins.Left = 0 - Margins.Right = 0 - Margins.Bottom = 0 - Align = alClient - ChangeDelay = 50 - Images = MainForm.ImageListMain - Indent = 19 - ReadOnly = True - ShowLines = False - TabOrder = 1 - OnChange = treeTopicsChange - OnExpanding = treeTopicsExpanding - end object editFilter: TButtonedEdit AlignWithMargins = True Left = 0 @@ -104,8 +84,41 @@ object frmSQLhelp: TfrmSQLhelp LeftButton.Hint = 'Search' LeftButton.ImageIndex = 53 LeftButton.Visible = True + RightButton.ImageIndex = 26 TabOrder = 0 + TextHint = 'Filter' OnChange = editFilterChange + OnRightButtonClick = editFilterRightButtonClick + end + object treeTopics: TVirtualStringTree + AlignWithMargins = True + Left = 0 + Top = 24 + Width = 153 + Height = 283 + Margins.Left = 0 + Margins.Right = 0 + Margins.Bottom = 0 + Align = alClient + Header.AutoSizeIndex = 0 + Header.DefaultHeight = 17 + Header.Font.Charset = DEFAULT_CHARSET + Header.Font.Color = clWindowText + Header.Font.Height = -11 + Header.Font.Name = 'Tahoma' + Header.Font.Style = [] + Header.MainColumn = -1 + Images = MainForm.ImageListMain + TabOrder = 1 + TreeOptions.PaintOptions = [toHotTrack, toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toThemeAware, toUseBlendedImages, toUseExplorerTheme, toHideTreeLinesIfThemed] + OnFocusChanged = treeTopicsFocusChanged + OnFreeNode = treeTopicsFreeNode + OnGetText = treeTopicsGetText + OnGetImageIndex = treeTopicsGetImageIndex + OnGetNodeDataSize = treeTopicsGetNodeDataSize + OnInitChildren = treeTopicsInitChildren + OnInitNode = treeTopicsInitNode + Columns = <> end end object pnlRight: TPanel @@ -143,13 +156,13 @@ object frmSQLhelp: TfrmSQLhelp Width = 405 Height = 16 Align = alTop - Caption = 'lblKeyword' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -13 Font.Name = 'Tahoma' Font.Style = [fsBold] ParentFont = False + ShowAccelChar = False end object lblExample: TLabel Left = 0 @@ -236,4 +249,10 @@ object frmSQLhelp: TfrmSQLhelp Left = 480 Top = 8 end + object timerSearch: TTimer + Interval = 500 + OnTimer = DoSearch + Left = 8 + Top = 320 + end end diff --git a/source/sqlhelp.pas b/source/sqlhelp.pas index 45cbd717..9769671e 100644 --- a/source/sqlhelp.pas +++ b/source/sqlhelp.pas @@ -5,7 +5,7 @@ interface uses Windows, SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, ComCtrls, ExtCtrls, Buttons, SynMemo, SynEditHighlighter, SynHighlighterURI, - SynURIOpener, SynEdit, + SynURIOpener, SynEdit, VirtualTrees, Graphics, mysql_connection; type @@ -17,7 +17,7 @@ type ButtonClose: TButton; pnlMain: TPanel; pnlLeft: TPanel; - treeTopics: TTreeView; + treeTopics: TVirtualStringTree; editFilter: TButtonedEdit; pnlRight: TPanel; Splitter2: TSplitter; @@ -27,23 +27,34 @@ type memoDescription: TSynMemo; lblExample: TLabel; MemoExample: TSynMemo; + timerSearch: TTimer; procedure FormCreate(Sender: TObject); - procedure treeTopicsExpanding(Sender: TObject; Node: TTreeNode; - var AllowExpansion: Boolean); - procedure treeTopicsChange(Sender: TObject; Node: TTreeNode); - procedure FormShow(Sender: TObject); + procedure FormDestroy(Sender: TObject); procedure memosKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure ButtonOnlinehelpClick(Sender: TObject); procedure ButtonCloseClick(Sender: TObject); - function ShowHelpItem: Boolean; - procedure fillTreeLevel( ParentNode: TTreeNode ); - procedure findKeywordInTree; + procedure DoSearch(Sender: TObject); + procedure treeTopicsGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); + procedure treeTopicsInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; + var InitialStates: TVirtualNodeInitStates); + procedure treeTopicsInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; + var ChildCount: Cardinal); + procedure treeTopicsGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer); + procedure treeTopicsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); + procedure treeTopicsFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); + procedure treeTopicsFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; + Column: TColumnIndex); procedure editFilterChange(Sender: TObject); + procedure editFilterRightButtonClick(Sender: TObject); private { Private declarations } FKeyword: String; + FRootTopics: TMySQLQuery; + function GetHelpResult(Node: PVirtualNode): TMySQLQuery; procedure SetKeyword(Value: String); public { Public declarations } @@ -65,22 +76,11 @@ uses helpers, main; {$R *.dfm} -{** - FormCreate -} procedure TfrmSQLhelp.FormCreate(Sender: TObject); begin + // Set window-layout InheritFont(Font); SetWindowSizeGrip(Handle, True); -end; - - -{*** - Startup -} -procedure TfrmSQLhelp.FormShow(Sender: TObject); -begin - // Set window-layout Top := GetRegValue( REGNAME_SQLHELPWINTOP, Top ); Left := GetRegValue( REGNAME_SQLHELPWINLEFT, Left ); Width := GetRegValue( REGNAME_SQLHELPWINWIDTH, Width ); @@ -89,191 +89,154 @@ begin memoDescription.Height := GetRegValue( REGNAME_SQLHELPPRHEIGHT, memoDescription.Height ); Caption := DEFAULT_WINDOW_CAPTION; MainForm.SetupSynEditors; + FixVT(treeTopics); - // Gather help contents for treeview with SQL: HELP "CONTENTS" - fillTreeLevel( nil ); + treeTopics.Clear; + FreeAndNil(FRootTopics); + FRootTopics := Mainform.Connection.GetResults('HELP '+esc('CONTENTS')); + treeTopics.RootNodeCount := FRootTopics.RecordCount; end; - -{*** - Fills exactly one level of the folder-tree - Call with NIL to generate the root folders, - then call recursively to iterate through all folders and fill them - @param TTreeNode Parent node to fill (or NIL) -} -procedure TfrmSQLhelp.fillTreeLevel( ParentNode: TTreeNode ); +procedure TfrmSQLhelp.treeTopicsFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; + Column: TColumnIndex); var - tnode: TTreeNode; Results: TMySQLQuery; - topic: String; + VT: TVirtualStringTree; begin - if ParentNode = nil then begin - treeTopics.Items.Clear; - topic := 'CONTENTS'; - end else begin - ParentNode.DeleteChildren; - topic := ParentNode.Text; - end; - try - Screen.Cursor := crHourglass; - Results := Mainform.Connection.GetResults( 'HELP "'+topic+'"' ); - while not Results.Eof do begin - tnode := treeTopics.Items.AddChild( ParentNode, Results.Col('name')); - if (Results.Col('is_it_category', True) = 'Y') and ((ParentNode = nil) or (ParentNode.Text <> tnode.Text)) then begin - tnode.ImageIndex := ICONINDEX_CATEGORY_CLOSED; - tnode.SelectedIndex := ICONINDEX_CATEGORY_OPENED; - // Add a dummy item to show the plus-button so the user sees that there this - // is a category. When the plus-button is clicked, fetch the content of the category - treeTopics.Items.AddChild( tnode, DUMMY_NODE_TEXT ); - end else begin - tnode.ImageIndex := ICONINDEX_HELPITEM; - tnode.SelectedIndex := tnode.ImageIndex; - end; - Results.Next; - end; - finally - FreeAndNil(Results); - Screen.Cursor := crDefault; - end; -end; - - - -{*** - Show selected keyword in Tree -} -procedure TfrmSQLhelp.findKeywordInTree; -var - tnode : TTreeNode; - i : Integer; - tmp : Boolean; -begin - if Assigned(treeTopics.Selected) and (treeTopics.Selected.Text = FKeyword) then begin - // We've come here after user selected a tree node via mouse. No need to search for it. + // Topic selected + VT := Sender as TVirtualStringTree; + if not Assigned(VT.FocusedNode) then Exit; - end; - i := 0; - while i < treeTopics.Items.Count do - begin - tnode := treeTopics.Items[i]; - inc(i); - if tnode.Text = FKeyword then - begin - tnode.MakeVisible; - treeTopics.Selected := tnode; - break; - end; - treeTopicsExpanding( self, tnode, tmp ); - end; - treeTopics.SetFocus; -end; - - - -{*** - Selected item in treeTopics has changed -} -procedure TfrmSQLhelp.treeTopicsChange(Sender: TObject; Node: TTreeNode); - procedure OpenFolderIcons( ANode: TTreeNode ); - begin - if ANode = nil then - exit; - if ANode.ImageIndex = ICONINDEX_CATEGORY_CLOSED then - begin - ANode.ImageIndex := ICONINDEX_CATEGORY_OPENED; - end; - // Recursively update ANode's parent node - OpenFolderIcons( ANode.Parent ); - end; - -var - i : Integer; - tNode : TTreeNode; -begin - // 1. Show corresponding help-text - if Node.ImageIndex = ICONINDEX_HELPITEM then - Keyword := Node.Text; - - // 2. Ensure the icons in the preceding tree-path of the selected item - // show opened folders on each level - i := 0; - while i < treeTopics.Items.Count do - begin - tNode := treeTopics.Items[i]; - if tNode.ImageIndex = ICONINDEX_CATEGORY_OPENED then - begin - tNode.ImageIndex := ICONINDEX_CATEGORY_CLOSED; - end; - inc(i); - end; - OpenFolderIcons( Node ); - -end; - - - -{*** - Get topics from category -} -procedure TfrmSQLhelp.treeTopicsExpanding(Sender: TObject; Node: TTreeNode; - var AllowExpansion: Boolean); -begin - if (Node.getFirstChild <> nil) and (Node.getFirstChild.Text = DUMMY_NODE_TEXT) then - begin - fillTreeLevel( Node ); - end; -end; - - - -{*** - Fetch and show text of help-item in synmemo's - @return boolean Was the keyword found? -} -function TfrmSQLhelp.ShowHelpItem: Boolean; -var - Results: TMySQLQuery; -begin - lblKeyword.Caption := Copy(Keyword, 0, 100); + if VT.HasChildren[VT.FocusedNode] then + Exit; + FKeyword := VT.Text[VT.FocusedNode, VT.FocusedColumn]; + lblKeyword.Caption := Copy(FKeyword, 0, 100); MemoDescription.Lines.Clear; MemoExample.Lines.Clear; Caption := DEFAULT_WINDOW_CAPTION; - result := false; // Keyword not found yet - if Keyword <> '' then - try + if FKeyword <> '' then try Screen.Cursor := crHourglass; - Results := Mainform.Connection.GetResults('HELP "'+lblKeyword.Caption+'"'); - if Results.RecordCount = 1 then begin - // We found exactly one matching help item - lblKeyword.Caption := Results.Col('name'); - FKeyword := lblKeyword.Caption; - if lblKeyword.Caption = '&' then - lblKeyword.Caption := '&&'; // Avoid displaying "_" as alt-hotkey - Caption := Caption + ' - ' + Keyword; - MemoDescription.Text := fixNewlines(Results.Col('description')); - MemoExample.Text := fixNewlines(Results.Col('example')); - result := true; - end; + Results := Mainform.Connection.GetResults('HELP '+esc(FKeyword)); + Caption := Caption + ' - ' + FKeyword; + MemoDescription.Text := fixNewlines(Results.Col('description', True)); + MemoExample.Text := fixNewlines(Results.Col('example', True)); finally FreeAndNil(Results); Screen.Cursor := crDefault; end; // Show the user if topic is (not) available - if memoDescription.Text = '' then + if memoDescription.GetTextLen = 0 then memoDescription.Text := 'No help available for this keyword or no keyword was selected.'; - if memoExample.Text = '' then + if memoExample.GetTextLen = 0 then memoExample.Text := 'No example available or no keyword was selected.'; end; +procedure TfrmSQLhelp.treeTopicsFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); +var + Results: PMySQLQuery; +begin + // Node gets destroyed - free memory used for bound SQL result + Results := Sender.GetNodeData(Node); + if Assigned(Results^) then + Results^.Free; +end; + + +procedure TfrmSQLhelp.treeTopicsGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer); +begin + // Return open or closed book icon for folders, or document icon for topics + if not (Kind in [ikNormal, ikSelected]) then + Exit; + if Sender.HasChildren[Node] then begin + if Sender.Expanded[Node] then + ImageIndex := ICONINDEX_CATEGORY_OPENED + else + ImageIndex := ICONINDEX_CATEGORY_CLOSED; + end else + ImageIndex := ICONINDEX_HELPITEM; +end; + + +procedure TfrmSQLhelp.treeTopicsGetNodeDataSize(Sender: TBaseVirtualTree; + var NodeDataSize: Integer); +begin + // We bind one TMySQLQuery to a node + NodeDataSize := SizeOf(TMySQLQuery); +end; + + +function TfrmSQLhelp.GetHelpResult(Node: PVirtualNode): TMySQLQuery; +var + P: PMySQLQuery; +begin + // Find right result set for given node + if treeTopics.GetNodeLevel(Node) = 0 then + Result := FRootTopics + else begin + P := treeTopics.GetNodeData(Node.Parent); + Result := P^; + end; +end; + + +procedure TfrmSQLhelp.treeTopicsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); +var + Results: TMySQLQuery; +begin + // Ask result set for node text + Results := GetHelpResult(Node); + Results.RecNo := Node.Index; + CellText := Results.Col('name'); +end; + + +procedure TfrmSQLhelp.treeTopicsInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; + var ChildCount: Cardinal); +var + Results: PMySQLQuery; + VT: TVirtualStringTree; +begin + // Return number of children for folder + VT := Sender as TVirtualStringTree; + Results := VT.GetNodeData(Node); + Results^ := MainForm.Connection.GetResults('HELP '+esc(VT.Text[Node, VT.Header.MainColumn])); + ChildCount := Results.RecordCount; +end; + + +procedure TfrmSQLhelp.treeTopicsInitNode(Sender: TBaseVirtualTree; ParentNode, + Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); +var + Results: TMySQLQuery; + Folder: String; +begin + // Display plus button for nodes which are folders + Results := GetHelpResult(Node); + Results.RecNo := Node.Index; + Folder := ''; + if Assigned(ParentNode) then + Folder := treeTopics.Text[ParentNode, treeTopics.Header.MainColumn]; + if Results.ColExists('is_it_category') + and (Results.Col('is_it_category') = 'Y') + and (Results.Col('name') <> Folder) + then + Include(InitialStates, ivsHasChildren); +end; + -{*** - Save layout and close window -} procedure TfrmSQLhelp.ButtonCloseClick(Sender: TObject); +begin + Close; +end; + + +procedure TfrmSQLhelp.FormDestroy(Sender: TObject); begin OpenRegistry; MainReg.WriteInteger( REGNAME_SQLHELPWINLEFT, Left ); @@ -282,81 +245,144 @@ begin MainReg.WriteInteger( REGNAME_SQLHELPWINHEIGHT, Height ); MainReg.WriteInteger( REGNAME_SQLHELPPLWIDTH, pnlLeft.Width ); MainReg.WriteInteger( REGNAME_SQLHELPPRHEIGHT, memoDescription.Height ); - Close; end; - -{*** - Link/redirect to mysql.com for further help - @see http://www.heidisql.com/sqlhelp.php -} procedure TfrmSQLhelp.ButtonOnlinehelpClick(Sender: TObject); begin + // Link/redirect to mysql.com for further help ShellExec( APPDOMAIN + 'sqlhelp.php?mysqlversion='+inttostr(Mainform.Connection.ServerVersionInt)+ '&keyword='+urlencode(keyword) ); end; - -{*** - Esc pressed - close form. - Seems that if we're in a memo, the ButtonClose.Cancel=True doesn't have an effect -} -procedure TfrmSQLhelp.memosKeyDown(Sender: TObject; var Key: Word; - Shift: TShiftState); +procedure TfrmSQLhelp.memosKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin + // Esc pressed - close form. + // Seems that if we're in a memo, the ButtonClose.Cancel=True doesn't have an effect if Key = VK_ESCAPE then - ButtonCloseClick(self); + Close; end; procedure TfrmSQLhelp.SetKeyword(Value: string); +var + VT: TVirtualStringTree; + Node: PVirtualNode; + Results: TMySQLQuery; + SearchNoInit: Boolean; begin + // Find keyword in tree FKeyword := Value; - if ShowHelpItem then - findKeywordInTree; + if FKeyword = '' then + Exit; + Results := Mainform.Connection.GetResults('HELP '+esc(FKeyword)); + while not Results.Eof do begin + if Results.Col('is_it_category', true) = 'N' then begin + FKeyword := Results.Col('name'); + break; + end; + Results.Next; + end; + FreeAndNil(Results); + + VT := treeTopics; + if (not Assigned(VT.FocusedNode)) // No node selected + or VT.HasChildren[VT.FocusedNode] // Selected node is a folder, not a document + or (VT.Text[VT.FocusedNode, VT.Header.MainColumn] <> FKeyword) // Node is not the right one + then begin + // Start searching in initialized nodes, to minimize number of "HELP xyz" queries in certain cases + Node := VT.GetFirst; + SearchNoInit := False; + while Assigned(Node) do begin + if (not VT.HasChildren[Node]) and (UpperCase(VT.Text[Node, VT.Header.MainColumn]) = UpperCase(FKeyword)) then begin + SelectNode(VT, Node); + break; + end; + if not SearchNoInit then begin + Node := VT.GetNextInitialized(Node); + if not Assigned(Node) then begin + SearchNoInit := True; + Node := VT.GetFirst; + end; + end; + if SearchNoInit then + Node := VT.GetNext(Node); + end; + end; end; procedure TfrmSQLhelp.editFilterChange(Sender: TObject); -var - tnode: TTreeNode; - Results: TMySQLQuery; - topic: String; begin - // Apply filter text - if Trim(editFilter.Text) = '' then begin - fillTreeLevel(nil); - Exit; - end; - - Keyword := editFilter.Text; - treeTopics.Items.Clear; - topic := Keyword; - try - Screen.Cursor := crHourglass; - Results := Mainform.Connection.GetResults('HELP "%'+topic+'%"'); - while not Results.Eof do begin - tnode := treeTopics.Items.AddChild(nil, Results.Col('name')); - if Results.ColExists('is_it_category') and (Results.Col('is_it_category') = 'Y') then begin - tnode.ImageIndex := ICONINDEX_CATEGORY_CLOSED; - tnode.SelectedIndex := ICONINDEX_CATEGORY_OPENED; - // Add a dummy item to show the plus-button so the user sees that there this - // is a category. When the plus-button is clicked, fetch the content of the category - treeTopics.Items.AddChild(tnode, DUMMY_NODE_TEXT); - end else begin - tnode.ImageIndex := ICONINDEX_HELPITEM; - tnode.SelectedIndex := tnode.ImageIndex; - end; - Results.Next; - end; - finally - FreeAndNil(Results); - Screen.Cursor := crDefault; - end; - editFilter.SetFocus; + timerSearch.Enabled := False; + timerSearch.Enabled := True; + editFilter.RightButton.Visible := Trim(editFilter.Text) <> ''; end; +procedure TfrmSQLhelp.editFilterRightButtonClick(Sender: TObject); +begin + editFilter.Clear; +end; + + +procedure TfrmSQLhelp.DoSearch(Sender: TObject); +var + Node: PVirtualNode; + Search: String; + Vis: Boolean; + + function HasVisibleChildItems(Node: PVirtualNode): Boolean; + var + N: PVirtualNode; + begin + N := treeTopics.GetFirstChild(Node); + Result := False; + while Assigned(N) do begin + if treeTopics.HasChildren[N] then + Result := HasVisibleChildItems(N) + else + Result := treeTopics.IsVisible[N]; + if Result then + Exit; + N := treeTopics.GetNextSibling(N); + end; + end; +begin + // Apply filter text + Screen.Cursor := crHourglass; + treeTopics.BeginUpdate; + timerSearch.Enabled := False; + Search := Trim(editFilter.Text); + if Search = '' then begin + // Show all items + Node := treeTopics.GetFirstInitialized; + while Assigned(Node) do begin + treeTopics.IsVisible[Node] := True; + Node := treeTopics.GetNextInitialized(Node); + end; + end else begin + // Hide non matching child items + Node := treeTopics.GetFirst; + while Assigned(Node) do begin + if not treeTopics.HasChildren[Node] then + treeTopics.IsVisible[Node] := Pos(UpperCase(Search), UpperCase(treeTopics.Text[Node, treeTopics.Header.MainColumn])) > 0; + Node := treeTopics.GetNext(Node); + end; + // Hide empty folders + Node := treeTopics.GetFirst; + while Assigned(Node) do begin + if treeTopics.HasChildren[Node] then begin + Vis := HasVisibleChildItems(Node); + treeTopics.Expanded[Node] := Vis; + treeTopics.IsVisible[Node] := Vis; + end; + Node := treeTopics.GetNext(Node); + end; + end; + treeTopics.EndUpdate; + Screen.Cursor := crDefault; +end; + end.