Jump to content


Photo

SearchNext Confusion


  • Please log in to reply
27 replies to this topic

#1 deonvn

deonvn

    Senior Member

  • Honorable Members
  • PipPip
  • 313 posts

Posted 14 August 2007 - 04:55 PM

Hi Boki

I am not sure if this is intentional or not, but SearchNext currently works on partial matches.

I have a column with Record ID's that match a database table. If I use SearchNext to search for Record ID "1" it will return True for any row where the cell value is 1, 11, 12, 13, etc. because it's only checking the 1st digit of the cell value. This is fine if you want to use partial matches, but not so good if you actually want to search for a specific record.

In the Roll() function:
CODE
if WideCompareText(LeftStr(Cells[ColumnIndex, i], Length(Value)), Value) = 0 then

should be
CODE
if WideCompareText(LeftStr(Cells[ColumnIndex, i], Length(Cells[ColumnIndex, i])), Value) = 0 then

to get an exact match.

Regards,
Deon

#2 Boki (Berg)

Boki (Berg)

    Boki (Berg)

  • Forum Admin
  • PipPipPipPipPip
  • 8,191 posts
  • Gender:Male

Posted 14 August 2007 - 05:13 PM

Hello Deon,

I have made it like this, but if someone else want it to be exact match I may change it and apply your fix.

Best regards
boki@bergsoft.net | LinkedIn Profile
--
BergSoft Home Page: www.bergsoft.net
Users Section: users.bergsoft.net
Articles and Tutorials: help.bergsoft.net (Developers Network)
--
BergSoft Facebook page
--
Send us applications made with our components and we will submit them on: www.bergsoft.net/apps.htm. Link to this page will be also set on home page too.

#3 deonvn

deonvn

    Senior Member

  • Honorable Members
  • PipPip
  • 313 posts

Posted 14 August 2007 - 05:42 PM

Hi Boki

Hmmm - I didn't know that it worked like that so I was getting unexpted results, but maybe some people would want to use it like that?

Maybe it would be better to add a TLocateOptions param to SearcNext() so you can specify loPartialKey or loCaseInsensitive?

Or maybe it will be better to leave SearchNext like it is and add a Locate(ColIndex, Value, LocateOptions) function to find the first exact match?

What do you think?

Regards,
Deon

#4 Boki (Berg)

Boki (Berg)

    Boki (Berg)

  • Forum Admin
  • PipPipPipPipPip
  • 8,191 posts
  • Gender:Male

Posted 14 August 2007 - 06:03 PM

Hello Deon,

Maybe you are correct. I think that I will fix it and add Locate.

Best regards and thanks.
boki@bergsoft.net | LinkedIn Profile
--
BergSoft Home Page: www.bergsoft.net
Users Section: users.bergsoft.net
Articles and Tutorials: help.bergsoft.net (Developers Network)
--
BergSoft Facebook page
--
Send us applications made with our components and we will submit them on: www.bergsoft.net/apps.htm. Link to this page will be also set on home page too.

#5 Boki (Berg)

Boki (Berg)

    Boki (Berg)

  • Forum Admin
  • PipPipPipPipPip
  • 8,191 posts
  • Gender:Male

Posted 14 August 2007 - 10:03 PM

Hello Deon,

I have apply your fix, i hope that next line is fine:

CODE
if WideCompareText(Cells[ColumnIndex, i], Value) = 0 then


Now I will think about Locate function. Maybe I can implement some kind of "incremental search" like in Firefox smile.gif

Best regards
boki@bergsoft.net | LinkedIn Profile
--
BergSoft Home Page: www.bergsoft.net
Users Section: users.bergsoft.net
Articles and Tutorials: help.bergsoft.net (Developers Network)
--
BergSoft Facebook page
--
Send us applications made with our components and we will submit them on: www.bergsoft.net/apps.htm. Link to this page will be also set on home page too.

#6 deonvn

deonvn

    Senior Member

  • Honorable Members
  • PipPip
  • 313 posts

Posted 15 August 2007 - 07:44 AM

Thanks. I will test it again in next release.

Incremental search like FireFox? How does that work? I will download FF now and check it out smile.gif

Regards,
Deon

#7 deonvn

deonvn

    Senior Member

  • Honorable Members
  • PipPip
  • 313 posts

