Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forwarding keyboard events from one Windows control to another

In Delphi XE, I'm trying to implement an "instant search" feature - one that resembles Firefox's "search as you type" somewhat, but is better illustrated by a similar feature in an open source clipboard extender, Ditto:

Ditto search interface

There is a list of items that handles typical navigation events. However, any alphanumeric keys as well as navigation and editing commands (right/left arrows, shift+arrows, backspace, delete etc.) should be rerouted to the edit box below the list. An OnChange event of the edit box will trigger a refresh of the list.

The point of the UI is that user does not have to tab or shift-tab between the controls. The two controls (the list and the edit box) should 'feel" as if they were a single control. The behavior of the search UI should not be contingent on which control has focus.

It seems my best option is to forward certain keyboard events from the list control (I'm using TcxTreeList) to the edit box, and forward a handful of navigation keys from the edit box to the list. How can I achieve that?

Notes:

  1. TcxTreeList supports incremental search of course, but this is not what I'm after. The search goes to an SQLite database and looks for substring matches. The list displays only the matching items from the db.

  2. There is some overlap, e.g. both controls would normally handle VK_HOME and VK_END, but that's OK - in this case the keys would go to the list. I'll need to decide whether to forward each individual keypress, or handle it in the control that received it.

On Edit: One obvious way seemed to be to invoke the respective KeyDown, KeyUp and KeyPress methods of the edit control, like so:

type
  THackEdit = class( TEdit );

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

Unfortunately, this has no effect. My guess is TEdit won't process key events unless it is focused. Using SendMessage( THackEdit( edit1 ).Handle, WM_KEYDOWN, Key, 0 ) has no effect, either.

like image 921
Marek Jedliński Avatar asked Jan 18 '11 15:01

Marek Jedliński


2 Answers

You can use the message handling capability of a VCL control and send the relevant messages to one another. I don't know about a 'TcxTreeList', but the following demonstrates the idea on an edit control and a memo control responding to keyboard events synchronously (whereever possible of course).

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;

You might need to intervene additional messages but you get the idea.

If for one reason or another you cannot derive a new control or override message handling, you can consider sub-classing the controls. Answer to this question would show you how to do that.

like image 114
Sertac Akyuz Avatar answered Oct 08 '22 17:10

Sertac Akyuz


Not exactly what you are asking for, but for similar results, I use the following trick.

Assume you have one TEdit Edit1 and one TListbox Listbox1.

In the OnEnter event of Listbox1, simply yield focus to Edit1

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

And in the OnKeyDown event of Edit1, use the up and down arrows to navigate the items of the listbox and use the enter key to move the selected item to the edit box.

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;
like image 38
PA. Avatar answered Oct 08 '22 17:10

PA.