-
Virtualtree met Subnodes
ivroegah, had ik een programma voor een soort News reader, was dus meer dan 10 jaar oud is denk ik.
En ben de source ervan ook kwijt.
Ik kan me voor geen meter meer herinneren hoe ik Sub nodes aan een normale node kan doen.
Ik heb een column die ik wou gebruiken, dat bepaalt alles voor de main node en dan de andere er aanvast the zetten, maar kom er niet uit.
iemand die me kan helpen?
Heb bv.
Nam, mynindex
Piet 1
Klaas 2
Pietje 1
Peter 1
Dus dan wordt het
+ Piet
- Pietje
- Peter
+ Klaas
Dank u
-
Wat heb je en wat gaat er mis?
-
Dit is wat ik momenteel heb.
Code:
// butten om de VirtualTree te laden
procedure Tfrmmain.dxBarButton3Click(Sender: TObject);
var
i : integer;
Column: TSWITCHViewerColumns;
begin
FCommandText := 'SELECT * FROM SWITCHDATA';
try
SwitchTree.BeginUpdate();
try
switchquery1.Close();
switchquery1.SQL.Text := FCommandText ;
for I := 0 to FCommandParams.Count - 1 do
switchquery1.Params[i].AsString := FCommandParams[I];
switchquery1.Open();
InitSwitchFields();
SwitchTree.RootNodeCount := switchquery1.RecordCount;
finally
SwitchTree.EndUpdate();
end;
finally
end;
end;
procedure Tfrmmain.switchreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
fsize11: string;
fsize2 : int64;
fsize22: string;
scountry : string;
datechange : string;
categories : string;
begin
fsize22:='';
fsize11 := FormatSize(switchquery1.Fields[FLD_SIZE].AsLargeInt, rsMB);
datechange:=FswitchFields[SWITCH_RELEASEDATE].AsString;
if length(datechange)=8 then
begin
datechange:=copy(datechange,5,2)+'/'+copy(datechange,7,2)+'/'+copy(datechange,1,4);
end;
categories:=FswitchFields[SWITCH_CATEGORY].AsString;
case TswitcRomViewerColumns(Column) of
SWITCH_NAME : CellText := FswitchFields[SWITCH_NAME].AsString;
SWITCH_ID : CellText := FswitchFields[SWITCH_ID].AsString;
SWITCH_REGION : CellText := FswitchFields[SWITCH_REGION].AsString;
SWITCH_RELEASEDATE : CellText := datechange;
SWITCH_CATEGORY : CellText := categories;
SWITCH_SIZE : CellText := fsize11;
SWITCH_FILENAME : CellText := FswitchFields[SWITCH_FILENAME].AsString;
SWITCH_STATUS : CellText := appconsts.CStatusTab[FswitchFields[SWITCH_STATUS].AsInteger];
SWITCH_COMBINEID : CellText := FswitchFields[SWITCH_COMBINEID].AsString; // als dit veld hetzelfde is als the SWITCH_ID dan moet het een child node worden.
else
CellText := Format('not implemented (column: %d)', [Column]);
end;
end;
-
ok, ben opnieuw opgestart en hebt dit nu
Code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf,
FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt, FireDAC.UI.Intf,
FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Phys, FireDAC.VCLUI.Wait,
FireDAC.Comp.Client, Vcl.StdCtrls, VirtualTrees, Data.DB, FireDAC.Comp.DataSet,
FireDAC.Phys.SQLite, FireDAC.Phys.SQLiteDef, FireDAC.Stan.ExprFuncs,
Generics.Collections,system.ioutils;
type
TForm1 = class(TForm)
SwitchQuery: TFDQuery;
dbconnection: TFDConnection;
SwitchTree: TVirtualStringTree;
procedure FormCreate(Sender: TObject);
procedure SwitchTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
procedure FormDestroy(Sender: TObject);
private
procedure BuildTree;
function NodeWithBaseName (const BaseName : string) : PVirtualNode;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
//---------------------------------
function TForm1.NodeWithBaseName (const BaseName: string): PVirtualNode;
var
SavedRecNo : Integer;
Node : PVirtualNode;
begin
Result := nil;
SavedRecNo := SwitchQuery.RecNo;
try
Node := SwitchTree.GetFirst;
while Node.IsAssigned do
begin
SwitchQuery.RecNo := Node.GetData<Integer>;
if SwitchQuery.Fields.FieldByName ('Basename').AsString = BaseName then
Exit (Node);
Node := SwitchTree.GetNext (Node);
end;
finally
SwitchQuery.RecNo := SavedRecNo;
end;
end;
//---------------------------------
procedure TForm1.BuildTree;
begin
SwitchQuery.First;
while (not SwitchQuery.Eof) do
begin
if SwitchQuery.Fields.FieldByName ('ID').AsString.EndsWith ('000') then
SwitchTree.AddChild (nil, Pointer (SwitchQuery.RecNo));
SwitchQuery.Next;
end;
SwitchQuery.First;
while (not SwitchQuery.Eof) do
begin
if not SwitchQuery.Fields.FieldByName ('ID').AsString.EndsWith ('000') then
SwitchTree.AddChild (NodeWithBaseName (SwitchQuery.Fields.FieldByName ('Basename').AsString), Pointer (SwitchQuery.RecNo));
SwitchQuery.Next;
end;
end;
//---------------------------------
procedure TForm1.SwitchTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
SavedRecNo : Integer;
begin
if not Column in [0..3] then Exit;
SavedRecNo := SwitchQuery.RecNo;
try
SwitchQuery.RecNo := Node.GetData<Integer>;
CellText := SwitchQuery.Fields[Column].AsString;
finally
SwitchQuery.RecNo := SavedRecNo;
end;
end;
//---------------------------------
procedure TForm1.FormCreate (Sender: TObject);
begin
SwitchTree.NodeDataSize := SizeOf (Integer);
dbconnection.Params.Database := '..\..\database.db';
dbconnection.Params.Values['FailIfMissing'] := 'False';
dbconnection.Params.Values['SharedCache'] := 'True';
dbconnection.Open;
SwitchQuery.SQL.Text := 'SELECT * FROM SWITCHDATA';
SwitchQuery.Open;
SwitchQuery.Last;
BuildTree;
end;
//---------------------------------
procedure TForm1.FormDestroy(Sender: TObject);
begin
dbconnection.Close;
end;
//---------------------------------
end.
het werkt maar de builtTree procedure is heel erg traag.
Is er een manier om dat sneller te maken?
Had a beginupdat gebruikt, maar dat maakt niks uit
dank u.
-
Tja, je loopt 2 keer door je dataset heen. De tweede keer doe je voor elke rij nog een keer een loop door je nodes om de node met een basename te zoeken. Daarin zitten ook weer dure stringoperaties én verspringingen in je dataset. Ik weet het niet precies, maar het zou me niets verbazen als je voor een paarhonderd rijen uiteindelijk een miljoen keer je querycomponent aanspreekt.
Mogelijke manieren om dat sneller te maken zijn:
- Je zou al snelheidswinst halen als je in plaats van het recno een objectje zou maken dat je koppelt aan de node. Daarin kan je het recno opslaan, maar ook de basename en andere info. Dan hoeft NodeWithBaseName alleen maar door de nodes te zoeken zonder ook in de dataset heen en weer te springen.
- Het query-resultaat sorteren, zodat de parent en z'n children achter elkaar staan. Dan hoef je niet steeds terug te zoeken naar de juiste parent. De laatst toegevoegde parent is dan de parent van je nieuwe node.
Sorteren zelf kan ook duur en traag zijn, dus die is niet per se de heilige graal. Als je dat kan doen zonder te moeten knippen in ID's is het alleen maar beter. Sorteren kan mogelijk in de dataset zelf (TClientDataSet heeft 'indexes' die daarbij kunnen helpen), of in de achterliggende query (order by clause, goede indexen kunnen helpen bij het sorteren, afhankelijk van de database en de structuur van de query), of door de data in records of objecten in te lezen en deze zelf te sorteren.
- Eigen dictionaries gebruiken om te helpen met het opzoeken. Een dictionary van basenames met hun node, zorgt ervoor dat je niet steeds terug hoeft te kijken naar je dataset en daar een platte loop op moet doen.
-
Allereerst, start je BuildTree met SwitchTree.BeginUpdate en eindig met SwitchTree.EndUpdate.
Verder: maak een hash stringlist (kan ook een sorted TStringList zijn; ook al gebruikt die quick sort wat trager is dan een hash index). Laten we aannemen dat je die FIndex noemt.
Elke node die je aan de tree toevoegt voeg je toe aan je index (bijv FIndex.AddObject(SwitchQuery.Fields.FieldByName ('Basename').AsString, Node)
In NodeWithBaseName gebruik je dan:
Code:
idx := FIndex.IndexOf(BaseName);
if idx <> -1 then
result := PVirtualNode(FIndex.Objects[idx])
else
result := nil;
Dat alleen zal je code al drastisch versnellen
-
Hallo,
I heb me code aangepast, en het is nu wat sneller,
maar nu worden er ge
en subnodes meer aangemaakt, en in het resultaat zijn de waardes for the columns opgeschoven.
Waar ik een naam had, heb ik nu een basename met wat onbekende data en waar een basename moet zijn, is het leeg.
En zo zijn er nog wel een paar.
Code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf,
FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt, FireDAC.UI.Intf,
FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Phys, FireDAC.VCLUI.Wait,
FireDAC.Comp.Client, Vcl.StdCtrls, VirtualTrees, Data.DB, FireDAC.Comp.DataSet,
FireDAC.Phys.SQLite, FireDAC.Phys.SQLiteDef, FireDAC.Stan.ExprFuncs,
Generics.Collections,system.ioutils;
type
TForm1 = class(TForm)
SwitchQuery: TFDQuery;
dbconnection: TFDConnection;
SwitchTree: TVirtualStringTree;
procedure FormCreate(Sender: TObject);
procedure SwitchTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
procedure FormDestroy(Sender: TObject);
private
FIndex : Tstringlist;
procedure BuildTree;
function NodeWithBaseName (const BaseName : string) : PVirtualNode;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
//---------------------------------
function TForm1.NodeWithBaseName (const BaseName: string): PVirtualNode;
var
SavedRecNo : Integer;
Node : PVirtualNode;
idx : Integer;
begin
idx := FIndex.IndexOf(BaseName);
if idx <> -1 then
result := PVirtualNode(FIndex.Objects[idx])
else
result := nil;
// Result := nil;
// SavedRecNo := SwitchQuery.RecNo;
// try
// Node := SwitchTree.GetFirst;
// while Node.IsAssigned do
// begin
// SwitchQuery.RecNo := Node.GetData<Integer>;
// if SwitchQuery.Fields.FieldByName ('Basename').AsString = BaseName then
// Exit (Node);
// Node := SwitchTree.GetNext (Node);
// end;
// finally
// SwitchQuery.RecNo := SavedRecNo;
// end;
end;
//---------------------------------
procedure TForm1.BuildTree;
var
Node : Tobject;
begin
SwitchTree.BeginUpdate;
SwitchQuery.First;
while (not SwitchQuery.Eof) do
begin
FIndex.AddObject(SwitchQuery.Fields.FieldByName ('Basename').AsString, Node);
if SwitchQuery.Fields.FieldByName ('ID').AsString.EndsWith ('000') then
SwitchTree.AddChild (nil, Pointer (SwitchQuery.RecNo));
SwitchQuery.Next;
end;
SwitchQuery.First;
while (not SwitchQuery.Eof) do
begin
if not SwitchQuery.Fields.FieldByName ('ID').AsString.EndsWith ('000') then
SwitchTree.AddChild (NodeWithBaseName (SwitchQuery.Fields.FieldByName ('Basename').AsString), Pointer (SwitchQuery.RecNo));
SwitchQuery.Next;
end;
SwitchTree.EndUpdate;
end;
//---------------------------------
procedure TForm1.SwitchTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
SavedRecNo : Integer;
begin
if not Column in [0..3] then Exit;
SavedRecNo := SwitchQuery.RecNo;
try
SwitchQuery.RecNo := Node.GetData<Integer>;
CellText := SwitchQuery.Fields[Column].AsString;
finally
SwitchQuery.RecNo := SavedRecNo;
end;
end;
//---------------------------------
procedure TForm1.FormCreate (Sender: TObject);
begin
SwitchTree.NodeDataSize := SizeOf (Integer);
dbconnection.Params.Database := '..\..\database.db';
dbconnection.Params.Values['FailIfMissing'] := 'False';
dbconnection.Params.Values['SharedCache'] := 'True';
dbconnection.Open;
SwitchQuery.SQL.Text := 'SELECT * FROM SWITCHDATA';
SwitchQuery.Open;
SwitchQuery.Last;
FIndex:=tstringlist.Create;
BuildTree;
end;
//---------------------------------
procedure TForm1.FormDestroy(Sender: TObject);
begin
dbconnection.Close;
end;
//---------------------------------
end.
-
Kan je een sampletje van die SwitchData tabel geven, zodat we de structuur zien, en aangeven hoeveel rijen je ongeveer in totaal hebt?
En misschien een screenshot van de werkende, maar te trage versie, zodat we kunnen zien wat je wil bereiken?
-
Zoiets?
Code:
procedure TForm1.BuildTree;
var
Node : PVirtualNode;
begin
SwitchTree.BeginUpdate;
try
SwitchQuery.First;
while (not SwitchQuery.Eof) do
begin
if SwitchQuery.Fields.FieldByName ('ID').AsString.EndsWith ('000') then
begin
Node := SwitchTree.AddChild(nil, Pointer (SwitchQuery.RecNo));
FIndex.AddObject(SwitchQuery.Fields.FieldByName ('Basename').AsString, Node);
end;
SwitchQuery.Next;
end;
SwitchQuery.First;
while (not SwitchQuery.Eof) do
begin
if not SwitchQuery.Fields.FieldByName ('ID').AsString.EndsWith ('000') then
begin
Node := SwitchTree.AddChild (NodeWithBaseName (SwitchQuery.Fields.FieldByName ('Basename').AsString), Pointer (SwitchQuery.RecNo));
FIndex.AddObject(SwitchQuery.Fields.FieldByName ('Basename').AsString, Node);
end;
SwitchQuery.Next;
end;
finally
SwitchTree.EndUpdate;
end;
end;
Dus na het aanmaken van de node, de node aan de FIndex toevoegen. De unit VirtualTrees heb ik niet, maar ik neem aan dat SwitchTree.AddChild een PVirtualNode terug geeft
-
In aanvulling op mijn vorige post. Na het aanmaken van de FIndex in FormCreate moet je de de sorted property nog op True zetten.
Code:
FIndex:=tstringlist.Create;
FIndex.Sorted := True;
En in BuildTree zou ik ook starten met FIndex.Clear; aan te roepen, maar als je BuildTree toch maar 1 keer aanroept dan is dat niet zo "heel" belangrijk.
-
Hier is een kopie van de database. het zijn 35 rijen en ongeveer 14000 records
Alle ID's that eindig met 000 is the hoofd regel, alles dat niet met 000 eindigd is the Chilld. en dan word are naar de basename gekeken of die hetzelfde is.
Hier is het hele project + database https://www77.zippyshare.com/v/GSUCcrpp/file.html
Ik kan hem er niet aanhangen.. limiet is 100K.
-
Even een stopwatch toegevoegd en de laatste aanpassing van havezet meegenomen:
(System.Diagnostics toevoegen aan de uses).
Als ik het meet dan duurde het opbouwen van de tree 8 seconden. Na het op sorted zetten van de stringlist, waardoor hij binary search kan doen, is dat 100ms. Dit is exclusief het uitvoeren van de query zelf.
Code:
procedure TForm1.FormCreate (Sender: TObject);
var
s: TStopWatch;
begin
SwitchTree.NodeDataSize := SizeOf (Integer);
dbconnection.Params.Database := '..\..\database.db';
dbconnection.Params.Values['FailIfMissing'] := 'False';
dbconnection.Params.Values['SharedCache'] := 'True';
dbconnection.Open;
SwitchQuery.SQL.Text := 'SELECT * FROM SWITCHDATA';
SwitchQuery.Open;
SwitchQuery.Last;
s := TStopWatch.StartNew;
FIndex:=tstringlist.Create;
FIndex.Sorted := True;
BuildTree;
ShowMessage(s.ElapsedMilliseconds.ToString());
end;
En de memory leak die je hebt is op te lossen door FIndex.Free in je FormDestroy method toe te voegen.
-
Ah, maar dit was wel de foute versie? De namen en regions komen me wat vreemd over. Overigens lijkt de data ook niet volgens verwachting te zijn. Er zijn twee ids die eindigen op 000, die dezelfde basename hebben. Het vinden van "de" node die bij dat id hoort, is dan niet echt mogelijk, want dat kunnen er meerdere zijn.
Dat valt overigens niet op, omdat de stringlist dubbelen toestaat. Als je FIndex.Duplicated := dupError zet, dan zie je het op een bepaald moment mis gaan.
De vreemde waarden lijken te komen omdat de dataset veel meer kolommen bevat. Door specifieke kolommen op te halen in de query (overeenkomstig met de kolom-volgorde van de treeview), lijkt het al een stuk beter te zijn:
SELECT id, name, region, basename FROM SWITCHDATA
Daarna valt het nog op dat elke eerste childnode een id is dat eindigt op 800 en verder geen titel of region lijkt te hebben. Als er meedere subnodes zijn, dan hebben die wel tekst.
-
Klopt alles met 800 zijn zeg maar updates, en die hebben geen naam. Ik zou bv. de naam van het 000 record kunnen gebruiken
Alles was niet 000 of 800 is zijn DLC items. for the 000 record.
Ben net klaar met me nieuwe laptop te installeren, dus kan ik weer met Delphi aan de slag.
edit,
Er is en ID dat heeft het volledige veld.
dan is er een BaseName die maar 8 of 9 karakters zijn. (is het ID - 3 ) aangezien de 1st 8/9 karakters altijd hetzelfde zijn voor de titel (name) kan je makkelijk
alle ID's pakken die met 000 eindigen en dan alle sub nodes die zijn dan ID <> 000 en BaseName = base ID van het ID die op 000 eindigt.
-
Hmm, geen idee of ik iets mis maar krijg nu
[dcc32 Error] Unit1.pas(111): E2010 Incompatible types: 'TObject' and 'PVirtualNode'
op deze regel
Code:
FIndex.AddObject(SwitchQuery.Fields.FieldByName ('Basename').AsString, Node);
ik had
Code:
FIndex : Tstringlist;
gedeclareerd tenzij dat verkeerd is.
the virtualtrees is the Free component Virtualtrees wat nu van Jamsoft is.
Hoe kreeg jij het werkend @GoleZtrol ?