Posted 15 August 2007 - 08:07 AM

Did you mean ThunderBird (where the visible rows are filtered based on SearchText)?

#8 deonvn

deonvn

    Senior Member

  • Honorable Members
  • PipPip
  • 313 posts

Posted 15 August 2007 - 10:23 AM

Hi Boki

I have been playing with SearchNext() some more and there are still problems in the way it's been implemented.

For example:
When the SearchNext() function reaches the last row a grid, it automatically "rolls over" to the first row of the grid and continues the search.

The following code will always cause an endless loop:
CODE
while SearcNext() do
....

because even if there is only one row in the grid that matches the search criteria, it will keep finding the same row over and over again.


Take a look at these changes:

CODE
TSearchOptions = set of (soCaseInsensitive, soPartialKey);


CODE
  function Roll(FromRow, ToRow: Integer; SearchOptions: TSearchOptions): Boolean;
  var
    i: Integer;
  begin
    Result := False;
    for i := FromRow to ToRow do
      begin
        if soCaseInsensitive in SearchOptions then
          begin
            if soPartialKey in SearchOptions then
              Result := WideCompareText(LeftStr(Cells[ColumnIndex, i], Length(Value)) , Value) = 0
            else
              Result := WideCompareText(Cells[ColumnIndex, i], Value) = 0;
          end
        else
          begin
            if soPartialKey in SearchOptions then
              Result := WideCompareStr(LeftStr(Cells[ColumnIndex, i], Length(Value)) , Value) = 0
            else
              Result := WideCompareStr(Cells[ColumnIndex, i], Value) = 0;
          end;
        if Result then
          begin
            SelectedRow := i;
            if not IsUpdating then ScrollToRow(i);
            Break;
          end;
      end;
  end;


I can now choose whether or not I want partial matches, and I can specify whether or not the search should be case-sensitive.

CODE
function TNxCustomGrid.SearchFirst(ColumnIndex: Integer; Value: WideString;
  SearchOptions: TSearchOptions = []): Boolean;
begin
  Result := False;
  if RowCount = 0 then Exit;
  Result := Roll(0, Pred(RowCount), SearchOptions);
end;


SearchFirst always starts from the first row, and then searches for the first match (if any).


CODE
function TNxCustomGrid.SearchNext(ColumnIndex: Integer; Value: WideString;
  SearchOptions: TSearchOptions = []): Boolean;
begin
  Result := False;
  if RowCount = 0 then Exit;
  Result := Roll(Succ(SelectedRow), Pred(RowCount), SearchOptions);
end;


SearcNext always starts at Succ(SelectedRow) and then searches only to the end of the grid. We want to find the next match, but exclude any matches we have already found.


Now we can use these functions recursively, and we know that we won't find the same row twice.

CODE
  Matches := 0;
  if SearchFirst(ACol, Value, []) then
    begin
      Inc(Matches);
      while SearchNext(ACol, Value, []) do
        Inc(Matches);
    end;



I also added a Locate function:

CODE
function TNxCustomGrid.Locate(ColumnIndex: Integer; Value: WideString;
  SearchOptions: TSearchOptions = []): Boolean;
begin
  Result := False;
  if RowCount = 0 then Exit;
  Result := Roll(SelectedRow, Pred(RowCount), SearchOptions) or Roll(0,SelectedRow, SearchOptions);
end;


Locate is very similar to SearchFirst, but it starts searching on the SelectedRow. If the row we are looking for is already selected, the function returns immediately without searching the whole grid, so it is good if we simply want to know if a match exists, but we don't care if it's the first match or about the position of row.

I think using the SearchFirst --> SearchNext setup makes the functions more usefull, and SearchOptions makes it a lot more flexible. What do you think?

Regards,
Deon

#9 deonvn

deonvn

    Senior Member

  • Honorable Members
  • PipPip
  • 313 posts

Posted 15 August 2007 - 10:49 AM

PS: It's now also easy to add SearchLast and SearchPrior

Deon

#10 Boki (Berg)

Boki (Berg)

    Boki (Berg)

  • Forum Admin
  • PipPipPipPipPip
  • 8,191 posts
  • Gender:Male

Posted 16 August 2007 - 04:32 AM

Hello Deon,

I am not sure that I understand part with infinite loop sad.gif

When specified string is found, search will stop. If you set FromFirst to False, it will continue from last fined row.

