Delphi の仮想ツリービューでの高速スクロール
-
19-09-2019 - |
質問
[これは以前に投稿された質問の更新版です。以前のタイトルは Delphi の仮想ツリービューでのインデックスによるノードの選択.]
一日の大半を終えた後は、仮想ツリービュー コンポーネント (強力だが複雑) がシンプルな 2 つのテーブルのデータ認識方式で動作するようになったと思います。
ここでは、単純にトップレベル ノードの 1,512 番目 (たとえば) を選択しようとしています。最初の最上位ノードを取得し、ループ内で GetNextSibling 1,511 を呼び出す以外にこれを行う方法は見つかりません。
これは不必要に関与しているように思えます。もっと簡単な方法はありますか?
アップデート
ツリー内のノードを初期化するにはデータベースにアクセスする必要があるため、起動時にすべてのノードを初期化することは現実的ではありません。ユーザーがまだレコードを選択していないフォームから開始しても問題ありません。ユーザーがツリーをスクロールすると、現在のウィンドウをツリーに表示するのに十分なノードが追加され、パフォーマンスは良好になります。
ユーザーがデータベース レコードがすでに選択された状態でダイアログ モードでフォームを開始する場合、ユーザーがフォームを表示する前にツリーをそのノードまで進める必要があります。これが問題となるのは、レコードがツリーの終わりに近づいている場合、最初のノードからツリーをたどるのに 10 秒かかる可能性があるためです。GetNextSibling() を実行できるたびに、それらのノードの大部分がユーザーに表示されない場合でも、ノードが初期化されます。これらのノードの初期化は、ユーザーに表示されるまで延期したいと考えています。
レコードを選択せずにツリーを開き、垂直スクロール バーを使用して 1 回の操作でツリーの中央に移動すると、正しいノードが表示されるため、もっと良い方法があるはずだとわかっています。 スキップしたノードを初期化する必要はありません.
これは、レコードを選択した状態でツリーを開いたときに実現したい効果です。行きたいノードのインデックスはわかっていますが、インデックスでそこに到達できない場合は、いくつかのノードを前後にジャンプできると仮定して、ツリー上で二分探索を行うことができます (ノードに直接スクロールするのと似ています)。木の真ん中)。
あるいは、グリッドをトラバースする際に中間ノードを初期化しないままにする、ツリー ビューに対して行うことができる状態設定があるかもしれません。Begin/End Updateを試してみましたが、うまくいかないようです。
解決
初期化せずにノードの兄弟を取得するには、単に NextSibling
ポインタ (宣言を参照) TVirtualNode
).
他のヒント
ツリー コントロールは、コンピューター サイエンスの授業で学ぶ古典的なツリーとまったく同じように構造化されています。ツリーのルートから 1512 番目の子に到達する唯一の方法は、リンクを 1 つずつたどることです。自分で行う場合でも、ツリー コントロールのメソッドを使用する場合でも、その方法で行う必要があります。コントロール自体には何も提供されていないので、この関数を使用できます。
function GetNthNextSibling(Node: PVirtualNode; N: Cardinal;
Tree: TBaseVirtualTree = nil): PVirtualNode;
begin
if not Assigned(Tree) then
Tree := TreeFromNode(Node);
Result := Node;
while Assigned(Result) and (N > 0) do begin
Dec(N);
Result := Tree.GetNextSibling(Result);
end;
end;
このようなことが頻繁にある場合は、自分自身をインデックスにするとよいでしょう。の配列を作成するのと同じくらい簡単かもしれません PVirtualNode
ポインターを作成し、その中に最上位の値をすべて格納するので、そこから 1512 番目の値を読み取るだけで済みます。ツリー コントロールはそのようなデータ構造自体を必要としないため、データ構造を維持しません。
どうかを再考することもできます。 あなた そのようなデータ構造が必要です。あなたは 本当に そのようなインデックスによってノードにアクセスする必要がありますか?あるいは、代わりに PVirtualNode
ポインターなので、ツリー内の残りのノードに対する相対的な位置は重要ではなくなります (つまり、たとえば、必要なノードへの参照を失わずにポインターを並べ替えることができるということです)。
アップデートには次のように書きます。
レコードを選択せずにツリーを開き、垂直スクロール バーを使用して 1 回の操作でツリーの中央に移動すると、正しいノードが表示されるため、もっと良い方法があるはずだとわかっています。 スキップしたノードを初期化する必要はありません.
垂直スクロールによってクライアント位置 0 に表示される論理 Y 座標が変更されるため、ここには違いがあります。コントロールはスクロールバーの位置とスクロール範囲からオフセットを計算し、次にどのノードがコントロールの上部に表示されるかを計算します。ノードは、スクロールして表示された領域をペイントする必要がある場合にのみ再度初期化されます。
ノードの Y 座標がある場合は、呼び出してノード ポインターを取得できます。
function TBaseVirtualTree.GetNodeAt(X, Y: Integer; Relative: Boolean;
var NodeTop: Integer): PVirtualNode;
ノードの Y 座標は、以前に表示されていたすべてのノードの高さの合計です。折りたたまれたノードがなく (つまり、レコードのフラットなリストであるか、子ノードを持つすべてのノードが展開されている)、ノードがすべてデフォルトの高さを持っていると仮定すると、これは簡単です。このコードは出発点として最適です。
procedure TForm1.SelectTreeNode(AIndex: integer; ACenterNodeInTree: boolean);
var
Y, Dummy: integer;
Node: PVirtualNode;
begin
Y := Round((AIndex + 0.5) * VirtualStringTree1.DefaultNodeHeight);
Node := VirtualStringTree1.GetNodeAt(0, Y, False, Dummy);
if Node <> nil then begin
Assert(Node.Index = AIndex);
VirtualStringTree1.ScrollIntoView(Node, ACenterNodeInTree);
VirtualStringTree1.Selected[Node] := True;
VirtualStringTree1.FocusedNode := Node;
end;
end;