3

現在VST(VirtualStringTree)を使用してデータを保存しているので、プログラムのデータ構造を本当に書き直す必要があることに気付きました(今ではありませんが、締め切りは月曜日です)。

私が達成したいのは、連絡先リストの構造です。ルートノードはカテゴリであり、子は連絡先です。全部で2つのレベルがあります。

ただし、複数のカテゴリに表示するには連絡先が必要ですが、同期する必要があります。特にチェックステート

現在、同期を維持するために、ツリー全体をループして、変更されたばかりのIDと同じIDを持つノードを見つけます。ただし、ノードの数が非常に多い場合、これを行うのは非常に遅くなります。

それで、私は考えました:複数のカテゴリで連絡先オブジェクトの1つのインスタンスを表示することは可能でしょうか?

注:正直なところ、私はこの用語に100%精通していません。インスタンスとは、1つのオブジェクト(またはレコード)であるため、ツリー全体を調べて同じIDの連絡先オブジェクトを見つける必要はありません。

次に例を示します。

例

ご覧のとおり、ToddHirschはテストカテゴリとすべての連絡先に表示されます。しかし、舞台裏では、これらは2つのPVirtualNodeであるため、ノードの1つ(CheckStateなど)のプロパティ、またはノードのデータレコード/クラスの何かを変更すると、2つのノードは同期されません。そして現在、それらを同期できる唯一の方法は、ツリーをループして、同じ連絡先を収容するすべてのノードを見つけ、それらとそのデータに変更を適用することです。

要約すると、私が探しているのは、1つのオブジェクト/レコードを使用して、ツリー内の複数のカテゴリに表示する方法です。1つのノードがチェックされるたびに、同じContactオブジェクトを格納する他のすべてのノードもチェックされます。

ここで意味がありますか?

4

1 に答える 1

9

もちろんできます。ノードとデータを頭の中で分離する必要があります。TVirtualStringTree のノードはデータを保持する必要はありません。単にデータが存在するインスタンスを指すために使用できます。もちろん、2 つのノードを同じオブジェクト インスタンスにポイントすることもできます。

TPerson のリストがあり、それぞれの人を異なるノードに表示したいツリーがあるとします。次に、ノードに使用するレコードを次のように宣言します。

TNodeRecord = record
  ... // anything else you may need  or want
  DataObject: TObject;
  ...
end;

ノードが初期化されるコードでは、次のようにします。

PNodeRecord.DataObject := PersonList[SomeIndex];

それが要点です。上に示したように、一般的な NodeRecord が必要な場合は、さまざまな Get... メソッドで使用するために、適切なクラスにキャストし直す必要があります。もちろん、ツリーごとに特定のレコードを作成することもできます。この場合、DataObject をツリーに表示する特定のタイプのクラスとして宣言します。唯一の欠点は、そのクラスのオブジェクトの情報を表示するようにツリーを制限することです。

どこかに横たわっているより精巧な例が必要です。見つけたら、この回答に追加します。


ツリーで使用するレコードを宣言します。

RTreeData = record
  CDO: TCustomDomainObject;
end;
PTreeData = ^RTreeData;

TCustomDomainObject は、すべてのドメイン情報の基本クラスです。次のように宣言されています。

TCustomDomainObject = class(TObject)
private
  FList: TObjectList;
protected
  function GetDisplayString: string; virtual;
  function GetCount: Cardinal;
  function GetCDO(aIdx: Cardinal): TCustomDomainObject;
public
  constructor Create; overload;
  destructor Destroy; override;

  function Add(aCDO: TCustomDomainObject): TCustomDomainObject;

  property DisplayString: string read GetDisplayString;
  property Count: Cardinal read GetCount;
  property CDO[aIdx: Cardinal]: TCustomDomainObject read GetCDO;
end;

このクラスは、他の TCustomDomainObject インスタンスのリストを保持できるように設定されていることに注意してください。ツリーを表示するフォームに次を追加します。

TForm1 = class(TForm)
  ...
private
  FIsLoading: Boolean;
  FCDO: TCustomDomainObject;
protected
  procedure ShowColumnHeaders;
  procedure ShowDomainObject(aCDO, aParent: TCustomDomainObject);
  procedure ShowDomainObjects(aCDO, aParent: TCustomDomainObject);

  procedure AddColumnHeaders(aColumns: TVirtualTreeColumns); virtual;
  function GetColumnText(aCDO: TCustomDomainObject; aColumn: TColumnIndex;
    var aCellText: string): Boolean;
protected
  property CDO: TCustomDomainObject read FCDO write FCDO;
public
  procedure Load(aCDO: TCustomDomainObject);
  ...
end;  

Load メソッドがすべての始まりです。

procedure TForm1.Load(aCDO: TCustomDomainObject);
begin
  FIsLoading := True;
  VirtualStringTree1.BeginUpdate;
  try
    if Assigned(CDO) then begin
      VirtualStringTree1.Header.Columns.Clear;
      VirtualStringTree1.Clear;
    end;
    CDO := aCDO;
    if Assigned(CDO) then begin
      ShowColumnHeaders;
      ShowDomainObjects(CDO, nil);
    end;
  finally
    VirtualStringTree1.EndUpdate;
    FIsLoading := False;
  end;
end;

実際に行うことは、フォームをクリアして、ほとんどの場合、他の CustomDomainObjects を含むリストである新しい CustomDomainObject 用に設定することだけです。

ShowColumnHeaders メソッドは、文字列ツリーの列ヘッダーを設定し、列の数に応じてヘッダー オプションを調整します。

