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.
Related
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;
I have a Frame and some controls on them (edits, buttons, etc.). How to intercept pressing of ENTER key anywhere on a frame control and translate in to TAB key (taking into account SHIFT status)?
All you need is to modify the handling of CM_DIALOGKEY.
type
TMyForm = class(TForm)
protected
procedure CMDialogKey(var Message: TCMDialogKey); message CM_DIALOGKEY;
end;
procedure TMyForm.CMDialogKey(var Message: TCMDialogKey);
begin
if Message.CharCode=VK_RETURN then
Message.CharCode := VK_TAB;
inherited;
end;
Well, it's pretty obvious what this does and how it works.
You ask how to do this in a frame. It's not possible to handle dialog navigation in a frame. That's done by the form for fairly obvious reasons. So you'll need somehow to splice this code into the form that hosts your frame.
For a memo control this will have no effect. They will treat pressing ENTER as input of a line break. But I presume that's what you would wish to happen. Otherwise the memo control would be completely unusable.
Here's some example code that would handle a message on the frame to be able to navigate to the next control when Enter is pressed. Note that this sample does not modify the Enter key to become a Tab key. Instead it selects the next control and prevents further processing of the key down message.
Also note that the code may require further tweaking. One for, if any of the controls actually need to process the Enter key, for instance a TMemo, you need to add an exception. Second for, the navigation is wrapped in the frame, i.e. after the last frame control the first frame control is focused - not a control on the form and not on the frame. For these, you might want to add conditions for the message return, if you want default processing on some condition simply call inherited without doing any other thing.
type
TFrame2 = class(TFrame)
...
protected
procedure CMChildKey(var Message: TCMChildKey); message CM_CHILDKEY;
end;
..
procedure TFrame2.CMChildKey(var Message: TCMChildKey);
begin
if Message.CharCode = VK_RETURN then begin
SelectNext(Screen.ActiveControl, not Bool(GetKeyState(VK_SHIFT) and $80), True);
Message.Result := 1;
end else
inherited;
end;
I think there is a lot of "drop and forget" components to do it, for example on http://Torry.net
For instance, such a component was part of RxLib and later was inherted in JediVCL with TJvEnterAsTab name.
I have an Edit Control on a DevExpress Ribbon of type TcxBarEditItem which I am recording the keypresses of to update a "floating" listbox of possible functionality to fire.
For some reason, the TcxBarEditItem and it's parent classes' event handler's do not work at all like Delphi's vanilla equivalents, meaning I have to record these keypresses.
My question however, is how to record/or prohibit, the user doing things like pasting in loads of text, or highlighting and deleting loads of text?
The way in which these controls seem to work means using String(TcxBarEditItem(control).EditValue) (which is how I would access the control as it is a member of a Command class - TS8RibbonCommand) isn't actually indicative of the text in the edit control until the user clicks out of it. I've tried doing loads of things like programmatically setting focus elsewhere and refocusing but nothing else seems to work bar recording the keypresses.
In the code snippet mirroredJumpStart is my copy of what the user is typing. The RefreshJumpStart function takes a string value and iterates over all the different string values in a list and populates a Listbox using AnsiContainsString.
procedure TS8RibbonJumpStartEdit.KeyPress(Sender: TObject; var Key: Char);
begin
if (Key in ['a'..'z']) or (Key in ['A'..'Z']) or (Key in ['0'..'9']) or (Key = ' ') then
manager.mirroredJumpStart := manager.mirroredJumpStart + Key
else if (Key = Chr(VK_BACK)) and (Length(manager.mirroredJumpStart) <> 0) then
Delete(manager.mirroredJumpStart, Length(manager.mirroredJumpStart), 1);
manager.RefreshJumpStart(manager.mirroredJumpStart);
end;
Any help would be great!
Assuming this is a standard TEdit control...
You can limit the amount of text by using MaxLength property
You can catch individual keystrokes by observing Key parameter in the event OnKeyDown
Just a couple little tips, not sure if it will help, because you don't say you're using the TEdit
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;
I recently added a feature to a large application written in Delphi (version 2009) that allows the user to drag files from Windows explorer and drop them on a TcxGrid control. I achieved this via the common method of attaching my own window proc to the grid and intercepting the WM_DROPFILES message:
originalGridWindowProc := cxGrid.WindowProc; // remember the old one
cxGrid.WindowProc := GridWindowProc; // assign the new one
DragAcceptFiles(cxGrid.Handle, LongBool(True)); // setup to accept dropped files
I now am trying to enhance this feature to detect when the user drops a file onto an existing row in the grid, which will begin the process of overwriting an existing file with a new version.
My first thought was to see if the grid control's mouseover event would work. It does, but not during the drag operation.
I then used a program called Winspector to see what messages were being sent to the grid control as the mouse is moved over the grid, and I can now detect what row the mouse is over and highlight it. I'm using the same technique as above, but in this case I am overriding the window proc for the GridSite and not the grid itself, because that is where the messages appear to be going according to Winspector:
originalGridSiteWindowProc := cxGrid.ActiveView.Site.WindowProc;
cxGrid.ActiveView.Site.WindowProc := GridSiteWindowProc;
Here is the body of GridSiteWindowProc:
procedure Tfrm.GridSiteWindowProc(var message: TMessage);
var
hitTest: TcxCustomGridHitTest;
gridRecord: TcxCustomGridRecord;
begin
//Log(IntToStr(message.Msg));
case message.Msg of
WM_NCHITTEST: begin
hitTest := cxGrid.ActiveView.GetHitTest(cxGrid.ScreenToClient(Mouse.CursorPos));
if hitTest is TcxGridRecordCellHitTest then begin
gridRecord := TcxGridRecordCellHitTest(HitTest).GridRecord;
if Not gridRecord.Focused then
gridRecord.Focused := True;
end;
originalGridSiteWindowProc(message);
end
else
originalGridSiteWindowProc(message);
end;
end;
As you can see, I'm trapping the WM_NCHITTEST message to achieve this. According to Winspector, this message also gets sent to the grid site window during the drag operation, but if I uncomment that Log() statement which will output the message value to a string list (which I manually dump to a memo field afterwards), I have determined that for some reason, I only get one or two of these messages when dragging a file over the grid.
Now - here's the interesting part: if I have Winspector running and monitoring messages going to that window, I suddenly start getting all the WM_NCHITTEST messages during the file drag operation. This is also the case if I output the integer value of all the messages coming to the window proc directly to a separate log window instead of to a string list buffer first. I am hoping that someone will be able to offer some clue as to why this is happening or how to get this to work.
Rather than using the WM_DROPFILES message, you should use OLE Drag'n'Drop. Look at the RegisterDropTarget API. You can get more detailed information about where a drag or drop is taking place. You can also accept more kinds of drag objects.