I will look at your code more, it look interesting. Anyway, we may still discus on this topic until I implement it.

Best regards
boki@bergsoft.net | LinkedIn Profile
--
BergSoft Home Page: www.bergsoft.net
Users Section: users.bergsoft.net
Articles and Tutorials: help.bergsoft.net (Developers Network)
--
BergSoft Facebook page
--
Send us applications made with our components and we will submit them on: www.bergsoft.net/apps.htm. Link to this page will be also set on home page too.

#11 deonvn

deonvn

    Senior Member

  • Honorable Members
  • PipPip
  • 313 posts

Posted 16 August 2007 - 09:04 AM

Hi Boki

The SearchNext function itself will not cause the infinite loop, but if you call SearchNext in a while loop it will be infinite.

QUOTE
When specified string is found, search will stop. If you set FromFirst to False, it will continue from last fined row.


Yes, but then it will find the same row again. Look at the (current) SearchNext code. If it can't find a match between Succ(SelectedRow) and Pred(RowCount), it continues searching from 0 to SelectedRow.

You can test this easily:
Make a new grid with 3 rows. In row 0, 1 and 2 enter "Frank", "Peter" and "Frank".

Search for all rows with "Frank":
CODE
Count := 0;
while Grid.SearchNext(0,'Frank') do
  Inc(Count);


This will cause an infinite loop. If you do the same search for "Peter" you will also get an infinite loop, because it keeps finding the same row over and over again.

SearchFirst should find the first row, and then SearchNext should only check for more matches between the last row that was found and the last row in the grid. If there is no match between Succ(SelectedRow) and Pred(RowCount), SearcNext must return false because there is no "next" match. Take another look at the SearchFirst and SearchNext examples from my previous post, and you will see what I mean.

The way it is now is not realy a "SearchNext" - it's more like a "SearchAny" or "Locate".

Regards,
Deon

#12 Xillion

Xillion
  • Members
  • 41 posts
  • Gender:Male
  • Location:Netherlands & Belgium

Posted 05 September 2007 - 02:41 PM

Hi Boki,

