Delphi TActionMainMenu Accelerators on Secondary Form - delphi

I am working with Delphi XE7 in Windows 10. I have a TMainMenuBar on both a primary form and a modal secondary form. The problem is that the accelerator keys on the secondary form do not activate the menu if the secondary form also contains a TMemo. For example, if the secondary form has a File menu, Alt+F does not open the file menu. However, if alt is pressed and released, "File" is highlighted and "F" is underlined and pressing F will open the menu. Note that the problem disappears if the TMemo is removed. Also, a Tmemo on the primary form does not cause problems with the menu on the primary form.
I have searched on "TActionMainMenuBar Accelerator Keys on Secondary Form" but none of the hits describe this particular problem, although other issues with this component and accelerator keys are discussed. Does anyone know how to achieve the desired behavior while still using TActionMainMenuBar on both forms? (I prefer to not use a standard TMenu for various reasons.)

This is a VCL design issue. (Below explanation maybe somewhat off in parts. I'm tracing with XE2 and the behavior is not exactly equivalent. You may need to leave off one of the message handlers at the solution part.)
Menu bar accelerators generate a WM_SYSCOMMAND message. Your exact accelerator case is given as an example in API documentation:
If the wParam is SC_KEYMENU, lParam contains the character code of the
key that is used with the ALT key to display the popup menu. For
example, pressing ALT+F to display the File popup will cause a
WM_SYSCOMMAND with wParam equal to SC_KEYMENU and lParam equal to 'f'.
Action menu bars are proprietary VCL components. As such default window procedure of a form have no chance to handle an accelerator message for them. The component itself have the code to simulate the behavior (TCustomActionMainMenuBar.WMSysCommand), but it has to be delivered the message to be able to do that. VCL's design issue is that, only an action menu bar on the main form is given that opportunity.
A TWinControl receiving a WM_SYSCOMMAND (secondary form itself or the memo in this case), makes its parent form (secondary form) perform a CM_APPSYSCOMMAND. Upon receiving the message, the form (again the secondary form) sends the message to the Application window. CM_APPSYSCOMMAND handler of the Application, transforms the message again to a WM_SYSCOMMAND and sends it to the main form.
I can only guess, but the purpose behind this design can be, to be able to access the main menu from secondary forms that don't have menu bars.
In any case, short of switching to native menus, you have to intercept and forward the message to the action menu bar on the secondary form before the message is processed by the VCL. There can be other ways to accomplish this, but what I tried and seemed to work is this:
type
TSecondaryForm = class(TForm)
...
protected
procedure CMAppsyscommand(var Message: TMessage); message CM_APPSYSCOMMAND;
procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND;
...
procedure TSecondaryForm.CMAppsyscommand(var Message: TMessage);
begin
if ActionMainMenuBar1.Perform(PMessage(Message.LParam).Msg,,
PMessage(Message.LParam).WParam, PMessage(Message.LParam).LParam) = 0 then
inherited;
end;
// you may not need the below handler
procedure TSecondaryForm.WMSysCommand(var Message: TWMSysCommand);
begin
if ActionMainMenuBar1.Perform(Message.Msg,
TMessage(Message).WParam, TMessage(Message).LParam) = 0 then
inherited;
end;

Related

Delphi Alt key + accelerator key plays a "wrong entry" sound

I'm coding a custom buttonn derived from tExCustomControl wich, in turn, is derived from tCustomControl. The tExCustomControl component, takes care of painting and has an internal tLabel for caption display (my option for the tLabel was to facilitate the underlying of the accelerator char). In the tExCustomControl, I handle the "keyUp" event like this:
if Char (Key) = AcceleratorChar then
if AltKeyIsDown then
if Assigned (OnClick) then
OnClick (Self);
This works fine, except for one thing: while holding down the Alt key and after pressing and realeasing the accelerator key, I get a "wrong entry" sound.
Why is this sound played? How can I avoid this?
Thanks in advance.
When the Alt key is pressed down while another key is pressed, the system generates a WM_SYSKEYDOWN which is then translated to a WM_SYSCHAR by the TranslateMessage function. The 'beep' is caused by the default processing of this message, which is typically used only for system menu accelerators.
You can prevent further handling of the message to prevent the beep. While you're there, you can process the key too.
procedure tMyExCustomControl.WMSysChar(var Message: TWMSysChar);
begin
if Message.CharCode = Ord(AcceleratorChar) then
OnClick(Self)
else
inherited;
end;
The issue was with the tLabel that acts as a caption. It receives the message but, since there was no focus control setted, Windows sounds the ding. Setting the focus control to the tExButton solves the problem. In fact, the ding was played at form level, hence, before the KeyUp event in the tExCustomControl. That's the reason why the tExCustomControl had no messages to process, they where already processed by the form.

Can't hide a window that's not been fully initialized

I have this awkward situation that causes an exception. I've found the problem but the solution is so far tricky to implement for me.
In our application, if a user stays in-active for a certain period of time, the security time-out procedure kicks in to prompt the user the password entry box.
However, whenever a form has a message box displayed during the FormShow() event for any particular reason (thing to pay attention here; the formShow event execution hasn't been fully completed yet) and the user decided to not click the OK button of the dialog box for a certain time, the security code kicks in and tries to hide all forms so that it can prompt a password.
This scenario will trigger the exception "Cannot change Visible in OnShow or OnHide".
Security code loops all forms using TScreen.FormCount and hides them using TForm(TScreen.Forms[ii]).Hide individually. Hide procedure will cause the exception, because I think this form has not completed it's loading procedure fully yet.
I've done tests, and if I display a message box after the FormShow() event is executed, the security code works perfectly fine and hides all windows without any issues.
I've tried several properties and window message checking to do an "if check" before hiding forms, like Screen.Forms[ii].Visible, Screen.Forms[ii].Active but no luck so far. The mentioned form will be visible and there's no guarantee that it will be active, and if it's active how am I going to hide other non active forms. So my question is, which property or Windows message would indicate that a form is fully loaded, or at least it has past the FormShow event of any given form that exists in TScreen.Forms?
I need an answer to what I am asking please, I need a generalized solution that needs to be implemented in the security code, I can't go through over a thousand forms we have in this giant application and individually try to find solutions to any validation/warning logic exist in those forms.
Thank you
The simple answer is to stop showing the modal dialog in the OnShow of the owner form. Wait until the form has finished showing before you display the modal dialog. If you make that change, and that change alone, your existing code will start to work.
The question title you chose was:
Can't hide a window that's not been fully initialized
And so the obvious solution is to wait until the window has been fully initialized.
The simplest way to achieve this is to move your code that currently runs in OnShow into a handler for CM_SHOWINGCHANGED:
procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
Implement it like this:
procedure TMyForm.CMShowingChanged(var Message: TMessage);
begin
inherited; // this is what invokes OnShow
if Visible then
begin
// do what you previously did in OnShow
end;
end;
David Heffernan's solution gave me an idea and I solved this issue on my end.
I created following;
const
WM_SHOW_MESSAGE = WM_USER + 1;
private
procedure WMShowMessage(var Msg: TMessage); message WM_SHOW_MESSAGE;
Inside constructor;
PostMessage(Handle, WM_SHOW_MESSAGE, 0, 0);
And this will have the message box logic:
procedure MyMethod.WMShowMessage(var msg: TMessage); message WM_SHOW_MESSAGE;

DELPHI Edit.OnExit by TAB, show window result on focus bug

I'm having trouble with the following scenario:
2 Edit's
Type something in Edit1 and press TAB, focus goes to Edit2
Edit1.OnExit -> show a Form with a message "Processing..." (makes a lengthy validation)
After the form closes, the focus on Edit2 seems to be "crashed"...
- the hole TEXT in Edit2 isn't selected
- the carret isn't flashing
Example:
Create a new form
Put 2 edits
Set this as OnExit event in Edit1:
procedure TForm1.Edit1Exit(Sender: TObject);
begin
with TForm.CreateNew(self) do
try
Width := 100;
Height := 50;
Position := poMainFormCenter;
show;
sleep(200);
finally
Free;
end;
end;
Run the application
Set focus in the Edit1 and press TAB
I'm using:
Delphi 7 Enterprise
Windows 7 x64
This is a known problem. Windows has problems when you change focus before it's completed the last focus change (eg., focus starts changing from Edit1 to Edit2, but Edit1.OnExit does something to change focus to another control or form.
This happens, for instance, when apps try to do validations in an OnExit event and then try to return focus to the original control when the validation fails.
The easiest solution is to post a message to your form handle in the OnExit instead, and handle the focus change need there. It will fire once the target control gets the input focus, and Windows doesn't get confused.
const
UM_EDIT1_EXITED = WM_USER + 1;
type
TForm1=class(TForm)
...
private
procedure UMEdit1Exited(var Msg: TMessage); message UM_EDIT1_EXITED;
end;
implementation
procedure TForm1.Edit1Exit(Sender: TObject);
begin
PostMessage(Handle, UM_EDIT1_EXITED, 0, 0);
end;
procedure TForm1.UMEdit1Exited(var Msg: TMessage);
begin
// Show your other form here
end;
From an old Borland NG post by Dr. Peter Below of TeamB:
here is my general sermon on the "show dialog from OnExit" problem:
If an OnExit handler is triggered (which happens in response to the
Windows
message WM_KILLFOCUS) Windows is in the midst of a focus change. If you do
something in the handler that causes another focus change (like popping up
a message box or doing a SetFocus call) Windows gets terribly confused.
The
missing cursor is a symptom of that.
If you have to display a message to your user from an OnExit handler, do
it
this way:
Define a constant for a user message somewhere in the INterface
section
of your unit, above the type declaration for your form
'Const
UM_VALIDATE = WM_USER + 200;'
Give your Form a handler for this message, best placed in the private
section of the class declaration:
Procedure UMValidate( Var Msg: TMessage ); message UM_VALIDATE;
Post a UM_VALIDATE message to the form from the OnExit handler if
the contents of the field are not ok. You can pass additional
information in the wparam and lparam parameters of the message, e.g.
an error number and the Sender object. In fact you could do the whole
validation in the UMValidate handler!
I'm not sure precisely what's going on here, but it looks like the order of processing of messages is a bit messed up. Instead of killing your other form with Free, use Release and the focus will behave as you desire.
Another option is to use ShowModal instead of Show. Normally you show a processing dialog modally because you don't want the user making modifications to the main form whilst you are processing. If you do that then you can carry on using Free.

Delphi Application Loses Focus

I implement this chevron tool bar in my application and it works perfectly great; however, when I clicked on any items on the menu, my application loses focus. Even if I move my mouse over the the corner of the form, the cursor doesn't change to the resize handle. I need to click on the form or application in order to regain focus which I would not like to have to do. Calling MainForm.Setfocus after calling the menu item doesn't help. I would like to have the focus be automatically on my application so my users don't need to click on the form before doing the things they need to do.
Any idea on how to regain focus on the form and/or application?
Thanks
Intercept the WM_KillFocus message.
pseudo code
don't have Delphi on this terminal, will fill in the blanks when home.
type
TForm1 = class(TForm)
...
protected
procedure WMKillFocus(message: TWM_Killfocus); message WM_KillFocus;
...
procedure TForm1.WMKillFocus(message: TWM_Killfocus);
begin
//do stuff to prevent the focus from shifting.
//do *NOT* call SetFocus, it confuses Windows/Delphi and leads to suffering
//Call PostMessage or handle the KillFocus message
//From MSDN
//While processing this message, do not make any function calls that display
//or activate a window. This causes the thread to yield control and can
//cause the application to stop responding to messages. For more information
//see Message Deadlocks.
//Also don't use SendMessage, PostMessage is OK though.
//Suggestion:
PostMessage(Self.handle, WM_SETFOCUS, 0, 0);
end;

Using KeyDown event in the parent window of a Delphi Mdi Application

How do I get the KeyDown event to work in a Delphi (2007) MDI Applications Parent window, even if a Child window has focus?
I would like to implement a shortcut key (F1) that brings up a help screen in a MDI application, I have added the KeyDown procedure to the MDI Parent window and enabled KeyPreview in both the Parent and Child windows, but it does not work as expected.
If I put a break point in the Parents KeyDown code I can see it never executes, even it there are no child windows open. But if I add the same code to the child window it works fine.
Is there a way to get the parent window to receive the key presses, even if the child window has focus, as adding the code to 25+ forms seams a little wasteful?
I had the exact same problem this week! I fixed it by creating an action in the ActionManager on the mainform. This action opens the help file and has the F1-key set as shortcut. It also works for all MDI child screens.
You could use a local (global is not needed) keyboard hook. You could also derive all your MDI Child forms from a signle form base class and implement it there once. You will find that this design comes in handy for other problems as well.
edit
Application wide hotkeys/shortcuts can also be implemented with the TApplication.OnShortCut event. See http://delphi.about.com/od/adptips2004/a/bltip0904_3.htm
F1 is already the standard help shortcut which triggers TApplication.OnHelp. So maybe you want to use the OnHelp event? And if you use the HelpFile, HelpContext, HelpType and HelpKeyword properties you probably don't even need to implement any code at all.
How do I get the KeyDown event to work in a Delphi (2007) MDI Applications Parent window, even if a Child window has focus?
As a more generic solution (for applications other than F1 for help) I use code similar to this to trap a keydown event in the main form. This gets all keys no matter what, even when an MDI child is active. In this example I'm doing the opposite of what you are trying to do (I want the message to be handled by my child form instead of the main form), but the concept of catching the keys in the parent is the same).
Application.OnMessage := AppMessage;
procedure TMainForm.Appmessage(var Msg: TMsg; var Handled: Boolean);
var
message: TWMKey;
begin
If (msg.message = WM_KEYDOWN) and
( LoWord(msg.wparam) = VK_TAB ) and
(GetKeyState( VK_CONTROL ) < 0 ) and
Assigned( ActiveMDIChild ) then
Begin
Move( msg.message, message.msg, 3*sizeof(Cardinal));
message.result := 0;
Handled := ActiveMDIChild.IsShortcut( message );
End;
end;
F1 help processing is built into Delphi, so all you have to do is handle the help messages properly. This may be as little as setting the helpfile property for the application. You can set particular pages using the form's help??? properties.
Basically, just use the help system supplied and forget keydown. This is Delphi - you don't have to work hard.

Resources