Matching typed text with items in a ComboBox: unclear why OnChange does/does not get called

Hello Delphi programmers,

I am trying to make a Delphi program do the following: when a user types text in a combobox, the first item from the combobox that matches the typed text is selected. The part that was added to the typed text is marked as selected. This is the same way the location bar in IE works, for example.

To do this I made the cbTestChange procedure listed below. It uses a TMemo component to list debugging statements. So far everything works fine.

When a user types backspace I want the selection mentioned above and the last typed character to be deleted. The remaining text has to be matched again with the items in the combobox, so we have a valid selected item from the combobox again. To do that I made cbTestKeyPress.

I assumed that, as soon as the combobox text is changed in cbTestKeyPress, cbTestChange is called, and that what I described above will happen automatically. However it didn't. That is my first question: why not?

So I added a line that calls cbTestChange (marked with a comment, see source). Now, cbTestChange is called as I wanted to and an item is selected from the list. (See debugging text.) However, after cbTestKeyPressed has finished, cbTestChange now does get called "automagically"! Moreover, the Text has changed back from the complete name of the item ("first item") to what it was after backspace was pressed ("f").

I hope someone can explain to me why the OnChange event doesn't happen at all in the first case and happens twice in the second, and why the Text changes the second time the OnChange event gets called. And most important: how I can make the code work properly?

Thanks in advance,
Arjen

P.S.: I know that all this can be done better with an edit box combined with a list box. I also know that part of this functionality works when you drop down the combobox and start typing, but I want to do it this way and I want to keep the combobox as I am changing an existing program.


--- Debugging text:

Text changed from: f ...   // I typed 'f'
...to: first item
Text changed from: fi ...  // I typed 'i'
...to: first item
Text after BACKSPACE: f    // I typed backspace
Text changed from: f ...   // cbTestChange is called from the line that I added
...to: first item          // this is what I want
Text changed from: f ...   // cbTestChange is called again! Why is Text now 'f' again?
...to: f

--- Source code:

procedure TForm1.cbTestChange(Sender: TObject);
var
  TmpText : string;
  i : integer;
begin
  with TComboBox(Sender) do
  begin
    TmpText := Text;  // save the text that was typed by the user
    memoDebug.Lines.Add('Text changed from: ' + Text + ' ...');
    for i := 0 to Items.Count - 1 do
      if Pos(Text, Items[i]) = 1 then
      begin
        ItemIndex := i;
        SelStart := Length(TmpText);
        SelLength := Length(Items[i]) - Length(TmpText);
        Break;
      end;
  end;
  memoDebug.Lines.Add('...to: ' + TComboBox(Sender).Text);
end;

procedure TForm1.cbTestKeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #8 then
  begin
    with TComboBox(Sender) do
    begin
      Text := Copy(Text, 1, Length(Text) - SelLength - 1);
      ItemIndex := -1;
    end;
    memoDebug.Lines.Add('Text after BACKSPACE: ' + TComboBox(Sender).Text);
    cbTestChange(Sender);  // Added this line later
  end;
end;
arjendkAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

kretzschmarCommented:
hi arjendk,

my version

Function ReturnIndex(Fragment : String; Strings : TStrings) : Integer;
var I : Integer;
begin
  I := 0;
  while (I < Strings.Count) and
        (pos(Fragment,Strings[I]) <> 1) do inc(i);
  if I = Strings.Count then I := -1;
  result := i;
end;

procedure TForm1.ComboBox1KeyPress(Sender: TObject; var Key: Char);
var
  I : Integer;
  J : Integer;
  S : String;
begin
  if not (Key in [#8,#13]) then
  begin
    J := ComboBox1.SelStart;
    S := copy(ComboBox1.Text,1,J)+key;
    I := ReturnIndex(S,ComboBox1.Items);
    if I > -1 then
    begin
      ComboBox1.Text := ComboBox1.Items[i];
      ComboBox1.SelStart := J+1;
      ComboBox1.SelLength := length(ComboBox1.Text)-J;
    end
    else
    begin
      ComboBox1.Text := s;
      ComboBox1.SelStart := J+1;
      ComboBox1.SelLength := 0;
    end;
    Key := #0;
  end;
end;

maybe it helps you

meikl
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
geobulCommented:
Hi arjendk,

Try your code without the line
 ItemIndex := -1;
in cbTestKeyPress procedure.

Regards,
Geo  
0
geobulCommented:
Hi again,

To avoid second call of cbTestChange procedure add

  Key := #0;

after

cbTestChange(Sender);  // Added this line later

as Meikl said.

Regards,
Geo
0
Exploring SQL Server 2016: Fundamentals

Learn the fundamentals of Microsoft SQL Server, a relational database management system that stores and retrieves data when requested by other software applications.

kretzschmarCommented:
arjendk, are you here?
0
arjendkAuthor Commented:
Hi,

The code provided by Meikl is not exactly what I had in mind. However, Key := #0 was the solution to the problem of the OnChange event being called twice, so both of you guided me in the right direction. Still I don't know *why* all this happens, as I wrote in my previous message. Summarized:

Case 1) KeyPress doesn't contain any of the lines mentioned in 2) and 3): OnChange event doesn't get called at all? Why?

Case 2) KeyPress contains this extra line:

  cbTestChange(Sender);

