Recent

Author Topic: [SOLVED] Sorting a virtual string tree's columns  (Read 1307 times)

JD

  • Hero Member
  • *****
  • Posts: 1793
[SOLVED] Sorting a virtual string tree's columns
« on: April 29, 2020, 04:29:19 pm »
Hi there everyone,

I have the following code in OnCompareNodes for sorting a virtual string tree. I want to click on any column header and sort the column using this code but I keep getting a range check error on the test.

Code: Pascal  [Select][+][-]
  1. procedure CompareVSTNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode;
  2.   Column: TColumnIndex; var Result: Integer);
  3. var
  4.   Data1, Data2: PVTData;
  5.  
  6.   // HELPER FUNCTIONS
  7.   //
  8.   function CompareTextAsIPAddress(const s1, s2: string): integer;
  9.  
  10.       function CompareIPs(AString1, AString2: string): integer;
  11.       // Modified original source: http:www.delphipages.com/forum/showthread.php?t=104191
  12.       // Also on http://forum.lazarus.freepascal.org/index.php/topic,23656.msg141508.html#msg141508
  13.       var
  14.         a, b: TInAddr;
  15.       begin
  16.         //
  17.         a := StrToHostAddr(PChar(AString1));
  18.         b := StrToHostAddr(PChar(AString2));
  19.         //
  20.         if ntohl(a.S_addr) > ntohl(b.S_addr) then
  21.           Result := -1
  22.         else if ntohl(a.S_addr) < ntohl(b.S_addr) then
  23.           Result := 1
  24.         else
  25.           Result := 0;
  26.       end;
  27.  
  28.   begin
  29.     Result := CompareIPs(s1, s2);
  30.   end;
  31.  
  32.   function CompareTextAsInteger(const s1, s2: string): Integer;
  33.   begin
  34.     Result := CompareValue(StrToInt(s1), StrToInt(s2));
  35.   end;
  36.  
  37.   function CompareTextAsFloat(const s1, s2: string): Integer;
  38.   begin
  39.     Result := CompareValue(StrToFloat(s1), StrToFloat(s2));
  40.   end;
  41.  
  42.   function CompareTextAsDateTime(const s1, s2: string): Integer;
  43.   begin
  44.     Result := CompareDateTime(StrToDateTime(s1), StrToDateTime(s2));
  45.   end;
  46.  
  47.   // Source: http://delphiheaven.blogspot.fr/2007/03/check-if-string-is-valid-ip-address.html
  48.   function ValidateIP(IP4: string): Boolean; // Coding by Dave Sonsalla
  49.   var
  50.     Octet : String;
  51.     Dots, I : Integer;
  52.   begin
  53.     IP4 := IP4 + '.';     // add a dot. We use a dot to trigger the Octet check, so need the last one
  54.     Dots := 0;
  55.     Octet := '0';
  56.     //
  57.     for I := 1 To Length(IP4) do
  58.     begin
  59.       if IP4[I] in ['0'..'9', '.'] then
  60.       begin
  61.         if IP4[I] = '.' then  // found a dot so inc dots and check octet value
  62.         begin
  63.           Inc(Dots);
  64.           //
  65.           if (length(Octet) = 1) or (StrToInt(Octet) > 255) then Dots := 5; // Either there's no number or it's higher than 255 so push dots out of range
  66.             Octet := '0'; // Reset to check the next octet
  67.         end // End of IP4[I] is a dot
  68.         else // Else IP4[I] is not a dot so
  69.           Octet := Octet + IP4[I]; // Add the next character to the octet
  70.       end // end of IP4[I] is not a dot
  71.       else // else IP4[I] Is not in CheckSet so
  72.         Dots := 5; // Push dots out of range
  73.     end;
  74.     Result := (Dots = 4) // The only way that Dots will equal 4 is if we passed all the tests
  75.   end;
  76.  
  77.   function IsValidIntegerCheck(const AValue: String): Boolean;
  78.   var
  79.     intTemp: integer;
  80.   begin
  81.     Result := TryStrToInt(AValue, intTemp);
  82.   end;
  83.  
  84.   function IsValidFloatCheck(const AValue: String): Boolean;
  85.   var
  86.     intTemp: float;
  87.   begin
  88.     Result := TryStrToFloat(AValue, intTemp);
  89.   end;
  90.  
  91.   function IsValidDateTimeCheck(const AValue: String): Boolean;
  92.   var
  93.     dtTemp: TDateTime;
  94.   begin
  95.     Result := False;
  96.     //
  97.     if Length(AValue) = 10 then begin
  98.       if (AValue[3] = '/') and (AValue[6] = '/') then
  99.         Result := TryStrToDate(AValue, dtTemp);
  100.     end
  101.     else if Length(AValue) = 19 then begin
  102.       if (AValue[14] = ':') and (AValue[17] = ':') then
  103.         Result := TryStrToDateTime(AValue, dtTemp);
  104.     end;
  105.   end;
  106.  
  107. begin
  108.   //
  109.   Data1 := Sender.GetNodeData(Node1);
  110.   Data2 := Sender.GetNodeData(Node2);
  111.   //ShowMessage('Column: ' + IntToStr(Column));
  112.  
  113.   // SUDDENLY DOES NOT WORK ANYMORE!! IS THIS A "FEATURE" OF VirtualTrees?
  114.   try
  115.     // TO BE COMPLETED
  116.     if IsValidIntegerCheck(Data1^.RowData[Column]) then
  117.       Result := CompareTextAsInteger(Data1^.RowData[Column], Data2^.RowData[Column])
  118.     else if IsValidFloatCheck(Data1^.RowData[Column]) then
  119.       Result := CompareTextAsFloat(Data1^.RowData[Column], Data2^.RowData[Column])
  120.     else if IsValidDateTimeCheck(Data1^.RowData[Column]) then
  121.       Result := CompareTextAsDateTime(Data1^.RowData[Column], Data2^.RowData[Column])
  122.     else if IsValidDateTimeCheck(Data1^.RowData[Column]) then
  123.       Result := CompareTextAsDateTime(Data1^.RowData[Column], Data2^.RowData[Column])
  124.     else if ValidateIP(Data1^.RowData[Column]) then
  125.       Result := CompareTextAsIPAddress(Data1^.RowData[Column], Data2^.RowData[Column])
  126.     else
  127.       Result := Utf8CompareText(UTF8UpperCase(Data1^.RowData[Column]), UTF8UpperCase(Data2^.RowData[Column]));
  128.   except
  129.     // Default result
  130.     Result := 0;
  131.   end;
  132. end;
  133.  


