用于TListbox的OnClick事件,使用箭头键调用以更改所选项目

时间:2015-06-11 17:07:53

标签: delphi delphi-xe7

我的From上有一个ListBox,里面有几个项目。当用户单击某个项目(OnClick事件)时,将更改“用户状态”并通知TCP服务器。如果我在键盘上使用箭头键,则会调用相同的事件,就像OnChange事件一样。但是没有OnChange事件。

使用箭头键的问题在于,如果用户快速移动多个项目,则会多次调用我的Notify Server方法。 (这不好)

为了解决这个问题,我在OnKeyPress事件上放了一个计时器。按下箭头键时如果用户停止按箭头键2秒,则调用Notify Server方法,通知服务器一次。 (理论上)

仍然会调用OnKeyPress和OnClick。

是否有人熟悉TListbox向我解释为什么会发生这种情况,以及是否有更好的方法来思考这个问题?用户要求是使用列表框,而不是禁用箭头键。

4 个答案:

答案 0 :(得分:3)

当用户点击ListBox时会触发OnClick事件,但当选择因任何原因实际发生更改时,也会触发。这是TListBox实施方式的设计缺陷(恕我直言)。它应该暴露实际的OnChangingOnChange事件(因为底层的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来减少第二个间隔时间,但需要你调用特殊方法来获得毫秒格式的时间。

存储完最后一个通知时间之后,您需要检查是否已经过了某个时间间隔。

如果您需要真正记录每个事件,您可以将客户端配置为将它们存储在某个队列中,然后将整个队列发送到服务器。