OnClick Event for TListbox, Calls while using the arrow Keys to change a selected item

1.9k views Asked by At

I Have a ListBox on my From with several items in it. When the User clicks an item (OnClick Event) the Users Status is changed and a TCP server is notified. If I use the Arrow Keys On the Keyboard, the Same Event Is called, like an OnChange Event. However There is no OnChange Event.

The Problem with using the arrow keys is that If a User rapidly moves across several items, my Notify Server Method is called several times. (this is not good)

To Get around this I Put a Timer on the OnKeyPress Event. When The arrow keys are pressed If the user stops pushing the arrow key for 2seconds, the Notify Server Method is called, Notifying the server once. (In theory)

Both OnKeyPress and OnClick are still called.

Is anyone familiar enough with TListbox to explain to me why this happens, and if there is a better way of thinking about this problem? The User Requirements are to use a Listbox, and to Not disable the arrow keys.

4

There are 4 answers

0
David Heffernan On

This is documented behaviour:

This event can also occur when the user selects an item in a grid, outline, list, or combo box by pressing an arrow key.

From the perspective of the user, why should using the keyboard be discriminated against. If I want to select the item immediately below the current selection then why does it matter whether I use the mouse or the keyboard. Some users don't even have mice.

You need to design your program to be resilient to such actions. Your current approach is not unreasonable. I'd take the same approach even if the user clicked with the mouse. Users often miss and need to click again. So, wait for a short period of time after OnClick before responding.

Another approach might be to make the user actively invoke the action. So, provide a button, perhaps captioned Apply and only do work when the user presses it.

3
SilverWarior On

If I understand you correctly your problem is that your program can send to manny notifications to your server.

If this is true then you should not be considering of how TListBox events work but how can you prevent to manny nitifications being sent to your server.

So first thing you should do is move all the code related to nitifying your server into a seperate method if you haven't done so.

Then this method should check when the last notification to the server was sent in order to determine if another server notification is allowed.

For this you can simply store the last time that notification to the server was sent either by using Now (TTime format) to get current system time whic would be good for one second or larger intervals or GetTickCount if you are interested in intervals shorter than one second. Technically you could also use Now for less tahan a second intervals but would require you to call special methods to get the time in milliseconds format.

After you have the last notification time stored all you need to do is check if certain interval has already passed.

And if you need to really log every event you can configure your client to store them in some que and then send the whole que to the server.

0
David Schwartz On

Rather than performing the action automatically each time a selection or onChange event occurs, either make the action explicit with a button, as suggested elsewhere here; or reset a timer, then when the timer goes off, if the selection is still valid, trigger the action on the current selection (effectively clicking the button in the timer handler). This approach lends itself to a nice user-configurable option where you can enable automatic notification after ___ seconds or require the button to be clicked manually.

0
Remy Lebeau On

The OnClick event is triggered when the user clicks on the ListBox, but it is also triggered when the selection actually changes for any reason. This is a design flaw (IMHO) in how TListBox is implemented. It should have exposed actual OnChanging and OnChange events instead (since the underlying ListBox control provides such notifications), like other components do.

However, you can the use the following approach to distinguish between a mouse click and a keyboard arrow keypress:

Set a flag in the OnKeyDown event if an up/down arrow is being held down.

Clear the flag in the OnKeyUp event for the same arrow key.

You can then check for that flag in the OnClick event (or better, subclass the ListBox to intercept the LBN_SELCHANGING/LBN_SELCHANGE notification directly). If the flag is set, start your timer to delay your server action, otherwise perform your action immediately.

For example:

type
  TForm1 = class(TForm)
    ...
  private
    IsArrowDown: Boolean;
    ...
  end;

...

procedure TForm1.ListBox1Click(Sender: TObject);
begin
  if IsArrowDown then
  begin
    Timer1.Enabled := False;
    Timer1.Interval := 1000;
    Timer1.Enabled := True;
  end else
    UpdateUserStatus;
end;

procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key in [VK_DOWN, VK_UP] then
    IsArrowDown := True;
end;

procedure TForm1.ListBox1KeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key in [VK_DOWN, VK_UP] then
    IsArrowDown := False;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  UpdateUserStatus;
end;

procedure TForm1.UpdateUserStatus;
begin
  // notify server as needed...
end;

Update: a double-click also triggers the OnClick event before the OnDblClick event. So if you need to differentiate between single-clicking and double-clicking, you will have to use a timer for that as well:

type
  TForm1 = class(TForm)
    ...
  private
    IsArrowDown: Boolean;
    ...
  end;

...

procedure TForm1.ListBox1Click(Sender: TObject);
begin
  if IsArrowDown then
  begin
    Timer1.Enabled := False;
    Timer1.Interval := 1000;
    Timer1.Enabled := True;
  end else
  begin
    Timer1.Enabled := False;
    Timer1.Interval := GetDoubleClickTime() + 500;
    Timer1.Enabled := True;
  end;
end;

procedure TForm1.ListBox1DblClick(Sender: TObject);
begin
  Timer1.Enabled := False;
  UpdateUserStatus;
end;

procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key in [VK_DOWN, VK_UP] then
    IsArrowDown := True;
end;

procedure TForm1.ListBox1KeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key in [VK_DOWN, VK_UP] then
    IsArrowDown := False;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  UpdateUserStatus;
end;

procedure TForm1.UpdateUserStatus;
begin
  // notify server as needed...
end;