Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

TFindDialog and finding text in an upward direction

Status
Not open for further replies.

Stretchwickster

Programmer
Apr 30, 2001
1,746
GB
Probably me being really brain-dead but is there a way of using the TRichEdit.FindText function in an upward direction. I've worked out how to detect whether the user selects Up or Down in a FindDialog and have implemented the Down direction with no problems. I just can't think how to implement the Up searching facility and I've scoured the net!

Please help!!

Clive [infinity]

P.S. Here is my code which implements the Down direction:
Code:
procedure TForm1.FindDialog1Find(Sender: TObject);
var
  FoundAt: LongInt;
  StartPos, ToEnd: Integer;
  SearchType: TSearchTypes;
begin
  with RichEdit do
  begin
    if frWholeWord in FindDialog1.Options then
      SearchType := SearchType + [stWholeWord]
    else
      SearchType := SearchType + [];
    if frMatchCase in FindDialog1.Options then
      SearchType := SearchType + [stMatchCase]
    else
      SearchType := SearchType + [];
    if frDown in FindDialog1.Options then
    begin
      { begin the search after the current selection if there is one }
      { otherwise, begin at the start of the text }
      if SelLength <> 0 then
        StartPos := SelStart + SelLength
      else
        StartPos := 0;
      { ToEnd is the length from StartPos to the end of the text in the rich edit control }
      ToEnd := Length(Text) - StartPos;
      FoundAt := FindText(FindDialog1.FindText, StartPos, ToEnd, SearchType);
    end
    else
    begin
      {Up-direction code goes here}
    end;
    if FoundAt <> -1 then
    begin
      SelStart := FoundAt;
      SelLength := Length(FindDialog1.FindText);
    end;
  end;
end;
 
Stretchwickster,

Yes, there is, but you can't rely on the native FindText() method implemented in the VCL. Basically, Borland either forgot...or removed...support for other flags supported by the EM_FINDTEXT and EM_FINDTEXTEX messages that Microsoft provided for searching rich edits.

Basically, you control bit 0 of the Flags parameter (wParam) of the message. When set to 0, the search goes up. Complete
details can be found at Microsoft's MSDN site ( (Sorry for the ugly URL, but tek-tips doesn't appear to allow HREF links. :-().

Following is a very quick and dirty example of how you can accomplish this by creating a new version of FindText that allows this. (Please note that this is proof of concept code, not production quality. Follow the general idea, but come up with a better implementation. ;-))

Code:
uses
   richedit;

// ...

function TForm1.FindText2( const SearchStr: string;
               StartPos, Length: Integer; Options: TSearchTypes;
               SearchDown : Boolean = TRUE ): Integer;
var
  Find: TFindText;
  Flags: Integer;

begin
  with Find.chrg do
  begin
    cpMin := StartPos;
    cpMax := cpMin + Length;
  end;
  Flags := 0;
  if stWholeWord in Options then Flags := Flags or FT_WHOLEWORD;
  if stMatchCase in Options then Flags := Flags or FT_MATCHCASE;

  if SearchDown then
     Flags := Flags OR $01
  else
     Flags := Flags AND $01;


  Find.lpstrText := PChar(SearchStr);
  Result := SendMessage( RichEdit1.Handle, EM_FINDTEXT, Flags, LongInt(@Find));
end;

procedure TForm1.Button3Click(Sender: TObject);
var
   intPos : Integer;
begin

   intPos := findText2( Edit1.Text, 0, RichEdit1.getTextLen, [ ],
                       ( not cbxSearchUp.Checked ) );
   if intPos = 0 then
      beep
   else
      begin
         richedit1.SelStart := intPos;
         richEdit1.SelLength := Edit1.GetTextLen;
      end;

end;

Hope this helps...

-- Lance
 
Thanks for a very helpful post Lance, but I can't get it to work. I've scoured the documentation to see how the flags operate but without much luck. Here's what's happening when the following options are set:

stWholeWord = true, flags = flags + 2
stMatchCase = true, flags = flags + 4
SearchDown = true, flags = flags + 1
SearchDown = false, flags = 0

Is this correct behaviour?
How is it known whether the other flags have been set if SearchDown = false and consequently flags = 0?
What does $01 represent in the above code?

Help! Clive [infinity]
 
Lance said it was concept code. I think that the following code fragment needs changing from
Code:
  if SearchDown then
     Flags := Flags OR $01
  else
     Flags := Flags AND $01;
to
Code:
  if SearchDown then
     Flags := Flags OR $01
  else
     Flags := Flags AND $FE;
although I haven't tried it out.

Andrew




 
Sorry Andrew - I don't understand what $01 and $FE represent - are they hex equivalents? If so, why can't we just assign an integer value?

What value do i need to add to the flag to signal the up search direction?

And can you explain the terminology:
Code:
  Flags := Flags OR $01

  Flags := Flags AND $FE;

