【问题标题】:Forwarding keyboard events from one Windows control to another将键盘事件从一个 Windows 控件转发到另一个
【发布时间】:2011-01-18 15:05:23
【问题描述】:

在 Delphi XE 中,我正在尝试实现“即时搜索”功能 - 有点类似于 Firefox 的“键入时搜索”,但更好地说明了开源剪贴板扩展器中的类似功能,Ditto

有一个处理典型导航事件的项目列表。但是,任何字母数字键以及导航和编辑命令(右/左箭头、shift+箭头、退格、删除等)都应重新路由到列表下方的编辑框。编辑框的 OnChange 事件会触发列表的刷新。

用户界面的重点是用户不必在控件之间使用制表符或切换制表符。这两个控件(列表和编辑框)应该“感觉”就像它们是一个单独的控件。搜索 UI 的行为应该取决于哪个控件具有焦点。

看来我最好的选择是将列表控件(我使用TcxTreeList)中的某些键盘事件转发到编辑框,然后从编辑框转发一些导航键到列表中。我怎样才能做到这一点?

注意事项:

  1. TcxTreeList 当然支持增量搜索,但这不是我想要的。搜索转到 SQLite 数据库并查找子字符串匹配项。该列表仅显示数据库中的匹配项。

  2. 有一些重叠,例如这两个控件通常都会处理 VK_HOME 和 VK_END,但这没关系 - 在这种情况下,键将进入列表。我需要决定是转发每个单独的按键,还是在接收它的控件中处理它。

编辑时: 一种明显的方法似乎是调用编辑控件的相应 KeyDown、KeyUp 和 KeyPress 方法,如下所示:

type
  THackEdit = class( TEdit );

procedure TMainForm.cxTreeList1KeyDown(Sender: TObject; var Key: Word; 
    Shift: TShiftState);
begin
  THackEdit( edit1 ).KeyDown( Key, Shift );
end;

很遗憾,这没有任何效果。我的猜测是 TEdit 不会处理关键事件,除非它是专注的。使用 SendMessage( THackEdit( edit1 ).Handle, WM_KEYDOWN, Key, 0 ) 也没有效果。

【问题讨论】:

    标签: windows delphi keyboard-events forwarding


    【解决方案1】:

    您可以使用 VCL 控件的消息处理能力并将相关消息发送给彼此。我不知道“TcxTreeList”,但下面演示了编辑控件和备忘录控件同步响应键盘事件的想法(当然,只要可能)。

    type
      TEdit = class(stdctrls.TEdit)
      private
        FMsgCtrl: TWinControl;
        FRecursing: Boolean;
        procedure WmChar(var Msg: TWMChar); message WM_CHAR;
        procedure WmKeyDown(var Msg: TWMKeyDown); message WM_KEYDOWN;
        procedure WmKeyUp(var Msg: TWMKeyUp); message WM_KEYUP;
      end;
    
      TMemo = class(stdctrls.TMemo)
      private
        FMsgCtrl: TWinControl;
        FRecursing: Boolean;
        procedure WmChar(var Msg: TWMChar); message WM_CHAR;
        procedure WmKeyDown(var Msg: TWMKeyDown); message WM_KEYDOWN;
        procedure WmKeyUp(var Msg: TWMKeyUp); message WM_KEYUP;
      end;
    
      TForm1 = class(TForm)
        Edit1: TEdit;
        Memo1: TMemo;
        procedure FormCreate(Sender: TObject);
      private
      public
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    { TEdit }
    
    procedure TEdit.WmChar(var Msg: TWMChar);
    begin
      if not FRecursing then begin
        inherited;
    
        // Insert test here to see if the message will be forwarded
        // exit/modify accordingly.
    
        if Assigned(FMsgCtrl) then begin
          FRecursing := True;
          try
            FMsgCtrl.Perform(Msg.Msg,
                             MakeWParam(Msg.CharCode, Msg.Unused), Msg.KeyData);
          finally
            FRecursing := False;
          end;
        end;
      end;
    end;
    
    procedure TEdit.WmKeyDown(var Msg: TWMKeyDown);
    begin
      // exact same contents as in the above procedure
    end;
    
    procedure TEdit.WmKeyUp(var Msg: TWMKeyUp);
    begin
      // same here
    end;
    
    { TMemo }
    
    procedure TMemo.WmChar(var Msg: TWMChar);
    begin
      // same here
    end;
    
    procedure TMemo.WmKeyDown(var Msg: TWMKeyDown);
    begin
      // same here
    end;
    
    procedure TMemo.WmKeyUp(var Msg: TWMKeyUp);
    begin
      // same here
    end;
    
    
    { TForm1 }
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      Edit1.FMsgCtrl := Memo1;
      Memo1.FMsgCtrl := Edit1;
    end;
    

    您可能需要干预其他消息,但您明白了。

    如果由于某种原因无法派生新控件或覆盖消息处理,则可以考虑对控件进行子类化。回复 this question 会告诉你如何做到这一点。

    【讨论】:

    • 奇怪,但是使用 TEdit 的 Perform 方法仍然没有效果 - 就像我的 OP 中的 SendMessage 示例一样。即使尝试一些简单的事情,比如总是发送退格键(编辑控件有文本并且插入符号的位置是 > 0 )什么都不做:edit1.Perform(WM_KEYDOWN, 8, 0);
    • @mood - 尝试edit1.Perform(WM_CHAR, VK_BACK, 0); 退格。这就是示例项目中包含 WM_CHAR、WM_KEYDOWN、WM_KEYUP 消息的原因。
    【解决方案2】:

    不完全是您要求的,但对于类似的结果,我使用以下技巧。

    假设您有一个 TEdit Edit1 和一个 TListbox Listbox1

    在 Listbox1 的 OnEnter 事件中,只需将焦点交给 Edit1

    procedure TForm1.ListBox1Enter(Sender: TObject); 
     begin   
      edit1.SetFocus; 
     end;
    

    并且在Edit1的OnKeyDown事件中,使用上下箭头来导航列表框的项目,并使用回车键将选中的项目移动到编辑框。

    procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); 
     var k:word; 
     begin   
      if (Shift=[]) and (key=VK_DOWN) then    
       begin
        listbox1.ItemIndex:=listbox1.ItemIndex+1;
        key:=0;    
       end   
      else if (Shift=[]) and (key=VK_UP) then
       begin
        listbox1.ItemIndex:=listbox1.ItemIndex-1;
        key:=0;    
       end   
      else if (Shift=[]) and (key=VK_RETURN) then
       begin
        edit1.text:=listbox1.items[listbox1.itemindex];
       end; 
     end;
    

    【讨论】:

    • 谢谢,PA。我已经考虑过这种方法,它仍然是一种可能性。不利的一面是 TEdit 将始终保持焦点,而我认为我更喜欢列表集中。 (同上还有其他事情:它使列表保持焦点,并在字母数字按键上将焦点放在编辑框 将该键传递给编辑。所以使用这种方法我仍然需要一个转发按键的方式。)
    猜你喜欢
    • 2017-04-12
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    • 1970-01-01
    • 2011-12-14
    • 2011-10-31
    • 1970-01-01
    • 2012-07-29
    相关资源
    最近更新 更多