[my 2 cents mode]
I can understand the idea that a search on an exact match is a nescescarry feature. However I have impemented a search feature which navigates according to a partial match (something like firefox's incrimental search ;) ). So after an update I was supprised that I suddenly was confronted with a "bug" in my software because a function's behaviour was altered. Since the definitions stayed the same it didn't generate a compile error or something. It took some time to find out that the SearchNext function's behaviour was indeed altered, and coused my "strange behaviour".

Personally I don't find it wise to alter a function's essential behaviour without renaming, redefining or overloading the function in a way that the original behaviour can still be used without altering your code. This way I'm in the risk that my application will no longer function (properly) after each update... As far as I have seen the change in behaviour wasn't documented in the changelogs either.
[/my 2 cents mode]

Anyhow, now it seems that I myself have a problem since I am depending an a partial key search.

QUOTE (Boki (Berg) @ Aug 14 2007, 11:03 PM) <{POST_SNAPBACK}>
Now I will think about Locate function. Maybe I can implement some kind of "incremental search" like in Firefox smile.gif


You talk of some kind of locate function, so I was wandering what is the status of this is? When should this be ready?

I could solve my problem by changing the code of the SearchNext again, but I'm not perticulary fond of this solution because it complicates future updates. (I would have to copy/reimplement my own code each time I install an update.) I'd much rather see a structural solution in the component's original code.


I'm looking forward to your reply.

Stijn.
Xillion ICT Solutions - www.xillion.nl

aut viam inveniam aut faciam

#13 Boki (Berg)

Boki (Berg)

    Boki (Berg)

  • Forum Admin
  • PipPipPipPipPip
  • 8,191 posts
  • Gender:Male

Posted 05 September 2007 - 03:13 PM

Hello Stijn,

You are correct. I was think that previous behaviour is a bug and then fix it to solve some future problems. If you want, you may send me this function and I may implement it in my version.

I will try to finish Locate function for next version. I suggest visiting this topic from time to time for status updates.

Best regards
boki@bergsoft.net | LinkedIn Profile
--
BergSoft Home Page: www.bergsoft.net
Users Section: users.bergsoft.net
Articles and Tutorials: help.bergsoft.net (Developers Network)
--
BergSoft Facebook page
--
Send us applications made with our components and we will submit them on: www.bergsoft.net/apps.htm. Link to this page will be also set on home page too.

#14 Xillion

Xillion
  • Members
  • 41 posts
  • Gender:Male
  • Location:Netherlands & Belgium

Posted 05 September 2007 - 03:51 PM

Thx for the quick reply Boki (as usual).

Sending you the function has little use since your previous version of SearchNext did fine wink.gif.
Just for the sake of information, here's what I did: I have built a new component that inherits from your's (this was actually done long before to add some functionality specific to our own framework). in that component I have made a new function (with a different name) that has the exact same code as you previous SearchNext (the one with the partial search).
In my case this is enough to use as a temporary fix until the Locate function is finished. Then I can discard the unsupported function I added myself, and start using the spiffy new locate you are about to build wink.gif

Should you be interested: what we have built earier in our own component (the one derived from your TNextGrid) is a set of code on the OnKeyPress and an additional property of the type TCustomEdit. The result of it all is that I can link any TCustomEdit descendant @designtime. This edit will then display what you have typed so far (not nescescary, but very usefull visual indication for the user) and the grid will navigate to the first item that matches to what you have typed. If it can't match, the grid won't navigate and the edit will be colored red.

Regards.
Xillion ICT Solutions - www.xillion.nl

aut viam inveniam aut faciam

#15 Boki (Berg)

Boki (Berg)

    Boki (Berg)

  • Forum Admin
  • PipPipPipPipPip
  • 8,191 posts
  • Gender:Male

Posted 05 September 2007 - 04:35 PM

Hello Stijn,

Locate function is planed for next release, and I will definitelly put code here first to consult with you and deon, and then I will include it in final release.

I have in plan for v5, to add IInplaceEdit interface for InplaceEditor support. In this case any component who implement this interface will be able to be a Inplace Editor.

Best regards
boki@bergsoft.net | LinkedIn Profile
--
BergSoft Home Page: www.bergsoft.net
Users Section: users.bergsoft.net
Articles and Tutorials: help.bergsoft.net (Developers Network)
--
BergSoft Facebook page
--
Send us applications made with our components and we will submit them on: www.bergsoft.net/apps.htm. Link to this page will be also set on home page too.

#16 Boki (Berg)

Boki (Berg)

    Boki (Berg)

  • Forum Admin
  • PipPipPipPipPip
  • 8,191 posts
  • Gender:Male

Posted 23 September 2007 - 12:18 PM

Hello Deon and Stijn,

I have made some combinations of existing and Deon's code.

1) Added SearchOptions. I have extend Deon's declaration to:

CODE
TSearchOptions = set of (soCaseInsensitive, soContinueFromTop, soFromSelected, soSearchInvisible, soExactMatch);


- soContinueFromTop will continue search from top after search reach end. Work if seFromSelected is True.
- seFromSelected will start search from selected row, not from start
- soSearchInvisible will search invisible rows too
- soExactMatch only exact match will be considered

2) In private section (TNxCustomGrid) I have add Locate (Roll) procedure:

CODE
function Locate(Index, FromRow, ToRow: Integer; S: WideString; Options: TSearchOptions): Boolean;
.
.
.
function TNxCustomGrid.Locate(Index, FromRow, ToRow: Integer; S: WideString;
   Options: TSearchOptions): Boolean;
var
   i: Integer;
begin
   Result := False;
   for i := FromRow to ToRow do
   begin
     if (soSearchInvisible in Options) or GetRowVisible(i) then
     begin
       if soCaseInsensitive in Options then
       begin  
         if soExactMatch in Options then Result := WideCompareText(Cells[Index, i], S) = 0
         else Result := Pos(LowerCase(S), LowerCase(Cells[Index, i])) > 0;
       end else
       begin
         if soExactMatch in Options then Result := WideCompareStr(Cells[Index, i], S) = 0
         else Result := Pos(S, Cells[Index, i]) > 0;;
       end;
       if Result then
       begin
         SelectedRow := i;
         if not IsUpdating then ScrollToRow(i);
         Break;
       end;
     end; { invisible }
   end; { for }
end;


3) In public section, I have add new procedure:

CODE
function FindText(const Index: Integer; S: WideString;
   Options: TSearchOptions = [soCaseInsensitive, soFromSelected]): Boolean;