procedure TForm1.ShowColumnHeaders;
begin
  AddColumnHeaders(VirtualStringTree1.Header.Columns);
  if VirtualStringTree1.Header.Columns.Count > 0 then begin
    VirtualStringTree1.Header.Options := VirtualStringTree1.Header.Options
      + [hoVisible];
  end;
end;

procedure TForm1.AddColumnHeaders(aColumns: TVirtualTreeColumns);
var
  Col: TVirtualTreeColumn;
begin
  Col := aColumns.Add;
  Col.Text := 'Breed(Group)';
  Col.Width := 200;

  Col := aColumns.Add;
  Col.Text := 'Average Age';
  Col.Width := 100;
  Col.Alignment := taRightJustify;

  Col := aColumns.Add;
  Col.Text := 'CDO.Count';
  Col.Width := 100;
  Col.Alignment := taRightJustify;
end;

AddColumnHeaders は、このフォームをツリーで情報を表示する他のフォームのベースとして使用できるようにするために分離されました。

ShowDomainObjects は、ツリー全体がロードされるメソッドのように見えます。そうではありません。結局のところ、仮想ツリーを扱っています。したがって、仮想ツリーにノードの数を伝えるだけで済みます。

procedure TForm1.ShowDomainObjects(aCDO, aParent: TCustomDomainObject);
begin
  if Assigned(aCDO) then begin
    VirtualStringTree1.RootNodeCount := aCDO.Count;
  end else begin
    VirtualStringTree1.RootNodeCount := 0;
  end;
end;

これでほとんどの設定が完了し、さまざまな VirtualStringTree イベントを実装するだけですべてを開始できます。実装する最初のイベントは OnGetText イベントです。

procedure TForm1.VirtualStringTree1GetText(Sender: TBaseVirtualTree; Node:
    PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText:
    string);
var
  NodeData: ^RTreeData;
begin
  NodeData := Sender.GetNodeData(Node);
  if GetColumnText(NodeData.CDO, Column, {var}CellText) then
  else begin
    if Assigned(NodeData.CDO) then begin
      case Column of
        -1, 0: CellText := NodeData.CDO.DisplayString;
      end;
    end;
  end;
end;

VirtualStringTree から NodeData を取得し、取得した CustomDomainObject インスタンスを使用してそのテキストを取得します。これには GetColumnText 関数を使用します。これは、このフォームをツリーを表示する他のフォームのベースとして使用できるようにするために行われました。そのルートに進むときは、このメソッドを仮想として宣言し、子孫のフォームでオーバーライドします。この例では、次のように単純に実装されています。

function TForm1.GetColumnText(aCDO: TCustomDomainObject; aColumn: TColumnIndex;
  var aCellText: string): Boolean;
begin
  if Assigned(aCDO) then begin
    case aColumn of
      -1, 0: begin
        aCellText := aCDO.DisplayString;
      end;
      1: begin
        if aCDO.InheritsFrom(TDogBreed) then begin
          aCellText := IntToStr(TDogBreed(aCDO).AverageAge);
        end;
      end;
      2: begin
        aCellText := IntToStr(aCDO.Count);
      end;
    else
//      aCellText := '';
    end;
    Result := True;
  end else begin
    Result := False;
  end;
end;

ノード レコードから CustomDomainObject インスタンスを使用する方法を VirtualStringTree に指示したので、もちろんメイン CDO のインスタンスをツリーのノードにリンクする必要があります。これは OnInitNode イベントで行われます。

procedure TForm1.VirtualStringTree1InitNode(Sender: TBaseVirtualTree;
    ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
  ParentNodeData: ^RTreeData;
  ParentNodeCDO: TCustomDomainObject;
  NodeData: ^RTreeData;
begin
  if Assigned(ParentNode) then begin
    ParentNodeData := VirtualStringTree1.GetNodeData(ParentNode);
    ParentNodeCDO := ParentNodeData.CDO;
  end else begin
    ParentNodeCDO := CDO;
  end;

  NodeData := VirtualStringTree1.GetNodeData(Node);
  if Assigned(NodeData.CDO) then begin
    // CDO was already set, for example when added through AddDomainObject.
  end else begin
    if Assigned(ParentNodeCDO) then begin
      if ParentNodeCDO.Count > Node.Index then begin
        NodeData.CDO := ParentNodeCDO.CDO[Node.Index];
        if NodeData.CDO.Count > 0 then begin
          InitialStates := InitialStates + [ivsHasChildren];
        end;
      end;
    end;
  end;
  Sender.CheckState[Node] := csUncheckedNormal;
end;

CustomDomainObject は他の CustomDomainObjects のリストを持つことができるため、lsit の Count がゼロより大きい場合に HasChildren を含めるようにノードの InitialStates も設定します。これは、ユーザーがツリーのプラス記号をクリックしたときに呼び出される OnInitChildren イベントも実装する必要があることを意味します。繰り返しますが、そこで行う必要があるのは、準備する必要があるノードの数をツリーに伝えることだけです。

procedure TForm1.VirtualStringTree1InitChildren(Sender: TBaseVirtualTree; Node:
    PVirtualNode; var ChildCount: Cardinal);
var
  NodeData: ^RTreeData;
begin
  ChildCount := 0;

  NodeData := Sender.GetNodeData(Node);
  if Assigned(NodeData.CDO) then begin
    ChildCount := NodeData.CDO.Count;
  end;
end;

以上です!

簡単なリストの例を示したように、どのデータ インスタンスをどのノードにリンクする必要があるかを把握する必要がありますが、どこでそれを行う必要があるかについては、かなりのアイデアが必要です: OnInitNode イベントを設定する場所選択した CDO インスタンスを指すノード レコードの CDO メンバー。

于 2011-04-09T10:04:02.137 に答える