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:
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:
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.
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.
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.
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With