【问题标题】:Switching from ListView to VirtualStringTree从 ListView 切换到 VirtualStringTree
【发布时间】:2011-01-03 17:38:56
【问题描述】:

由于速度差异巨大,我正在尝试使用 VirtualStringTree 而不是 Listview 来构建我的项目。问题是,即使在查看了演示之后,我也无法确切地弄清楚我将如何将它用作 ListView。比如,添加、删除和基本上只是使用 ListView 项目非常容易,但是当我查看 VT 时,它变得几乎太复杂了。

我正在寻找的只是一个看起来像 ListView、带有子项等的 VT。

以下是一些使用 ListView 的例程,我想将它们与 VT 一起使用(这只是一个伪示例:

procedure Add;
begin
  with ListView.Items.Add do
    Begin
      Caption := EditCaption.Text;
      SubItems.Add(EditSubItem.Text):
    End;

end;

Procedure ReadItem(I : Integer);
begin

   ShowMessage(ListView.Items[I].Caption);
   ShowMessage(ListView.Items[I].SubItems[0]);

end;

当然还有Delete功能,但既然是1行,我就不费心了:P

谁能把上面的例子翻译成使用 ListView 风格的 VT?

谢谢!

【问题讨论】:

    标签: delphi listview virtualtreeview


    【解决方案1】:

    为什么不在虚拟模式下使用列表视图?这看起来和感觉都很好,而且性能很好。

    Delphi TListView 控件是 Windows 列表视图组件的包装器。在其默认操作模式下,列表数据的副本会从您的应用程序传输到 Windows 控件,这很慢。

    在 Windows 术语中,此替代方法称为虚拟列表视图。您的应用不会将数据传递给 Windows 控件。相反,当控件需要显示数据时,它会要求您的应用仅提供所需的数据。

    Delphi TListView 控件通过使用 OwnerData 属性公开虚拟列表视图。您将不得不重新编写列表视图代码,但这并不难。

    我还提供了指向另一个 question 的链接,该链接涵盖了类似的领域。相当奇怪的是,即使该问题是关于列表视图控件的,该问题的公认答案也谈到了列表框。

    【讨论】:

    • +1。我同意。大多数情况下,不需要放弃本机控件。 Microsoft Windows 操作系统是一个非常成熟的产品。
    • 我也同意,如果速度不是问题,最好使用本机控件,否则 VirtualStringTree 是你最好的伙伴(:
    • @dorin 的虚拟列表视图速度不够快?
    • @david 坦率地说,我从来没有在虚拟模式下使用过列表视图,我更喜欢 VirtualStringTree,因为它非常灵活,但是我通常更喜欢使用标准控件而不是第三方来实现未来的兼容性,但我很少忽略这一点超过生产力...
    • @Dorin 我刚刚编写了我的第一个虚拟列表视图!它毫不费力地显示了 1000 万行,坦率地说,这正是我所期望的。无论如何,我确定 VirtualStringTree 很好,但我认为 TListView 在虚拟模式下没有性能问题。
    【解决方案2】:

    使用 VirtualStringTree,它比简单的 TListView 稍微复杂一些,但这是我不久前创建的一个非常简单的教程,介绍了如何使用 VirtualStringTree http://www.youtube.com/watch?v=o6FpUJhEeoY 希望对您有所帮助,干杯!

    【讨论】:

    • Youtube 链接已失效
    【解决方案3】:

    只需使用您的普通 TListView,但在虚拟模式下使用它。

    其实很简单:

    1. OwnerData 属性设置为true
    2. 实现OnData 事件处理程序。

    显示 3 行的简单列表的示例实现:

    Type TMyItem=record
      Item:String;
      SubItem:String;
    end;
    
    var Items:Array of TMyItem;
    
    // set up some in-memory dataset.. choose your own layout
    SetLength(Items,3);
    Items[0].Item := 'foo1';
    Items[0].SubItem := 'bar1';
    
    Items[1].Item := 'foo2';
    Items[1].SubItem := 'bar2';
    
    Items[2].Item := 'foo3';
    Items[2].SubItem := 'bar3';
    
    // tell ListView1 how many items there are
    ListView1.Items.Count := Length(Items); 
    
    procedure TfrmMain.ListView1Data(Sender: TObject; Item: TListItem);
    begin
      Item.Caption := IntToStr(Item.Index);
      Item.SubItems.Add( MyArray[Item.Index] );
      Item.SubItems.Add( UpperCase(MyArray[Item.Index]) );
    end;
    
    // Updating a value:
    Items[1].Item := 'bzzz';
    ListView1.Update;
    

    就是这样!

    注意事项:

    1. 您不再调用 ListView1.Items.Add()。
    2. 您需要将自己的数据列表保存在内存中的某个位置,或者实时获取数据,因此您无法再将数据“存储”在列表视图中。
    3. 您需要设置 items.count 属性,否则您将看不到任何内容。
    4. 如果发生变化,请调用 ListView1.Update()。

    【讨论】:

    • 而不是 ListView1.Update,Listview1.Repaint 在 Delphi 10.3.3 中为我工作。非常感谢您提供这个有用的答案。
    【解决方案4】:
    procedure Add;
    Var
      Data: PLogData;
      XNode: PVirtualNode;
    begin
      with vst do
        Begin
          XNode := AddChild(nil);
          ValidateNode(XNode, False);
          Data := GetNodeData(Xnode); 
          Data^.Name:= EditCaption.Text;
          Data^.Msg := EditSubItem.Text;
        End;
    
    end;
    
    Procedure ReadItem(I : Integer);
    var
      Data: PLogData;
    begin
      if not Assigned(vst.FocusedNode) then Exit;
    
      Data := vst.GetNodeData(vst.FocusedNode);
      ShowMessage(Data^.Name);
      ShowMessage(Data^.Msg);
    
    end;
    

    基本上这就是您需要做的,但是 VirtualStringTree 有/需要很多其他的东西一起工作才能完全理解它。一旦你“得到它”,VST 就变得简单而强大。以下网页将为您提供帮助:http://wiki.freepascal.org/VirtualTreeview_Example_for_Lazarus

    下面我将添加更多用于简单 VST 日志显示的代码。我将所有代码保存在数据模块中,只需使用过程 Log 来显示信息并将您的 FormMain.vstLog 更改为您的...

    unit udmVstLog;
    
    interface
    
    uses
      SysUtils, Windows, Forms, Classes, Graphics,
      VirtualTrees, ActnList, Dialogs, ExtDlgs;
    
    type
      PLogData = ^TLogData;
      TLogData = record
        IsErr   : Boolean;
        Name: String;
        Msg : String;
      end;
    
    type
      TdmVstLog = class(TDataModule)
        actlst1: TActionList;
        actClear: TAction;
        actSave: TAction;
        actCopyLine2Mem: TAction;
        sdlgLog: TSaveTextFileDialog;
        procedure DataModuleCreate(Sender: TObject);
        procedure actClearExecute(Sender: TObject);
        procedure actSaveExecute(Sender: TObject);
        procedure actCopyLine2MemExecute(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
        procedure VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
        procedure VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
          Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
        procedure VSTPaintText(Sender: TBaseVirtualTree;
          const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
          TextType: TVSTTextType);
      end;
    
      procedure Log(aIsErr: Boolean; AName, AMsg: string); overload;
      procedure Log(AName, AMsg: string); overload;
      procedure Log(AMsg: string); overload;
    
    var
      dmVstLog: TdmVstLog;
    
    implementation
    
    uses uFormMain, ClipBrd;
    
    {$R *.dfm}
    procedure Log(aIsErr: Boolean; AName, AMsg: string);
    Var
      Data: PLogData;
      XNode: PVirtualNode;
    begin
      XNode:=FormMain.vstLog.AddChild(nil);
      FormMain.vstLog.ValidateNode(XNode, False);
      Data := FormMain.vstLog.GetNodeData(Xnode);
      Data^.IsErr := aIsErr;
      if aIsErr then
        Data^.Name:= DateTimeToStr(Now) + ' ERROR ' + AName
      else
        Data^.Name:= DateTimeToStr(Now) + ' INFO ' + AName;
      Data^.Msg:= AMsg;
    end;
    
    procedure Log(AName, AMsg: string);
    begin
      Log(False,AName,AMsg);
    end;
    
    procedure Log(AMsg: string);
    begin
      Log(False,'',AMsg);
    end;
    
    
    
    // VirtualStringTree Events defined here
    procedure TdmVstLog.VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
    var
      Data: PLogData;
    begin
      Data:=Sender.GetNodeData(Node);
      Finalize(Data^);
    end;
    
    procedure TdmVstLog.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
     Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
    var
      Data: PLogData;
    begin
      Data := Sender.GetNodeData(Node);
      case Column of
        0: CellText := Data^.Name + ' - '+ Data^.Msg;
      end;
    end;
    
    procedure TdmVstLog.VSTPaintText(Sender: TBaseVirtualTree;
      const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
      TextType: TVSTTextType);
    Var
      Data: PLogData;
    begin
      Data := Sender.GetNodeData(Node);
    
      if Data^.IsErr then
        TargetCanvas.Font.Color:=clRed;
    
    end;
    
    //PopUpMenu Actions defined here!
    procedure TdmVstLog.actClearExecute(Sender: TObject);
    begin
      FormMain.vstLog.Clear;
    end;
    
    procedure TdmVstLog.actCopyLine2MemExecute(Sender: TObject);
    var
      Data: PLogData;
    begin
      if not Assigned(FormMain.vstLog.FocusedNode) then Exit;
    
      Data := FormMain.vstLog.GetNodeData(FormMain.vstLog.FocusedNode);
      ClipBoard.AsText := Data^.Name + ' - ' + Data^.Msg;
    end;
    
    procedure TdmVstLog.actSaveExecute(Sender: TObject);
    Var
      XNode: PVirtualNode;
      Data: PLogData;
      ts: TStringList;
    begin
      If FormMain.vstLog.GetFirst = nil then Exit;
      XNode:=nil;
      if sdlgLog.Execute then begin
        ts:= TStringList.create;
        try
          Repeat
            if XNode = nil then XNode:=FormMain.vstLog.GetFirst Else XNode:=FormMain.vstLog.GetNext(XNode);
            Data:=FormMain.vstLog.GetNodeData(XNode);
            ts.Add(Data^.Name + ' - '+ Data^.Msg);
          Until XNode = FormMain.vstLog.GetLast();
          ts.SaveToFile(sdlgLog.FileName);
        finally
          ts.Free;
        end;
      end;
    
    end;
    
    // Datamodule Events defined here
    procedure TdmVstLog.DataModuleCreate(Sender: TObject);
    begin
      with FormMain.vstLog do begin
        NodeDataSize := SizeOf(TLogData);
        OnFreeNode := VSTFreeNode;
        OnGetText := VSTGetText;
        OnPaintText := VSTPaintText;
      end;
    end;
    
    end.
    

    ...

    procedure RemoveSelectedNodes(vst:TVirtualStringTree);
    begin
      if vst.SelectedCount = 0 then Exit;
      vst.BeginUpdate;
      vst.DeleteSelectedNodes;
      vst.EndUpdate;
    end;
    
    procedure RemoveAllNodes(vst:TVirtualStringTree);
    begin
      vst.BeginUpdate;
      vst.Clear;
      vst.EndUpdate;
    end;
    

    【讨论】:

    • 虽然你的代码可以做一些 try finally 的,我讨厌 with 声明,+1 实际回答了这个问题。
    • 我挖掘了这段代码,它来自我创建的第一个项目之一,当 VirtualStringTree 组件最终“点击”并且我理解它时。远非完美,但我了解海报的内容。从组件中可以学到很多东西,而我远不是这方面的专家……哦,我也尽量远离 WITH 语句,但我尝试将他的代码用作模板
    • 是的,我知道,我经历了同样的过程。确实挖掘了一个示例,但它过于嵌入框架中,无法在此处发布。
    • 这回答了我的问题,但我想问一下,这个“记录”究竟是如何工作的?我也没有理解“^”的作用,因为我似乎找不到解释它的文章。关于指定代码的速成课程也很好,比如“数据^。”? :) 谢谢 :)
    【解决方案5】:

    获取VT Contributions 包并查看虚拟字符串树的一些后代。那是在里面。我没有在项目中使用它们,但它们似乎使 Virtual String Tree 更易于使用。


    这仍然是我的入门入门:

    我在使用 Virtual String Tree 之后发现,您可以充分利用它的唯一方法是实现 init 节点/子函数并设置根节点数,这与您使用列表非常相似查看 ownerdraw := true。

    用 VirtualStringTree 做事很容易,你只需要实现获取文本函数和节点大小函数(将它设置为你想用作树后面数据的任何记录的大小)

    我发现它几乎总是更容易做到 TVirtualTreeNodeRecordData = record Data : TVirtualTreeNodeData; end

    并在初始化函数上创建数据对象。它为您创建指针,但您需要释放对象(同样,使用另一个删除节点回调)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-02
      • 1970-01-01
      • 2013-05-20
      • 1970-01-01
      • 1970-01-01
      • 2011-09-28
      • 1970-01-01
      • 2018-07-19
      相关资源
      最近更新 更多