5

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

同上搜索界面

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

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

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

笔记:

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

  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 ) 也没有效果。

4

2 回答 2

7

您可以使用 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;

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

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

于 2011-01-18T18:14:02.200 回答
2

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

假设您有一个 TEditEdit1和一个 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;
于 2011-01-18T15:47:12.877 回答