Is it possible to use VirtualStringTree for a master detail grid view?

Alright I got something really tricky here... I would like to DRAW/USE Headers to a ChildNode. I think the idea is reasonable because it would look amazing to have headers in subnodes so the childnodes can be specified in a table. Is there a feature that VST has or is it not possible at all?

Thanks for your help.


1. Is there a way to use VirtualTreeView for a master / detail grid view ?

No, there is no such feature available at this time and IMHO won't be, since that would involve a very big intervention to an existing code.

2. How to create fully functional header for a child node detail grid view ?

Considering few ways, how to simulate header look and behavior for child nodes I've found useful to use nested tree views for a detail grid view. This brings you the separateness for your detail data and allows you to minimize the whole simulation to positioning of the nested tree view into a child node's rectangle.

2.1. Startup project

In the following project I'm trying to show how complicated could be implement such an easy task like the positioning of a control inside of a child node could be (without involving the original VirtualTree code). Take it just as a startup project, not as a final solution.

2.2. Known issues & limitations:

  • this project was written and tested to use only one child per root node, so don't be surprised with a behavior when you exceed this limit, because this was not designed nor even tested for
  • when a double click column resize of a main tree animates the column resize, the nested tree views are overdrawn with lines when the canvas is being scrolled by the ScrollDC function
  • to keep the VirtualTree code without changing I've overrided the method for scroll bars updating. It is used to update nested tree views bounds whenever the scrollbars needs to be updated
  • current OnExpanded implementation fires the event before the range and scroll positions are fixed, what makes the code more complicated and with a big weakness - the bounds of a detail tree view are updated after the tree is shown, what can be sometimes visible
  • 2.3. Project code

    It was written and tested in Delphi 2009 with respect to use in Delphi 7. For commented version of a next code follow this link :

    Unit1.pas

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, VirtualTrees;
    
    type
      TVTScrollBarsUpdateEvent = procedure(Sender: TBaseVirtualTree; DoRepaint: Boolean) of object;
      TVirtualStringTree = class(VirtualTrees.TVirtualStringTree)
      private
        FOnUpdateScrollBars: TVTScrollBarsUpdateEvent;
      public
        procedure UpdateScrollBars(DoRepaint: Boolean); override;
      published
        property OnUpdateScrollBars: TVTScrollBarsUpdateEvent read FOnUpdateScrollBars write FOnUpdateScrollBars;
      end;
    
    type
      PNodeSubTree = ^TNodeSubTree;
      TNodeSubTree = class
        FChildTree: TVirtualStringTree;
      end;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
        VirtualStringTree1: TVirtualStringTree;
        procedure FormCreate(Sender: TObject);
        procedure Button1Click(Sender: TObject);
        procedure VirtualStringTree1AfterAutoFitColumns(Sender: TVTHeader);
        procedure VirtualStringTree1BeforeDrawTreeLine(Sender: TBaseVirtualTree;
          Node: PVirtualNode; Level: Integer; var PosX: Integer);
        procedure VirtualStringTree1Collapsed(Sender: TBaseVirtualTree;
          Node: PVirtualNode);
        procedure VirtualStringTree1ColumnResize(Sender: TVTHeader;
          Column: TColumnIndex);
        procedure VirtualStringTree1Expanded(Sender: TBaseVirtualTree;
          Node: PVirtualNode);
        procedure VirtualStringTree1FocusChanging(Sender: TBaseVirtualTree; OldNode,
          NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex;
          var Allowed: Boolean);
        procedure VirtualStringTree1FreeNode(Sender: TBaseVirtualTree;
          Node: PVirtualNode);
        procedure VirtualStringTree1MeasureItem(Sender: TBaseVirtualTree;
          TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer);
      private
        procedure InvalidateSubTrees(Tree: TBaseVirtualTree);
        procedure ResizeSubTrees(Tree: TBaseVirtualTree);
        procedure UpdateSubTreeBounds(Tree: TBaseVirtualTree; Node: PVirtualNode);
        procedure OnUpdateScrollBars(Sender: TBaseVirtualTree; DoRepaint: Boolean);
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    { TVirtualStringTree }
    
    procedure TVirtualStringTree.UpdateScrollBars(DoRepaint: Boolean);
    begin
      inherited;
      if HandleAllocated and Assigned(FOnUpdateScrollBars) then
        FOnUpdateScrollBars(Self, DoRepaint);
    end;
    
    { TForm1 }
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ReportMemoryLeaksOnShutdown := True;
      VirtualStringTree1.NodeDataSize := SizeOf(TNodeSubTree);
      VirtualStringTree1.OnUpdateScrollBars := OnUpdateScrollBars;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      Data: PNodeSubTree;
      Node: PVirtualNode;
    begin
      Node := VirtualStringTree1.AddChild(nil);
      Node := VirtualStringTree1.AddChild(Node);
      VirtualStringTree1.InitNode(Node);
      Data := VirtualStringTree1.GetNodeData(Node);
      Data^ := TNodeSubTree.Create;
      Data^.FChildTree := TVirtualStringTree.Create(nil);
      with Data.FChildTree do
      begin
        Visible := False;
        Parent := VirtualStringTree1;
        Height := 150;
        DefaultNodeHeight := 21;
        Header.AutoSizeIndex := 0;
        Header.Font.Charset := DEFAULT_CHARSET;
        Header.Font.Color := clWindowText;
        Header.Font.Height := -11;
        Header.Font.Name := 'Tahoma';
        Header.Font.Style := [];
        Header.Height := 21;
        Header.Options := [hoColumnResize, hoDrag, hoShowSortGlyphs, hoVisible];
        TabStop := False;
        with Header.Columns.Add do
        begin
          Width := 100;
          Text := 'Header item 1';
        end;
        with Header.Columns.Add do
        begin
          Width := 100;
          Text := 'Header item 2';
        end;
      end;
    end;
    
    procedure TForm1.VirtualStringTree1AfterAutoFitColumns(Sender: TVTHeader);
    begin
      InvalidateSubTrees(Sender.Treeview);
    end;
    
    procedure TForm1.VirtualStringTree1BeforeDrawTreeLine(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Level: Integer; var PosX: Integer);
    begin
      if Level = 1 then
        PosX := 0;
    end;
    
    procedure TForm1.VirtualStringTree1Collapsed(Sender: TBaseVirtualTree;
      Node: PVirtualNode);
    var
      Data: PNodeSubTree;
    begin
      Data := VirtualStringTree1.GetNodeData(Node.FirstChild);
      if Assigned(Data^) and Assigned(Data^.FChildTree) then
        Data^.FChildTree.Visible := False;
    end;
    
    procedure TForm1.VirtualStringTree1ColumnResize(Sender: TVTHeader;
      Column: TColumnIndex);
    begin
      ResizeSubTrees(Sender.Treeview);
    end;
    
    procedure TForm1.VirtualStringTree1Expanded(Sender: TBaseVirtualTree;
      Node: PVirtualNode);
    var
      Data: PNodeSubTree;
    begin
      Data := VirtualStringTree1.GetNodeData(Node.FirstChild);
      if Assigned(Data^) and Assigned(Data^.FChildTree) then
        Data^.FChildTree.Visible := True;
    end;
    
    procedure TForm1.VirtualStringTree1FocusChanging(Sender: TBaseVirtualTree;
      OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex;
      var Allowed: Boolean);
    begin
      if Sender.GetNodeLevel(NewNode) = 1 then
      begin
        Allowed := False;
        if Sender.AbsoluteIndex(OldNode) > Sender.AbsoluteIndex(NewNode) then
          Sender.FocusedNode := Sender.GetPreviousSibling(OldNode)
        else
        if OldNode <> Sender.GetLastChild(nil) then
          Sender.FocusedNode := Sender.GetNextSibling(OldNode)
        else
          Sender.FocusedNode := OldNode;
      end;
    end;
    
    procedure TForm1.VirtualStringTree1FreeNode(Sender: TBaseVirtualTree;
      Node: PVirtualNode);
    var
      Data: PNodeSubTree;
    begin
      Data := VirtualStringTree1.GetNodeData(Node);
      if Assigned(Data^) then
      begin
        if Assigned(Data^.FChildTree) then
          Data^.FChildTree.Free;
        Data^.Free;
      end;
    end;
    
    procedure TForm1.VirtualStringTree1MeasureItem(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer);
    var
      Data: PNodeSubTree;
    begin
      if VirtualStringTree1.GetNodeLevel(Node) = 1 then
      begin
        Data := VirtualStringTree1.GetNodeData(Node);
        if Assigned(Data^) and Assigned(Data^.FChildTree) then
          NodeHeight := Data^.FChildTree.Height + 8;
      end;
    end;
    
    procedure TForm1.InvalidateSubTrees(Tree: TBaseVirtualTree);
    var
      Data: PNodeSubTree;
      Node: PVirtualNode;
    begin
      Node := Tree.GetFirst;
      while Assigned(Node) do
      begin
        if Tree.HasChildren[Node] then
        begin
          Data := Tree.GetNodeData(Node.FirstChild);
          if Assigned(Data^) and Assigned(Data^.FChildTree) then
          begin
            Data^.FChildTree.Header.Invalidate(nil);
            Data^.FChildTree.Invalidate;
          end;
        end;
        Node := Tree.GetNextSibling(Node);
      end;
    end;
    
    procedure TForm1.ResizeSubTrees(Tree: TBaseVirtualTree);
    var
      Node: PVirtualNode;
    begin
      Node := Tree.GetFirst;
      while Assigned(Node) do
      begin
        if Tree.HasChildren[Node] then
          UpdateSubTreeBounds(Tree, Node.FirstChild);
        Node := Tree.GetNextSibling(Node);
      end;
    end;
    
    procedure TForm1.UpdateSubTreeBounds(Tree: TBaseVirtualTree; Node: PVirtualNode);
    var
      R: TRect;
      Data: PNodeSubTree;
    begin
      if Assigned(Node) then
      begin
        Data := Tree.GetNodeData(Node);
        if Assigned(Data^) and Assigned(Data^.FChildTree) and
          Data^.FChildTree.Visible then
        begin
          R := Tree.GetDisplayRect(Node, -1, False, True);
          R.Left := R.Left + (Tree as TVirtualStringTree).Indent;
          R.Top := R.Top + 4;
          R.Right := R.Right - 8;
          R.Bottom := R.Bottom - 4;
          Data^.FChildTree.BoundsRect := R;
        end;
      end;
    end;
    
    procedure TForm1.OnUpdateScrollBars(Sender: TBaseVirtualTree; DoRepaint: Boolean);
    begin
      ResizeSubTrees(Sender);
    end;
    
    end.
    

    Unit1.dfm

    object Form1: TForm1
      Left = 0
      Top = 0
      Caption = 'Form1'
      ClientHeight = 282
      ClientWidth = 468
      Color = clBtnFace
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -11
      Font.Name = 'Tahoma'
      Font.Style = []
      OldCreateOrder = False
      OnCreate = FormCreate
      DesignSize = (
        468
        282)
      PixelsPerInch = 96
      TextHeight = 13
      object VirtualStringTree1: TVirtualStringTree
        Left = 8
        Top = 8
        Width = 371
        Height = 266
        Anchors = [akLeft, akTop, akRight, akBottom]
        Header.AutoSizeIndex = 0
        Header.Font.Charset = DEFAULT_CHARSET
        Header.Font.Color = clWindowText
        Header.Font.Height = -11
        Header.Font.Name = 'Tahoma'
        Header.Font.Style = []
        Header.Height = 21
        Header.Options = [hoColumnResize, hoDblClickResize, hoDrag, hoShowSortGlyphs, hoVisible]
        TabOrder = 0
        TreeOptions.MiscOptions = [toVariableNodeHeight]
        OnAfterAutoFitColumns = VirtualStringTree1AfterAutoFitColumns
        OnBeforeDrawTreeLine = VirtualStringTree1BeforeDrawTreeLine
        OnCollapsed = VirtualStringTree1Collapsed
        OnColumnResize = VirtualStringTree1ColumnResize
        OnExpanded = VirtualStringTree1Expanded
        OnFocusChanging = VirtualStringTree1FocusChanging
        OnFreeNode = VirtualStringTree1FreeNode
        OnMeasureItem = VirtualStringTree1MeasureItem
        ExplicitWidth = 581
        ExplicitHeight = 326
        Columns = <
          item
            Position = 0
            Width = 75
            WideText = 'Column 1'
          end
          item
            Position = 1
            Width = 75
            WideText = 'Column 2'
          end
          item
            Position = 2
            Width = 75
            WideText = 'Column 3'
          end>
      end
      object Button1: TButton
        Left = 385
        Top = 8
        Width = 75
        Height = 25
        Anchors = [akTop, akRight]
        Caption = 'Button1'
        TabOrder = 1
        OnClick = Button1Click
        ExplicitLeft = 595
      end
    end
    

    2.4. Screenshot

    在这里输入图像描述

    链接地址: http://www.djcxy.com/p/35028.html

    上一篇: VirtualStringTree锁定列0

    下一篇: 是否有可能使用VirtualStringTree作为主细节网格视图?