I haven't come across this before!

Btw, the amended code still causes EM_FINDTEXT to move in a downward direction. Clive [infinity]
 
Flags is an integer which consists of 32 bits. If you want to set a bit to one without affecting any of the other bits you shoud use an OR operation with the operator representing the bit that you want to turn on.

If you want to set a bit to zero without affecting any of the other bits then you should use an AND operation with the operator having 1s everywhere except for the bit that you want turned off.

$FE should really be $FFFFFFFE but I think only the low order bits are used by this flag.

I don't know the answer to your original problem - I thought that Lance's (concept) code looked wrong in that particular statement and that might have been why you couldn't get it to work.

Andrew
 
Okay, I don't know if you looked at the MSDN URL that Lance quoted but they key thing is that (paraphrasing the MSDN site):
Code:
If bit 0 is set, the search is from the end of the current selection to the end of the document. If not set, the search is from the end of the current selection to the beginning of the document.
So you must select the whole of your RichEdit text (or at least the part you want to conduct the search). The way to do this is to:
Code:
RichEdit1.SelStart := 0;
RichEdit1.SelLength := Length(RichEdit1.Text);
and then issue the SendMessage.

Andrew
 
Clive,

Sorry you're having problems. That's what I get for posting quickly. Andrew's spot on with the explanation. Flags is indeed an integer that uses each bit as a flag to control the operation.

Unfortunately, it's implemented oddly in the Rich Edit control, at least according to the documentation. First, you need to have Rich Edit 2.0 or later. If you're using a recent version of Windows, you should have this. I *believe* you need Win 95 OSR/2 or later, but my memory may be failing me on that one.

In any event, the &quot;trick&quot; lies in controlling the first (0-th) bit of the value passed as the wParam of the EM_FINDTEXT message, e.g. the Flags variable.

According to the docs I linked to earlier in the thread, the bit controls the direction of the search. When set, searching goes from the current position to the end. When not set (e.g. cleared), searching goes from the current position to the beginning.

Why is this odd? Well, there appears to be a difference in the documentation and observed results. Case in point, FindText() doesnt set this flag and consequently always searches down. but, if the flag isn't set, it's supposed to search up. Yet, it doesn't. I can't explain that yet, for the code I posted works in the example I crabbed together to make sure it would compile.

And, yes, the $xx shorthand is how to express hex values in code. You can use integers if you like; however, I tend to use hex codes as a coding convention to indicate the register manipulation. I suppose it's an old habit from my assembler days.

In any event, you need to determine the value of each flag you want to set, add them up, and then set flags to the unsigned result. Example:

Code:
  FR_DOWN      = 1;
  FT_MATCHCASE = 4;
  FT_WHOLEWORD = 2;
              ------     
                 7

Remember, though, that setting each flag only controls part of the behavior. If you set Flags to 0 when you search down, you won't get the case insensitive or whole word searching, either. Instead, you'd set flags to 6 when searching down and 7 when searching up.

Also, be sure to note that I modified the original implementation of FindText() to send the message directly to the RichEdit control on my form. If your RichEdit control isn't called RichEdit1 (and, to be fair, it shouldn't be), you'll need to modify things accordingly.

Oh, one final point. The documentation carefully notes that searching begins from the end of the selection. Perhaps you need to make sure that RichEdit1.SelStart has been initialized?

I know this is a lot of guessing, but I'm not sure why it's not working for you, so I'm shooting a lot in the dark.

Hope this helps...

-- Lance
 
Thanks for your posts guys - I understand what's going on now. I didn't realise you could set individual bits of an integer - so I've learned something already!

Lance, I'm using Delphi 6 on a Windows 2000 OS so I've definitely got RichEdit 2.0 or 3.0.

In my proper app and in a simple example app I setup to exactly implement Lance's code (as a last resort!), the directional behaviour is not affected by setting the flag!!

I've tried setting Flags with hex code and integer values and nothing seems to change the downward direction of the search. For example, I tried setting Flags = 6 and Flag = 7 which should initiate searches in different directions but both searches ran in a downward direction.

Rest assured Lance, when I initially implemented your code into my proper app I did make changes to various bits to make it compatible with my components. I've also tried using SelStart as it is, I've tried initialising the richedit SelStart to 0, and to the end of the richedit text.
I'm running out of ideas.

Lance, did you say that it's working fine for you? As I said, I implemented an exact replica of Lance's code with an edit box, rich edit box, a checkbox and a button and still couldn't change the direction from down to up!

Any suggestions anyone?
Clive [infinity]
 
A few months back I seem to remember someone writeing a component that would do comprehensive searching of richedits ??
Steve
 
I wrote a bit of code to run through the text of a richedit, replacing a given word with another word. It went like this.

