Reputation: 41
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.
Upvotes: 2
Views: 2104
Reputation: 1838
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.
Upvotes: 2
Reputation: 8386
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.
Upvotes: 0
Reputation: 597176
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;
Upvotes: 3
Reputation: 613302
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.
Upvotes: 1