.
.
.
function TNxCustomGrid.FindText(const Index: Integer; S: WideString;
   Options: TSearchOptions): Boolean;
begin
   Result := False;
   if RowCount = 0 then Exit;
   if soFromSelected in Options then
   begin
     Result := Locate(Index, SelectedRow + 1, RowCount - 1, S, Options);
     if not Result and (soContinueFromTop in Options) then Locate(Index, 0, RowCount - 1, S, Options);
   end else Result := Locate(Index, 0, RowCount - 1, S, Options);
end;


Here is a small sample project: Attached File  findtext_sample.zip   2.36KB   17 downloads

--
SearchNext function will be done tomorow.

Best regards
boki@bergsoft.net | LinkedIn Profile
--
BergSoft Home Page: www.bergsoft.net
Users Section: users.bergsoft.net
Articles and Tutorials: help.bergsoft.net (Developers Network)
--
BergSoft Facebook page
--
Send us applications made with our components and we will submit them on: www.bergsoft.net/apps.htm. Link to this page will be also set on home page too.

#17 Boki (Berg)

Boki (Berg)

    Boki (Berg)

  • Forum Admin
  • PipPipPipPipPip
  • 8,191 posts
  • Gender:Male

Posted 23 September 2007 - 06:42 PM

Update:

Here is a new code for SearchNext.

Please note that SearchNext need to work as partial match, because is used when coSearchColumn flag in Column.Options is True.

CODE
function TNxCustomGrid.SearchNext(Index: Integer; S: WideString;
  FromFirst: Boolean = False): Boolean;

  function SearchText(FromRow, ToRow: Integer): Boolean;
  var
    i: Integer;
  begin
    Result := False;
    for i := FromRow to ToRow do
    begin
      if GetRowVisible(i) then
      begin
        Result := WideCompareText(LeftStr(Cells[Index, i], Length(S)), S) = 0;
      end;
      if Result then
      begin
        SelectedRow := i;
        if not IsUpdating then ScrollToRow(i);
        Break;
      end;
    end;
  end;

begin
  Result := False;
  if RowCount = 0 then Exit;
  if FromFirst then Result := SearchText(0, Pred(RowCount)) else
  begin
    Result := SearchText(Succ(SelectedRow), Pred(RowCount))
      or SearchText(0, SelectedRow);
  end;
end;


FindText procedure is more suitable for advanced search.

Best regards
boki@bergsoft.net | LinkedIn Profile
--
BergSoft Home Page: www.bergsoft.net
Users Section: users.bergsoft.net
Articles and Tutorials: help.bergsoft.net (Developers Network)
--
BergSoft Facebook page
--
Send us applications made with our components and we will submit them on: www.bergsoft.net/apps.htm. Link to this page will be also set on home page too.

#18 deonvn

deonvn

    Senior Member

  • Honorable Members
  • PipPip
  • 313 posts

Posted 23 September 2007 - 07:08 PM

Hi Boki.

Thanks - it looks good. I think it has good flexibility now, and will work for most situations smile.gif

Just one small suggestion:
soWholeWords sounds like it has something to do with "words" (I could be searching for anything). Wouldn't it make more sense to users if it was called something like soPartialMatch or soExactMatch or something like that?

Thanks again.
Deon

#19 Boki (Berg)

Boki (Berg)

    Boki (Berg)

  • Forum Admin
  • PipPipPipPipPip
  • 8,191 posts
  • Gender:Male

Posted 23 September 2007 - 07:18 PM

Hello Deon,

You are correct. Do you think that maybe soExactMach will be better?

Best regards
boki@bergsoft.net | LinkedIn Profile
--
BergSoft Home Page: www.bergsoft.net
Users Section: users.bergsoft.net
Articles and Tutorials: help.bergsoft.net (Developers Network)
--
BergSoft Facebook page
--
Send us applications made with our components and we will submit them on: www.bergsoft.net/apps.htm. Link to this page will be also set on home page too.

#20 deonvn

deonvn

    Senior Member

  • Honorable Members
  • PipPip
  • 313 posts

Posted 23 September 2007 - 07:22 PM

Hi Boki

It is up to you...but yes, I think soExactMatch or soExactMatchOnly makes it clearer to the user what the option will do.

Regards,
Deon




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users