var
FoundAt: LongInt;
StartPos, ToEnd: Integer;
begin
with RichEdit1 do
begin
SelStart := 0;
if SelLength <> 0 then StartPos := SelStart + SelLength else
StartPos := 0;
ToEnd := Length(Text) - StartPos;
repeat
FoundAt := FindText(ReplaceKeyWord, StartPos, ToEnd, [stMatchCase]);
if FoundAt <> -1 then
begin
SetFocus;
SelStart := FoundAt;
SelLength := Length(ReplaceKeyWord);
SelText := Form1.Table2.FieldByName(ReplaceFieldName).AsString;
end;
until FoundAt = -1;
end;

I dont see any reason why instead of searching the entire text, you couldnt just set the selected text to just one line at a time. If you did this, you could place your code in a loop and start at a given line, then decrement the line number each time until you hit the first line in your richedit.

If I get some free time today I'll write an example to put up here. Arte Et Labore
 
Hi Stretchwickster,

I agree with you that EM_FINDTEXT just DOES NOT WORK in the up direction. I scoured the net and found two people claiming that it works but no source code (that works) to back it up.

This thread is a bit old so you have probably moved on but I was trying to get it to work and concluded that it simple doesn't. However I wrote the following work around.

------------------------------------------------------------
Code:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, ComCtrls, DBCtrls;

type
  TForm1 = class(TForm)
    RichEdit1: TRichEdit;
    BitBtnFind: TBitBtn;
    FindDialog: TFindDialog;
    procedure BitBtnFindClick(Sender: TObject);
    procedure FindDialogFind(Sender: TObject);
  private
    function FindText( const SearchStr: string;
               StartPos, FindLength : LongInt; Options: TSearchTypes;
               SearchDown : Boolean = TRUE ): Integer;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
uses
   richedit;
{$R *.dfm}

function TForm1.FindText( const SearchStr: string;
               StartPos, FindLength : LongInt; Options: TSearchTypes;
               SearchDown : Boolean = TRUE ): Integer;
var
Find: TFindText;
Flags: Word;

begin
with Find do
   begin
   chrg.cpMin := StartPos;
   chrg.cpMax := StartPos + FindLength;
   lpstrText := PChar(SearchStr);
   end;

Flags := 0;

if stWholeWord in Options then
   Flags := Flags or FT_WHOLEWORD
else
   Flags := Flags and not FT_WHOLEWORD;

if stMatchCase in Options then
   Flags := Flags or FT_MATCHCASE
else
   Flags := Flags and not FT_MATCHCASE;

if SearchDown then
   Flags := Flags OR $01
else
   begin
   //Flags := Flags AND not $01;     // Search Up, just does not work !
   Flags := Flags OR $01
   end;

Result := -1;

if SearchDown then
   Result := SendMessage(RichEdit1.Handle, EM_FINDTEXT, Flags, LongInt(@Find))
else
   // Search Up, just does not work !  So just search down over and over
   // adjusting the start point backward.
   while (StartPos > -1) and (result = -1) do
      begin
      //result := RichEdit1.Perform(EM_FindText, Flags, LongInt(@Find));
      Result := SendMessage(RichEdit1.Handle, EM_FINDTEXT, Flags, LongInt(@Find));
      Dec(StartPos);
      Find.chrg.cpMin := StartPos;
      end;
end;


procedure TForm1.BitBtnFindClick(Sender: TObject);
begin
FindDialog.Position := Point(RichEdit1.Left + RichEdit1.Width, RichEdit1.Top);
FindDialog.Execute;
end;

procedure TForm1.FindDialogFind(Sender: TObject);
var
FoundAt: LongInt;
StartPos, FindLength, ToEnd : LongInt;
TheFindOptions
   : TFindOptions;
TheSearchTypes
   : TSearchTypes;
begin
TheFindOptions := [];
TheSearchTypes := [];

if frDown in FindDialog.Options then
   begin
   StartPos := RichEdit1.SelStart + RichEdit1.SelLength;
   FindLength := Length(RichEdit1.Text) - StartPos;
   end
else
   begin
   StartPos := RichEdit1.SelStart;
   FindLength := 0;
   end;

with Sender as TFindDialog do
   begin
   if frMatchCase in Options then
      TheSearchTypes := TheSearchTypes + [stMatchCase];
   if frWholeWord in Options then
      TheSearchTypes := TheSearchTypes + [stWholeWord];
   end;

FoundAt := FindText(FindDialog.FindText, StartPos, FindLength, TheSearchTypes,
                    (frDown in FindDialog.Options) );
if FoundAt <> -1 then
   begin
   RichEdit1.SetFocus;
   RichEdit1.SelStart := FoundAt;
   RichEdit1.SelLength := Length(FindDialog.FindText);
   end
else
   MessageDlg('&quot;' + FindDialog.FindText + '&quot; not found.', mtInformation, [mbOk], 0);

end;

end.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top