When I investigated, I found out that the value of Column is -1.

Can anyone help me figure out what I am doing wrong?

Thanks a lot,

JD
« Last Edit: May 12, 2020, 02:15:13 pm by JD »
Windows (10, 7) - Lazarus 2.1/FPC 3.2, Delphi

Indy 10.6 series; mORMot; Zeos 7.3; SQLite, Firebird, PostgreSQL & MariaDB; VirtualTreeView 5.5.3 R1

Thaddy

  • Hero Member
  • *****
  • Posts: 10272
Re: Sorting a virtual string tree's columns
« Reply #1 on: April 29, 2020, 04:42:11 pm »
The virtual controls can be seen as a window on a usually larger set of data, often on a storage medium.
Hence for sort to work, you need to sort the - ALL - data, e.g. if the underlying storage is a database, you sort the database, which makes the virtual tree or virtual list also sorted.
The speed from the virtual controls comes from the fact that they only process a little part from a larger data collection.

So I am not surprised it doesn't work by using treeview methods.

If your data is small, don't use the virtual controls.

But maybe there are other options. I don't use these particular controls very much.
« Last Edit: April 29, 2020, 04:58:22 pm by Thaddy »
I am more like donkey than shrek

GetMem

  • Hero Member
  • *****
  • Posts: 3746
