【问题标题】:OnClick Event for TListbox, Calls while using the arrow Keys to change a selected itemTListbox 的 OnClick 事件,在使用箭头键更改选定项目时调用
【发布时间】:2015-06-11 17:07:53
【问题描述】:

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

使用箭头键的问题是,如果用户快速移动多个项目,我的通知服务器方法会被调用多次。 (这样不好)

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

仍然调用 OnKeyPress 和 OnClick。

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

【问题讨论】:

  • 为此使用OnClick 事件是一个糟糕的选择。触发此事件的是LBN_SELCHANGE 通知,因此此类事件的更好名称应为OnSelectionChanged。但是,列表框控件没有可以区分选择源的通知。
  • @TLama:控件在发出LBN_SELCHANGING/LBN_SELCHANGE通知时可能无法区分选择源,但您可以使用它的OnKeyDown/Up事件来帮助您区分它。
  • @Remy,如果您按住箭头键并单击 :) 会怎样?
  • @TLama:为什么用户会尝试这样做?尝试同时使用两种不同的输入法更改 ListBox 选择是没有意义的,尤其是因为它可能需要用户同时使用双手。不是直觉的人类行为。但无论如何,在我描述的方法中,键盘将优先,因此服务器更新仍将延迟到 ListBox 停止通过箭头键更改选择之后。
  • @RemyLebeau 如果用户快速连续点击两次怎么办?

标签: delphi delphi-xe7


【解决方案1】:

OnClick 事件在用户单击 ListBox 时触发,但在选择因任何原因实际更改时触发。这是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;

更新:双击OnDblClick事件之前触发OnClick事件。因此,如果您需要区分单击和双击,则还必须为此使用计时器:

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;

【讨论】:

    【解决方案2】:

    与其在每次选择或 onChange 事件发生时自动执行操作,不如使用按钮明确操作,如此处其他地方所建议的那样;或者重置一个计时器,然后当计时器关闭时,如果选择仍然有效,则触发当前选择的操作(有效地单击计时器处理程序中的按钮)。这种方法提供了一个很好的用户可配置选项,您可以在 ___ 秒后启用自动通知或要求手动单击按钮。

    【讨论】:

      【解决方案3】:

      这是documented 行为:

      当用户通过按箭头键选择网格、大纲、列表或组合框中的项目时,也会发生此事件。

      站在用户的角度,为什么要歧视使用键盘。如果我想选择当前选择正下方的项目,那么我使用鼠标还是键盘有什么关系。有些用户甚至没有鼠标。

      您需要设计您的程序以适应此类操作。您目前的做法并非不合理。即使用户用鼠标单击,我也会采用相同的方法。用户经常会错过,需要再次点击。所以,在OnClick 之后等待一小段时间再回复。

      另一种方法可能是让用户主动调用操作。因此,提供一个按钮,可能带有标题 Apply,并且仅在用户按下它时才起作用。

      【讨论】:

        【解决方案4】:

        如果我对您的理解正确,您的问题是您的程序可以向您的服务器发送许多通知。

        如果这是真的,那么您不应该考虑 TListBox 事件是如何工作的,而是如何防止向您的服务器发送大量通知。

        因此,您首先应该做的是将所有与 nitifying 您的服务器相关的代码移动到一个单独的方法中,如果您还没有这样做的话。

        然后这个方法应该检查最后一个通知发送到服务器的时间,以确定是否允许另一个服务器通知。

        为此,您可以简单地存储上次向服务器发送通知的时间,方法是使用 Now(TTime 格式)来获取当前系统时间,这对于一秒或更长的时间间隔或 GetTickCount (如果您对时间间隔感兴趣)是好的不到一秒。从技术上讲,您也可以使用 Now 来减少一秒钟的时间间隔,但需要您调用特殊方法来获取毫秒格式的时间。

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

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

        【讨论】:

        • 问题本身不在于“通知太多”。这是过多的不必要通知,是设计不当的症状。您不会通过在通知流中引入延迟来解决它。您更改逻辑,因此您不会收到不必要的通知流。箭头只是传递了不必要地触发通知的项目。只有当您确定了您想要的通知时,您才想触发通知。这可以手动完成,例如,通过单击按钮,或在最后一个更改事件后等待一段时间自动完成。
        • @DavidSchwartz 我基本上同意你的看法。但是由于 OP 没有提供这些通知在服务器端的作用的完整描述,我提到如果确实需要,将这些通知排队作为一个选项。
        • 我同意,除非 OP 明确声明“我的通知服务器方法被调用了多次。(这不好)”。如果他打算跟踪每一个变化,那将不是问题。这种跟踪也将后端与列表框中的内容紧密耦合。这是非常不明智的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-29
        • 2014-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多