OnChange event now gets called *twice*! Why?

Case 3) KeyPress contains these extra lines: (as in the code below)

  cbTestChange(Sender);
  Key := #0;

OnChange event now gets called only once, as I want it to be. I understand that this is because the key that was pressed is suppressed by the last line.

I don't like the idea of "voodoo programming", i.e. doing something you know that works but don't know why, so that's why I'm asking this again.

And another problem came up: when you type in text to select from the combobox and then exit it, ItemIndex changes back to -1! (You can try it with the code below.) This does not happen when the item is selected directly from the list by "pointing & clicking". To solve this, I had to add the cbTestExit procedure. Again: why?

I hope someone can help me with these questions.

Arjen

--- Code is now:

procedure TForm1.cbTestChange(Sender: TObject);
var
  TmpText : string;
  i : integer;
begin
  with TComboBox(Sender) do
  begin
    TmpText := Text;
    for i := 0 to Items.Count - 1 do
      if Pos(Text, Items[i]) = 1 then
      begin
        ItemIndex := i;
        SelStart := Length(TmpText);
        SelLength := Length(Items[i]) - Length(TmpText);
        Break;
      end;
    memoDebug.Lines.Add('ItemIndex = ' + IntToStr(ItemIndex);
  end;
end;

procedure TForm1.cbTestKeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #8 then
  begin
    with TComboBox(Sender) do
    begin
      Text := Copy(Text, 1, Length(Text) - SelLength - 1);
      ItemIndex := -1;
    end;
    cbTestChange(Sender);  // Added this line later
    Key := #0;  // Added this line
  end;
end;

procedure TForm1.cbTestExit(Sender: TObject);  // Had to add this procedure!
begin
  memoDebug.Lines.Add('ItemIndex = ' + IntToStr(TComboBox(Sender).ItemIndex));
  // At this point ItemIndex = -1, so we have to change it like this:
  with TComboBox(Sender) do ItemIndex := Items.IndexOf(Text);
end;
0
arjendkAuthor Commented:
It's not exactly what I meant, but Key := #0 did solve the problem.
0
arjendkAuthor Commented:
I didn't mean to add that last comment.
Also, I wanted to give points to both Meikl and Geo since you both gave me the right solution. However that doesn't seem to be possible. If anyone can tell me how please do so.
0
kretzschmarCommented:
hi  arjendk,

first thanks for accepting my comment as answer and i'm and i guess geo also are glad thats solved.

well, about splitting points, you can ask for this (with a zero-point q) in the customer service section by referencing to this q.

after accepting my comment as answer, there is no chance for splitting points, but nevertheless ask there, by referencing this q, if there a possibility to give geo the same amount of points without take it out from your question-points.

i'm sure the ex-ex team have a solution for this, just ask

meikl
0
geobulCommented:
Hi,

Don't worry about points.
We are here to help each other.

I think that your Delphi has DIFFERENT behaviour than my D3 C/S.

In my side your first code without the line
  ItemIndex := -1;
and with Key := #0; works fine. Setting ItemIndex to -1 clears ComboBox.Text and pressing BACKSPACE clears all the text, not the last typed char only.

I've no problems exiting ComboBox by pressing TAB key or clicking outside combo.

Regards, Geo
0
arjendkAuthor Commented:
Hello,

Thank you for your comment. I promise you'll get the points next time. :-)

I do have the code working the way I want (without the line ItemIndex := -1; and with Key := #0;) but I have to set the ItemIndex to what it should be in the OnExit event. (Not necessary when the item was selected from the combobox in the ordinary way, i.e. selecting from the list.) I think this is very strange, or am I missing something completely? I am using Delphi 4.

I think the answer to my question why I have to call the OnChange event manually lies in the fact that this event only gets called when the *ItemIndex* changes, not when the *Text* changes. Am I right?

So some things have been cleared up, others haven't, but my program works the way I want it to work. Which is probably the hard reality of real-life programming!

Arjen
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.