Re: Sorting a virtual string tree's columns
« Reply #2 on: April 29, 2020, 04:57:37 pm »
@JD
Quote
I have the following code in OnCompareNodes for sorting a virtual string tree.
First you have to implement the OnHeaderClick event, where you should call the SortTree method, which internally will call the compare nodes event. The OnHeaderClick typically looks like this:
Code: Pascal  [Select][+][-]
  1. procedure TSomeClass.VSTHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
  2. begin
  3.   if HitInfo.Button = mbLeft then
  4.   begin
  5.     with Sender, Treeview do
  6.     begin
  7.       if (SortColumn = NoColumn) or (SortColumn <> HitInfo.Column) then
  8.       begin
  9.         SortColumn    := HitInfo.Column;
  10.         SortDirection := sdAscending;
  11.       end
  12.       else
  13.       begin
  14.         if SortDirection = sdAscending then
  15.           SortDirection := sdDescending
  16.         else
  17.           SortDirection := sdAscending;
  18.         FSortDir := SortDirection;
  19.       end;
  20.       SortTree(SortColumn, SortDirection, False);
  21.       FSortCol := Sender.SortColumn;
  22.     end;
  23.   end;
  24. end;  

You can modify it according to your needs. Private properties FSortDir, FSortCol are used for later reference, not needed for the sort itself.

Thaddy

  • Hero Member
  • *****
  • Posts: 10272
Re: Sorting a virtual string tree's columns
« Reply #3 on: April 29, 2020, 05:05:42 pm »
Beware of side-effect, though. If you start scrolling funny things may happen. It is only a visual sort.
Out of order inserts may lead to a complete re-sort and a noticeable slowdown.
« Last Edit: April 29, 2020, 05:07:20 pm by Thaddy »
I am more like donkey than shrek

GetMem

  • Hero Member
  • *****
  • Posts: 3746
Re: Sorting a virtual string tree's columns
« Reply #4 on: April 29, 2020, 05:40:06 pm »
@Thaddy
After all this years, I'm still perplexed by your uncontrollable urge to reply in every single thread, even when you admit that the actual subject is not your strong point:
Quote
I don't use these particular controls very much.

I'm not judging you in any way, just don't understand your motivations.

zeljko

  • Hero Member
  • *****
  • Posts: 1144
    • http://wiki.lazarus.freepascal.org/User:Zeljan
Re: Sorting a virtual string tree's columns
« Reply #5 on: April 29, 2020, 06:33:56 pm »
@JD
Quote
I have the following code in OnCompareNodes for sorting a virtual string tree.
First you have to implement the OnHeaderClick event, where you should call the SortTree method, which internally will call the compare nodes event. The OnHeaderClick typically looks like this:
Code: Pascal  [Select][+][-]
  1. procedure TSomeClass.VSTHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
  2. begin
  3.   if HitInfo.Button = mbLeft then
  4.   begin
  5.     with Sender, Treeview do
  6.     begin
  7.       if (SortColumn = NoColumn) or (SortColumn <> HitInfo.Column) then
  8.       begin
  9.         SortColumn    := HitInfo.Column;
  10.         SortDirection := sdAscending;
  11.       end
  12.       else
  13.       begin
  14.         if SortDirection = sdAscending then
  15.           SortDirection := sdDescending
  16.         else
  17.           SortDirection := sdAscending;
  18.         FSortDir := SortDirection;
  19.       end;
  20.       SortTree(SortColumn, SortDirection, False);
  21.       FSortCol := Sender.SortColumn;
  22.     end;
  23.   end;
  24. end;  

You can modify it according to your needs. Private properties FSortDir, FSortCol are used for later reference, not needed for the sort itself.

Almost similar code I use with TVirtualStringTree and it works just fine.

JD

  • Hero Member
  • *****
  • Posts: 1793
Re: Sorting a virtual string tree's columns
« Reply #6 on: April 29, 2020, 10:01:02 pm »
@GetMem

 I already have an OnHeaderClick event. This is what it looks like:

Code: Pascal  [Select][+][-]
  1. procedure VSTHeaderSortColumn(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
  2. begin
  3.   if HitInfo.Column = NoColumn then Exit;
  4.  
  5.   if Sender.SortColumn <> HitInfo.Column then Sender.SortColumn := HitInfo.Column
  6.   else if Sender.SortDirection = sdAscending then Sender.SortDirection := sdDescending
  7.   else Sender.SortDirection := sdAscending;
  8.  
  9.   Sender.Treeview.SortTree( HitInfo.Column, Sender.SortDirection );
  10. end;
  11.  

JD
Windows (10, 7) - Lazarus 2.1/FPC 3.2, Delphi

Indy 10.6 series; mORMot; Zeos 7.3; SQLite, Firebird, PostgreSQL & MariaDB; VirtualTreeView 5.5.3 R1

GetMem

  • Hero Member
  • *****
  • Posts: 3746
Re: Sorting a virtual string tree's columns
« Reply #7 on: April 30, 2020, 07:07:21 am »
@JD

Do you sort the tree by default or programatically? VST->Header->SortColumn is different then -1?
1. If no, then VSTHeaderSortColumn will not work. The first line will exit the procedure.
2. If yes, then I don't know what is the problem, maybe you can attach a demo application?

JD

  • Hero Member
  • *****
  • Posts: 1793
Re: Sorting a virtual string tree's columns
« Reply #8 on: April 30, 2020, 10:07:12 am »
@JD

Do you sort the tree by default or programatically? VST->Header->SortColumn is different then -1?
1. If no, then VSTHeaderSortColumn will not work. The first line will exit the procedure.
2. If yes, then I don't know what is the problem, maybe you can attach a demo application?

@GetMem

My VST->Header->SortColumn is -1. What is funny is that I get the range check error when the tree is to be filled with data. After that, the sorting works on any column I click. I will upload a test project as soon as I can later today.

JD
Windows (10, 7) - Lazarus 2.1/FPC 3.2, Delphi

Indy 10.6 series; mORMot; Zeos 7.3; SQLite, Firebird, PostgreSQL & MariaDB; VirtualTreeView 5.5.3 R1

coliv_aja

  • New Member
  • *
  • Posts: 42
Re: Sorting a virtual string tree's columns
« Reply #9 on: April 30, 2020, 11:11:17 am »
if the column index was passed by onheaderclick event then it must be a valid column. there's no way it will yield range check error on column. there's must be something wrong in your code.

JD

  • Hero Member
  • *****
  • Posts: 1793
Re: Sorting a virtual string tree's columns
« Reply #10 on: May 11, 2020, 08:58:36 pm »
Hi there everyone,

Sorry for the long delay. I was otherwise occupied. I attach a comprehensive example for you all to see where my problem lies:

a) My date column (column 3 - Date sanction) is being sorted as UTF8 text (this is not what I intended but it happens because the date check fails)
b) My datetime column (column 7 - Crée le) is where the big problem lies. The date time check fails, it throws an error when it enters integer check and finally gets sorted as text

What am I doing wrong here?

JD
« Last Edit: May 11, 2020, 09:25:25 pm by JD »
Windows (10, 7) - Lazarus 2.1/FPC 3.2, Delphi

Indy 10.6 series; mORMot; Zeos 7.3; SQLite, Firebird, PostgreSQL & MariaDB; VirtualTreeView 5.5.3 R1

bytebites

  • Sr. Member
  • ****
  • Posts: 289
Re: Sorting a virtual string tree's columns
« Reply #11 on: May 11, 2020, 10:37:05 pm »
There is autosort set to true, which causes call to CompareVSTNodes with column value -1.

Otherwise the program works.

JD

  • Hero Member
  • *****
  • Posts: 1793
Re: Sorting a virtual string tree's columns
« Reply #12 on: May 11, 2020, 11:34:46 pm »
There is autosort set to true, which causes call to CompareVSTNodes with column value -1.

Otherwise the program works.

No it does not, even with AutoSort set to false. It sorts dates as text (even though the result is correct) and it still gives an error when I click on column 7 (Crée le) in the Debug mode.  See attachment

Line 312
Code: Pascal  [Select][+][-]
  1.       Result := CompareTextAsDateTime(Data1^.RowData[Column], Data2^.RowData[Column])
  2.  

and line 314
Code: Pascal  [Select][+][-]
  1.       Result := CompareTextAsDate(Data1^.RowData[Column], Data2^.RowData[Column])
  2.  
are never called

JD
« Last Edit: May 13, 2020, 10:19:36 am by JD »
Windows (10, 7) - Lazarus 2.1/FPC 3.2, Delphi

Indy 10.6 series; mORMot; Zeos 7.3; SQLite, Firebird, PostgreSQL & MariaDB; VirtualTreeView 5.5.3 R1

GetMem

  • Hero Member
  • *****
  • Posts: 3746
