個別のデータ構造のコードで最初に目にする問題は、カテゴリがusersを所有するという誤った設定になっていることです。つまり、同じ物理的な人物が 2 つのカテゴリに属している場合、データ構造では、2 人の完全に別々の人物であるかのように表示されます。その決定を後悔しながら、プロジェクトの生涯を過ごすことになります。
ユーザーのリストと、カテゴリのリストが必要です。ユーザーには、それが属するカテゴリのリストを含めることができます。または、カテゴリには、それに属するユーザーのリストを含めることができます。多分両方。
type
TUser = class;
TCategory = class;
TContactList = class
private
FUsers: TObjectList<TUser>;
FCategories: TObjectList<TCategory>;
end;
TUser = class
private
FCategories: TObjectList<TCategory>;
public
constructor Create(const DisplayName: string; SkypeID: Integer);
property DisplayName: string;
property SkypeID: Integer;
property Categories: TObjectList<TCategory> read FCategories;
end;
TCategory = class
private
FUsers: TObjectList<TUser>;
public
constructor Create(const Name: string; ID: Integer);
property Name: string;
property ID: Integer;
property Users: TObjectList<TUser> read FUsers;
end;
constructor TContactList.Create;
begin
// The contact list is the single master list of all contacts; it
// owns the user objects, so set OwnsObjects = True
FUsers := TObjectList<TUser>.Create(True);
FCategories := TObjectList<TCategory>.Create(True);
end;
constructor TUser.Create;
begin
// A user does not own its categories; set OwnsObjects = False
FCategories := TObjectList<TCategory>.Create(False);
end;
constructor TCategory.Create;
begin
// A category does not own its members; set OwnsObjects = False
FUsers := TObjectList<TUser>.Create(False);
end;
このコードがツリー コントロールについて言及していないことに注目してください。連絡先リストはツリー コントロールを所有していません。もしそうなら、あなたは数ヶ月前に始めたところ、つまり複数のツリー コントロールでユーザーを表示する方法に問題があったところに戻っていることになります。また、ユーザーは複数のカテゴリに表示される可能性がありますが、そのユーザーを所有する単一のカテゴリはありません。代わりに、連絡先リストがユーザーを所有し、それらを参照するためのアクセス許可をカテゴリに付与し、その逆も同様です。
このプロジェクトを開始したときの最初の問題の 1 つは、ツリー コントロール内の重複要素を検出する方法でした。ツリー コントロールがまったくないため、この問題ははるかに簡単になります。フラットなユーザー リストで重複を見つけるだけです。最初にそのリストに重複を追加しないと、より複雑な GUI コントロールで重複を見つけることを心配する必要がなくなります。
データ構造がツリーではないことに注意してください。これは 2 つのリストであり、リスト内の各項目は反対側のリストから任意の数の項目を参照できます。その意味では、それは実際にはグラフです。データをツリーとして表示するだけです。そうしないと視覚化するのが難しいからです。
ツリー コントロールから独立したデータ構造ができたので、ツリーを表示するデータにどのようにリンクさせるのでしょうか。各ノードは、それが表すTUser
またはへの参照を保持する必要があります。TCategory
ノードのデータ レコードは次のように定義できます。
type
PNodeData = ^TNodeData;
TNodeData = record
case Integer of
0: Obj: TObject;
1: User: TUser;
2: Category: TCategory;
end;
OnGetText
これを使用して、次のようにツリーのイベントを実装できます。
procedure TJeffForm.TreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
Data: PNodeData;
begin
if TextType = ttStatic then
Exit;
Data := Sender.GetNodeData(Node);
Assert(Assigned(Data), 'Node wasn''t initialized properly');
Assert(Assigned(Data.Obj), 'Node has no object');
if Data.Obj is TUser then
CellText := Data.User.DisplayName
else if Data.Obj is TCategory then
CellText := Data.Category.Name
else
CellText := Format('Unknown node type %s', [Data.Obj.ClassName]);
end;
つまり、各ノードにはユーザーまたはカテゴリが含まれます。配列の最初の要素には、これらの型の 1 つである値が含まれますが、どちらになるかは事前にわからないため、いずれかとして安全に使用できる 3 番目の型がありますTObject
。AddNewNode
ノードが完全に初期化される前であっても、を呼び出すときに設定できるフィールドであるため、レコードの最初のフィールドに必要なデータを含めることが重要です。
長いノード作成時間を回避する秘訣の 1 つは、不要なノードを作成しないようにすることです。最上位ノードが折りたたまれている場合、実際にその子を作成する必要はありません。最上位ノードに子があることを示すフラグを設定するだけで、「+」ボタンで正しくペイントされます。ユーザーが後でノードを展開するためにボタンをクリックすると、ツリー コントロールはいくつの子を持つかを尋ね、その時点でそれらを作成します。その場合でも、すぐにペイントする必要があるノードのみを初期化します。必要になるまで作業を遅らせます。100 万の連絡先を持っている人は、一度にすべての連絡先を見たいとは思わないでしょう。そのため、GUI コントロールに何百万もの項目を作成する必要はありません。
プログラムが起動したら、最初にユーザー リストとカテゴリ リストをロードし、ツリーのカテゴリ カウントを設定するだけです。
Tree.RootNodeCount := ContactList.Categories.Count;
それで全部です。
ツリーのイベントは、初期化フェーズの残りを処理します。カテゴリ ノードの一部を最初から展開したい場合は、それらを展開するだけです。ツリーのイベントは、各ノードが持つ子の数を尋ねます。そのイベントを実装して答えることができます。ツリーがそれらのノードを作成すると、それらを初期化する方法を尋ねられ、その時点で対応するユーザーまたはカテゴリ オブジェクトをノードに割り当てることができます。さらに情報が必要な場合は、ツリーが通知します。求められる以上のものを与える必要はありません。