I've built a custom control that I'm trying to send input to. It will accept mouse input and report MouseDown, MouseMove and MouseUp correctly, but for whatever reason, it won't accept keyboard input. When I click on it, it doesn't receive focus, and any keys I press get interpreted by whatever control had the focus already.
This is probably something really simple. The first place I thought to look was in the ControlStyle property, but the only thing I can see in the helpfile about keyboard input is csNoStdEvents, which disables it, and my control doesn't have that. So what do I need to do to make it so my control can receive input focus?
A few things to try:
On MouseDown, call Windows.SetFocus(Handle). In my experience, the WinAPI function SetFocus often works better than the VCL's SetFocus method.
In response to the WM_GETDLGCODE message, reply with Message.Result := Message.Result or DLGC_WANTCHARS or DLGC_WANTARROWS or DLGC_WANTTAB or DLGC_WANTALLKEYS;
Could it be as simple as calling SetFocus on mouse down?
procedure TYourCustomControl.MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer);
begin
inherited;
if CanFocus then
SetFocus;
end;
Do you have WS_TABSTOP set? You don't have input focus without that, I believe. But this is based on a recollection from nearly 10 years ago, when I was writing my own syntax-highlighting code editor, for which I have long since lost the source.
{TWinControl.}TabStop := True; ought to do. A quick test app with a do-nothing component derived from TWinControl and displaying a dialog for key events seems to show that it makes all the difference.
I've checked the code for my control and I can't see anything that might stop this working. Are you calling "inherited" in the Create procedure?
I do handle the following, but nothing special:
procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
Is the keystroke available at form level? That is, is KeyPreview turned on, and can you see the keystroke in the form's OnKeypress event? You can follow it from there in the debugger. Is the control (as Dan indicated) suitable for keyboard input? For instance, a TLabel, although it displays text, is a graphical control.
Related
I am using Tmainform.OnKeyDown and it fires always correctly, besides the controls or frames added to the form.
I need the same behavior for OnMouseDown.
My goal is to track activity of the user. After x minutes with no keyboard nor mouse clicks I want to close the application.
Edit: TMainForm.OnMouseDown never gets fired. I don't want to do anything with the event, just know that the user is alive and clicking.
For the Form to see keystrokes prior to the active control you need to set the KeyPreview property within the forms Object Inspector.
You can also do this via code: Form1.KeyPreview := True;
There is a substantial explanation in the accepted answer here: How does Delphi's KeyPreview work?
Regarding your mouse query, how do you know it isn't working if you're not doing anything there?
Put this code into your forms OnMouseDown event;
PROCEDURE TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
BEGIN
CASE Button OF
mbLeft: showmessage('Left Mouse Button!');
mbRight: showmessage('Right Mouse Button!');
mbMiddle: showmessage('Middle Mouse Button!');
END;
END;
I hope this is helpful and answers your question.
I have a fairly large program, and I need a way to suppress all the Windows beeps you get on pressing enter. I found a function that allows me to suppress all beep sounds, but I need the error sounds to be fired when something goes wrong, so that is not an option. I saw you could suppress the sound for a single textbox by setting the Key to 0, but this is not an option as there are a LOT of keypress events in my program.
You can suppress these beeps that are produced when an edit control has the focus and you press ESCAPE and ENTER by setting TForm.KeyPreview to True and then adding the following OnKeyPress event handler for your form:
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
if (Key=#13) or (Key=#27) then
Key := #0;
end;
If you have some controls for which you wish to accept ENTER or ESCAPE, for instance multi-line edit controls, then you could leave KeyPreview as False, and handle OnKeyPress for each single-line edit control:
procedure TForm1.EditKeyPress(Sender: TObject; var Key: Char);
begin
if (Key=#13) or (Key=#27) then
Key := #0;
end;
Or you could leave KeyPreview as True and then have a form-wide OnKeyPress handler that discriminated based on the control that has the focus. For example, a rather crude example:
function IsSingleLineEdit(Edit: TCustomEdit): Boolean;
var
Style: DWORD;
begin
Style := GetWindowLongPtr(Edit.Handle, GWL_STYLE);
Result := Style and ES_MULTILINE = 0;
end;
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
if (ActiveControl is TCustomEdit)
and IsSingleLineEdit(TCustomEdit(ActiveControl)) then
if (Key=#13) or (Key=#27) then
Key := #0;
end;
However, I would suggest that you do none of these things. I suggest that you leave the behaviour as it currently is. If neither the form, nor the focused control is going to response to these key presses, then beeping is the most appropriate response. The user presses ESCAPE when she wants to cancel the current dialog. If you are not going to respond to that, then the system beeps to indicate that. Likewise, the user presses ENTER when she wants to accept the current dialog. Again, since your dialog doesn't respond, a beep is appropriate.
I saw you could surpress the sound for a single textbox by setting the Key to 0, but this is not an option as there are a LOT of keypress events in my program. Is there any solution to this?
This is actually the correct approach to do this provided that specific EditBox does have proper code to execute certain action upon pressing the Enter key.
If you don't have any specific code to process some action on pressing the Enter key when in certain field then the Ding sound should be heard to tell the user he is doing something wrong.
Same would go if you are limited specific EditBoxes to only numbers. So that every time when the user presses any letter key the Ding sound is played and the user knows that letters are not allowed. Othervise the user might thing that there is something wrong with his keyboard.
You wouldn't want to be a guy who made a software whose usage resulted in lots of keyboards getting busted. Or would you? :-)
So I'm afraid you would have to do lots of code editing for a lot of KeyPress events. I must admit that I kinda feel sorry for you. Been in similar position once.
As for David Heffernan suggestion about filtering the Enter and Escape keys using forms KeyPreview.
Don't! Realy! DON'T! Why?
Becouse by doing so you could interfeere with normal functionality of some other components.
For instance using that code with combination of ComboBox prevents you from using Escape key to colapse the expanded combo box.
I encountered some behavior in FMX that is different in VCL. This relates to how main menu shortcuts are processed. It seems that an FMX application will intercept all shortcut keystrokes in the main form such that any other active forms do not see these keystrokes.
This means for example if you have a TMemo on a second form and the main form uses the Ctrl-V main menu short cut, it's not possible to paste text into the memo using Ctrl-V. This is unique to FMX, VCL works as expected where a second form receives all keystrokes irrespective of shortcuts on a main form.
An answer in this question How to intercept Menu shortcut event in Firemonkey explains how to intercept shortcuts in the main form.
The question here is how to get these intercepted keystrokes from the main form to the currently active form, so that controls such as TMemo or TEdit on the second form work as expected?
Based on the answer in How to intercept Menu shortcut event in Firemonkey, one way to pass on the mainmenu shortcut keystrokes, eg Ctrl-A, to the currently active form is to use this code in the main form:
TMenuItem = class(FMX.Menus.TMenuItem)
protected
procedure DialogKey(var Key: Word; Shift: TShiftState); override;
end;
procedure TMenuItem.DialogKey(var Key: Word; Shift: TShiftState);
var ch : char;
begin
if (ssCtrl in Shift) and (Key = 65){A} then
begin
ch := #0;
Screen.ActiveForm.KeyDown(Key, ch, Shift);
exit;
end;
inherited;
end;
An alternative answer to the first one, it's works but not everyone might like it, plus it has limitations. For simple cases is should work.
Before showing the second form, remove the shortcuts from the mainform, then restore the shortcuts when the form returns control to the mainform. Works ok if the second form is shown using showmodal. Pity there isn't a form OnShortCut event as we have in VCL. Eg rough example:
(MainMenu.Items[0] as TMenuItem).Items[0].ShortCut := TextToShortCut('');
FormTwo.ShowModal;
(MainMenu.Items[0] as TMenuItem).Items[0].ShortCut := TextToShortCut('Ctrl+V');
I am trying to put form into "help mode" in Delphi 2010.
I have a button which the user clicks, and I want the cursor to change to the help cursor, then when a user clicks onto a control, the help for the control is displayed
Is there a window message that I can send?
Send a WM_SYSCOMMAND message to the form passing SC_CONTEXTHELP as lParam.
Changes the cursor to a question mark with a pointer. If the user then clicks a control in the dialog box, the control receives a WM_HELP message.
Write something like this in your button OnClick event handler:
procedure TMyForm.Button1Click(Sender: TObject);
begin
SendMessage(Handle, WM_SYSCOMMAND, SC_CONTEXTHELP, 0);
end;
Many times after the Windows 98 era we have experienced that some dialogs lose their Z-Order and move back to the prior form.
For example:
Dialog1.ShowModal;
Dialog1.OnClickButton() : ShowMessage('anything');
When MessageBox appears, it sometimes doesn't have focus and is moved under Dialog1.
The users are confused about it, they say: My application froze!!!
But if they use Alt+Tab to move to another app and back, the focus returns to the MessageBox and it will be the foreground window.
We have experienced this with ShowMessage, MessageBox, normal forms, and also QuickReport forms.
Does anyone know about this? Is it a Windows bug? How can you prevent it? How to catch this?
Thanks for your help:
dd
I really said that AFTER Win98, so all OSs (Win7 also) are affected by this problem.
We used Delphi 6 Prof, so the properties are not working with Default forms.
Somebody said that message dialogs are controllable with MessageBox + MB_APPLMODAL.
This is good news, but we have many old forms and components, third party tools.
So it is hard work to make a completely new application with substitution of the forms.
But we will try doing this.
I think the answer is this is a half application problem and half a Windows problem. If Windows sometimes handles this, and sometimes doesn't - that seems to be a Windows bug.
But if we can force good modal window making then it is a programming bug.
Can somebody explain to me what is the meaning of the WS_POPUP flag?
Does it have some side effect or not?
Thanks:
dd
That's what the PopupMode and PopupParent properties are for.
E.g., you can do:
Dialog1.PopupMode := pmExplicit;
Dialog1.PopupParent := self;
Dialog1.ShowModal;
This tells Windows the correct Z-order.
For old versions of delphi (prior to Delphi 2007), on forms OTHER than your main form:
interface
TMyForm = Class(TForm)
protected
procedure CreateParams(var Para: TCreateParams); override;
end;
...
implementation
...
procedure TMyForm.CreateParams(var Para: TCreateParams);
begin
inherited;
Para.Style := Para.Style or WS_POPUP;
{ WinXP Window manager requires this for proper Z-Ordering }
// Para.WndParent:=GetActiveWindow;
Para.WndParent := Application.MainForm.Handle;
end;
For message boxes include MB_TOPMOST in your flags:
Application.MessageBox(PChar(amessage), PChar(atitle), otherflags or MB_TOPMOST);
I looked at this page and the FAQ for half an hour and still can't find how to post a comment, so forgive me for this breach of protocol.
First of all I'd like to make clear that the poster, IMHO, is not using Windows 98. He writes "after Windows 98 era" which I understand means he is having this problem with Windows versions after 98.
As I am having this problem too (CB2009), I'd like to emphasize the poster's question "Is it Windows bug?", which I have not seen answered. If it's a Delphi/Builder bug, maybe there is a way to avoid it? I can't see how intercepting all potential dialogs is a workable solution, nor avoid using fsStayOnTop. I have a settings form that needs to stay on top of my main form, but the settings form can and will popup dialogs that under certain conditions will disappear under the settings form.
It would be very helpful if I would understand where the support of z-order goes wrong, as it may offer a clue on how to avoid it.
A trick I've used recently was to apply these two lines of code during the creation of each form:
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or
WS_EX_APPWINDOW or WS_EX_TOPMOST);
SetWindowLong(Handle, GWL_HWNDPARENT, GetDesktopWindow);
Handle is the handle of the form (Form1.Handle). The WS_EX_APPWINDOW part makes each window appear on the task bar, remove it if you don't want that additional effect.
For my main form I use this line:
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or
WS_EX_TOPMOST);
I also use this function to help build my custom dialogs (I created a new function for each style of dialog - error, confirmation, etc.):
function CustomDlg(const AMessage : string; const ADlgType: TMsgDlgType;
const AButtons: TMsgDlgButtons; const ADefaultButton: TMsgDlgBtn) : TForm;
begin
Result := CreateMessageDialog(AMessage, ADlgType, AButtons, ADefaultButton);
with Result do
begin
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or
WS_EX_APPWINDOW or WS_EX_TOPMOST);
SetWindowLong(Handle, GWL_HWNDPARENT, GetDesktopwindow);
FormStyle := fsStayOnTop;
BringToFront;
end;
end;
The FormStyle := fsStayOnTop; part is optional, of course, but I use it to make sure my confirmation and error dialogs are always visible to the user.
It seems like a bit of work but the net effect is that I no longer have to worry about forms accidentally hiding behind other forms.