Re: Sorting a virtual string tree's columns
« Reply #13 on: May 12, 2020, 06:29:59 am »
@JD
The error message it's pretty clear. You should change StrToInt to TryStrToInt, like you did with the other conversions:
Code: Pascal  [Select][+][-]
  1. if TryStrToInt(Octet, LInt) then
  2.  if (length(Octet) = 1) or (LInt > 255) then Dots := 5;



bytebites

  • Sr. Member
  • ****
  • Posts: 289
Re: Sorting a virtual string tree's columns
« Reply #14 on: May 12, 2020, 07:16:41 am »
Changes I made to get it to work
Code: Pascal  [Select][+][-]
  1. ***************
  2. *** 193,198 ****
  3. --- 192,198 ----
  4.     Column: TColumnIndex; var Result: Integer);
  5.   var
  6.     Data1, Data2: PData;
  7. +   fmt: TFormatSettings;
  8.     // HELPER FUNCTIONS
  9.     //
  10.     function CompareTextAsIPAddress(const s1, s2: string): integer;
  11. ***************
  12. *** 236,242 ****
  13.  
  14.     function CompareTextAsDateTime(const s1, s2: string): Integer;
  15.     begin
  16. !     Result := CompareDateTime(StrToDateTime(s1), StrToDateTime(s2));
  17.     end;
  18.  
  19.     // Source: http://delphiheaven.blogspot.fr/2007/03/check-if-string-is-valid-ip-address.html
  20. --- 236,242 ----
  21.  
  22.     function CompareTextAsDateTime(const s1, s2: string): Integer;
  23.     begin
  24. !     Result := CompareDateTime(StrToDateTime(s1,fmt), StrToDateTime(s2,fmt));
  25.     end;
  26.  
  27.     // Source: http://delphiheaven.blogspot.fr/2007/03/check-if-string-is-valid-ip-address.html
  28. ***************
  29. *** 299,305 ****
  30.     var
  31.       DT: TDateTime;
  32.     begin
  33. !     Result := SysUtils.TryStrToDateTime(DateTimeString, DT);
  34.     end;
  35.  
  36.   begin
  37. --- 299,305 ----
  38.     var
  39.       DT: TDateTime;
  40.     begin
  41. !     Result := SysUtils.TryStrToDateTime(DateTimeString, DT,fmt);
  42.     end;
  43.  
  44.   begin
  45. ***************
  46. *** 307,317 ****
  47.     Data1 := Sender.GetNodeData(Node1);
  48.     Data2 := Sender.GetNodeData(Node2);
  49.  
  50.     try
  51.       if IsValidDateTimeString(Data1^.RowData[Column]) then
  52.         Result := CompareTextAsDateTime(Data1^.RowData[Column], Data2^.RowData[Column])
  53. !     else if IsValidDateString(Data1^.RowData[Column]) then
  54. !       Result := CompareTextAsDate(Data1^.RowData[Column], Data2^.RowData[Column])
  55.       else if IsValidInteger(Data1^.RowData[Column]) then
  56.         Result := CompareTextAsInteger(Data1^.RowData[Column], Data2^.RowData[Column])
  57.       else if IsValidFloat(Data1^.RowData[Column]) then
  58. --- 306,322 ----
  59.     //
  60.     Data1 := Sender.GetNodeData(Node1);
  61.     Data2 := Sender.GetNodeData(Node2);
  62. +   fmt:=DefaultFormatSettings;
  63. +   fmt.DateSeparator:='-';
  64. +   fmt.TimeSeparator:=':';
  65. +   fmt.ShortDateFormat:='yyyy-mm-dd';
  66. +   fmt.ShortTimeFormat:='hh:nn';
  67.  
  68.     try
  69.       if IsValidDateTimeString(Data1^.RowData[Column]) then
  70.         Result := CompareTextAsDateTime(Data1^.RowData[Column], Data2^.RowData[Column])
  71. !     //else if IsValidDateString(Data1^.RowData[Column]) then
  72. !     //  Result := CompareTextAsDate(Data1^.RowData[Column], Data2^.RowData[Column])
  73.       else if IsValidInteger(Data1^.RowData[Column]) then
  74.         Result := CompareTextAsInteger(Data1^.RowData[Column], Data2^.RowData[Column])
  75.       else if IsValidFloat(Data1^.RowData[Column]) then

 

TinyPortal © 2005-2018