我的From上有一个ListBox,里面有几个项目。当用户单击某个项目(OnClick事件)时,将更改“用户状态”并通知TCP服务器。如果我在键盘上使用箭头键,则会调用相同的事件,就像OnChange事件一样。但是没有OnChange事件。
使用箭头键的问题在于,如果用户快速移动多个项目,则会多次调用我的Notify Server方法。 (这不好)
为了解决这个问题,我在OnKeyPress事件上放了一个计时器。按下箭头键时如果用户停止按箭头键2秒,则调用Notify Server方法,通知服务器一次。 (理论上)
仍然会调用OnKeyPress和OnClick。
是否有人熟悉TListbox向我解释为什么会发生这种情况,以及是否有更好的方法来思考这个问题?用户要求是使用列表框,而不是禁用箭头键。
答案 0 :(得分:3)
当用户点击ListBox时会触发OnClick
事件,但当选择因任何原因实际发生更改时,也会触发。这是TListBox
实施方式的设计缺陷(恕我直言)。它应该暴露实际的OnChanging
和OnChange
事件(因为底层的ListBox控件提供了这样的通知),就像其他组件一样。
但是,您可以使用以下方法区分鼠标单击和键盘箭头按键:
如果按住向上/向下箭头,则在OnKeyDown
事件中设置一个标记。
清除OnKeyUp
事件中相同箭头键的标志。
然后,您可以在OnClick
事件中检查该标志(或者更好,将ListBox子类化为直接拦截LBN_SELCHANGING
/ LBN_SELCHANGE
通知)。如果设置了标志,请启动计时器以延迟服务器操作,否则立即执行操作。
例如:
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;
更新:双击同时会在OnClick
事件之前触发OnDblClick
事件。因此,如果您需要区分单击和双击,您还必须使用计时器:
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;
答案 1 :(得分:2)
不是每次选择或onChange事件发生时自动执行操作,而是使用按钮显示操作,如此处其他地方所建议的那样;或者重置计时器,然后当计时器关闭时,如果选择仍然有效,则触发对当前选择的操作(有效地单击计时器处理程序中的按钮)。这种方法适用于一个很好的用户可配置选项,您可以在___秒后启用自动通知,或者需要手动点击该按钮。
答案 2 :(得分:1)
这是documented行为:
当用户通过按箭头键选择网格,轮廓,列表或组合框中的项目时,也会发生此事件。
从用户的角度来看,为什么要区分使用键盘。如果我想在当前选择的正下方选择项目,那么为什么我使用鼠标或键盘都很重要。有些用户甚至没有老鼠。
您需要将程序设计为对此类操作具有弹性。您目前的做法并非不合理。即使用户点击了鼠标,我也会采用相同的方法。用户经常会错过并需要再次点击。因此,请在OnClick
之后等待一小段时间,然后才能回复。
另一种方法可能是让用户主动调用该操作。因此,提供一个按钮,可能标题为 Apply ,只有在用户按下它时才能工作。
答案 3 :(得分:0)
如果我理解正确您的问题是您的程序可以向您的服务器发送manny通知。
如果这是真的,那么您不应该考虑TListBox事件如何工作,但是如何防止将manny nitifications发送到您的服务器。
首先,你要做的就是将所有与服务器相关的代码移到一个单独的方法中,如果你还没有这样做的话。
然后,此方法应检查何时发送到服务器的最后一个通知,以确定是否允许其他服务器通知。
为此您可以简单地存储上次向服务器发送通知的时间,方法是使用Now(TTime格式)获取当前系统时间,这对于一秒或更长的时间间隔是好的,如果您对间隔感兴趣,可以使用GetTickCount不到一秒钟。从技术上讲,你也可以使用Now来减少第二个间隔时间,但需要你调用特殊方法来获得毫秒格式的时间。
存储完最后一个通知时间之后,您需要检查是否已经过了某个时间间隔。
如果您需要真正记录每个事件,您可以将客户端配置为将它们存储在某个队列中,然后将整个队列发送到服务器。