I have a modaldialog with an OK and a Cancel button. For the OK I set the Default property to True, and for the Cancel button the Cancel property. ModalResult is set to mrOK and mrCancel, resp.
However neither pressing the Enter nor the Esc key on my keyboard close the dialog. What did I miss here?
edit
I posted a small test application using the suspect dialog on my site. IDE is RAD Studio XE3.
From your posted example you can see that the TSpinEdit control is focused and captures the keys.
To close the modal form in all cases, set form KeyPreview to true and insert this into the OnKeyPress event:
procedure TSelectDlg.FormKeyPress(Sender: TObject; var Key: Char);
begin
if (Key = Char(vk_escape)) then // #27
CancelBtn.Click
else
if (Key = Char(vk_return)) then // #13
OkBtn.Click;
end;
For the record, this should work. However, it seems that TSpinEdit has a bug. Since TSpinEdit is a sample (Vcl.Samples.Spin.pas, note the "Samples"), you can fix this yourself.
To TSpinEdit, add the following method declaration just following WMCut:
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
Complete the class (Shift+Ctrl+C) and add the following code to WMGetDlgCode:
procedure TSpinEdit.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
inherited;
Message.Result := Message.Result and not DLGC_WANTALLKEYS;
end;
That will tell VCL that the edit control doesn't want to process the Enter and Escape keys (VK_ENTER, VK_ESCAPE). Since it doesn't process the keys, they'll be forwarded to the buttons, which will then be invoked base on their settings (Default & Cancel).
Feel free to report this at Quality Central
Related
Say you have a form with a TListView, with MultiSelect enabled. Normally you have to press shift or control to select multiple items. If I'd like to have the listview select/de-select additional items with only a mouse-click, how do I do that?
Like if you click item1 and then item3 both would be selected, and then if you click item1 again, it would leave only item3 selected.
I don't see any built in properties in the Object Inspector that look relevant. Do I need to modify my ListviewOnMouseDown or ListviewOnSelectItem to change the selection?
This kind of selection is not implemented in listview controls, maybe because they support checkboxes which can be independently checked, I don't know...
You have to modify the behavior yourself. But using OnMouseDown or OnSelectItem events are not really appropriate, as the selection have already been carried out by the time they are fired. Below example intercepts left mouse button down message.
type
TListView = class(vcl.comctrls.TListView)
protected
procedure WMLButtonDown(var Message: TWMLButtonDown);
message WM_LBUTTONDOWN;
end;
procedure TListView.WMLButtonDown(var Message: TWMLButtonDown);
begin
Message.Keys := MK_CONTROL;
inherited;
end;
You can intercept the message by any other means, assigning to WindowProc, deriving a new control... Of course you can also implement the behavioral change conditionally, or would like to test and preserve other virtual keys/buttons. See documentation in that case.
A ListView does not natively support what you are asking for. You will have to maintain your own list of "selected" items, using the OnClick or OnMouseDown event to detect which item the user is clicking on so you can toggle the contents of your list accordingly and then reset the ListView's selection to match your updated list as needed.
Set the ExtendedSelect property of the ListView to False.
Update:
The ListView does not have an ExtendedSelect property. It is only available for the ListBox.
But it would be possible to add that to the ListView. Here's an improved version of the code posted by Sertac that adds ExtendedSelect. I also improved it so it is a bit more user friendly than the original because it keeps the shift key for multi selection working. (I hope I may post that improved version here, it is a bit easier to read than in my comment).
type
TListView = class(Vcl.ComCtrls.TListView)
private
FExtendedSelect: Boolean;
procedure SetExtendedSelect(const Value: Boolean);
protected
procedure WMLButtonDown(var Message: TWMLButtonDown);
message WM_LBUTTONDOWN;
public
property ExtendedSelect: Boolean read FExtendedSelect write SetExtendedSelect;
end;
procedure TListView.SetExtendedSelect(const Value: Boolean);
begin
FExtendedSelect := Value;
end;
procedure TListView.WMLButtonDown(var Message: TWMLButtonDown);
begin
if not FExtendedSelect then
begin
if Message.Keys and MK_CONTROL <> 0 then
Message.Keys := Message.Keys and (not MK_CONTROL)
else if Message.Keys and MK_SHIFT = 0 then
Message.Keys := MK_CONTROL;
end;
inherited;
end;
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.
In my Delphi form's OnShow method, I determine that a dialog must be opened automatically once the form is opened - and I should be able to do this by simulating a click on a menuitem.
However, calling menuitem.Click brings up the dialog before the main form has opened - which is not what I want.
I expect that should do what I want, but I cannot find what parameters to pass for "wparam" to send the click to my menuitem.
PostMessage(handle, WM_COMMAND, wparam, 0)
The MSDN WM_COMMAND docs talk about IDM_* identifiers, but how does that appear in Delphi?
(I know this is a very old question but despite being resolved in some way the real question has really gone unanswered.)
--
The command item identifier of a 'TMenuItem' is in the Command property. According to WM_COMMAND's documentation the high word of 'wParam' would be '0' and the low word would be the menu identifier;
PostMessage(Handle, WM_COMMAND, MakeWParam(MyMenuItem.Command, 0), 0);
or simply;
PostMessage(Handle, WM_COMMAND, MyMenuItem.Command, 0);
With a popup menu item there would be a slight difference: the VCL handles popup menus' messages with a different utility window. The global PopupList variable has the handle to it in its Window property;
PostMessage(PopupList.Window, WM_COMMAND, MyPopupMenuItem.Command, 0);
Perhaps you can try to open the dialog in the OnActivate event ?
I am not really sure if the OnActivate gets fired again other than when the form is shown but if it does you can use :
procedure TForm1.FormActivate(Sender: TObject);
begin
Form2.ShowModal;
Self.OnActivate := nil;
end;
Wouldn't you have to do this with a one-time timer, if you want the form to appear as per a normal Show/ShowModal, get drawn (etc) fully, and then immediately do something else?
tmrKickOff : a TTimer, 100 ms interval, disabled at design time,
fires off a 'tmrKickOffTimer' event.
in form create,
tmrKickOff.Enabled:=false; //just in case something happened in IDE
in form show, at end of all other stuff;
tmrKickOff.Enabled:=true;
in tmrKickOffTimer
begin
tmrKickOffTimer.Enabled:=false;
menuItemClick(nil);
end;
with apologies for style, form and any error-trapping :-)
Alternatively, handle the Application.OnIdle event with something along the lines of ...
if not DialogDone then
begin
MyDialogForm.ShowModal; // or menuItem.Click ....
DialogDone := true;
end;
OnIdle won't fire (for the first time) until the Form is shown and the message queue is empty.
I don't think you can send a message directly to your menu item, but you can just post it to the main window and show your dialog from there. I do this and it works great so that the dialog box (in my case, a login prompt) appears on top of the main window to avoid confusion.
-Mark
procedure WMPostStartup(var Message: TMessage); message WM_POSTSTARTUP;
procedure TMainForm.WMPostStartup(var Message: TMessage);
begin
Self.Refresh;
// Now show the dialog box.
end;
One method I have used, which is very simular to MarkF's solution, is to create a new user defined message type and send a message using that type to yourself when you determine that you need to perform this other process after your main form displays:
const
wm_SpecialProc = wm_User + 1;
procedure TForm1.WMSpecialProc(var Message:tMessage); message wm_SpecialProc;
begin
Form2.ShowModal;
end;
procedure TForm1.OnShow(Sender:tObject);
begin
if true then
PostMessage(Application.MainForm.Handle,wm_SpecialProc,0,0);
end;
The nice thing about this method is that you are in control of the message generation, so can populate ANY lparam or wparam you want to later use by your handler. I sent the message directly through the application.mainform but you could also just say handle for the current form.
In code I have developed some years ago I have been using this a lot to close the current form on pressing the Escape key at any moment:
procedure TSomeForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
if key = #27 then close;
end;
This behaviour is defined for the TForm. The form's KeyPreview property is be set to True to let the form react to key presses before any other components. It all works perfectly well for the best part of the program, however, when the Escape key is pressed while a TEdit component is focused a sound (a ding sound used by Windows to signify invalid operation) is issued. It still works fine but I have never quite managed to get rid of the sound.
What's the problem with this?
Steps to recreate:
new VCL Forms application, set the form's KeyPreview to true
on the Events tab double-click the onKeyPress event and enter dummy code:
if key=#27 then ;
add a TListBox, TCheckBox, TEdit to the form and run the application
in the application try pressing Esc and NOTHING happens, as specified by the dummy code
focus the TEdit and press Esc. Nothing happens but the sound is played.
You get the ding because you left the ESC in the input. See how Key is a var? Set it to #0 and you eliminate the ding. That removes it from further processing.
procedure TSomeForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
if key = #27 then
begin
key := #0;
close;
end;
end;
KeyPreview is just that, a preview of what will be passed to the controls unless you stop it.
Starting from Jim's answer (thanks Jim) I had to make it work for me. What I needed was to make a dropped down combobox close keeping the selected item and move to the next/previous control when TAB/shift+TAB was pressed. Everytime I did press TAB the annoying sound filled the room. My work arroud was using onKeyDown event to catch the shiftstate, declaring var aShift: boolean; in form's interface and use the following code:
procedure TForm2.StComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if ssShift in Shift then aShift := true else aShift := false;
end;
procedure TForm2.StComboKeyPress(Sender: TObject; var Key: Char);
begin
if Key=char(VK_TAB) then
begin
Key := #0;
StCombo.DroppedDown := false;
if aShift
then previousControl.SetFocus
else nextControl.SetFocus;
end;
end;
Using the menu items and setting them to invisible, and using the shortcut, is a quick workaround that I've just stumbled across, but won't work if you need a shortcut that uses a character that is used in the first letter of an existing shortcut: For example for Alt+ENTER, you need to add something like this to the form create procedure:
MainMenu1.Items[0].ShortCut:=TextToShortCut('Alt+e');
However it's probably easier to use TActionList instead, and even though something like Alt+E is not listed you can add it.
It's an old thread... but anyway, here's a far better one: catching Alt-C!
Unlike ESC, Alt-C isn't serviced by KeyPress, so setting Key to #0 in KeyPress doesn't work, and the horrendous "ding!" is issued every time.
After hours of trying, here's the workaround I found:
- create a main menu option to service the request
- set its ShortCut to Alt+C - yes indeed, that is NOT one of the available ShortCut choices(!!)... but it does work anyway!
- do the processing in that menu option's OnClick
- you may even make in "in the background": you may set the menu option's Visible to false - as long as its Enabled stays true, it will be activated by Alt-C even though it will not be visible in the menu.
Hope that may help! And if you have something more elegant, please advise.
In one part of the application I'm working on, there is a form control that does validation on reception of the CMExit message, which is exactly how the Delphi documentation says to do it (this code sample is from the Delphi Help files):
procedure TDBCalendar.CMExit(var Message: TWMNoParams);
begin
try
FDataLink.UpdateRecord; { tell data link to update database }
except
on Exception do SetFocus; { if it failed, don't let focus leave }
end;
inherited;
end;
The purpose of this is to perform the validation as soon as the control loses focus. So, for example, if I were to click on the OK button, the form control would lose focus, this method would run and on an exception would set the focus back to that form control. (Thus the "click" event on the OK button would never go through and the dialog would never close).
The problem I'm having is that this form control is inside a modal dialog window. Clicking OK does indeed send the CMExit message and causes the record to update (and validation to occur). However, pressing Enter while in the form control causes the modal dialog to close without sending the CMExit message. It's as if the form control never "loses focus". This means that not only does the dialog close without the form actually validating the data, but the data set isn't updated, either.
Given this problem, where is the best place for me to place my dataset updating/validation code? I could move it up to the dialog form itself and implement it in the OnCloseQuery handler, but this would mean that the logic is duplicated in both the form control and on the form itself. (The form control is used in other places, and I want to avoid changing its behaviour).
(I speculate that CMExit isn't triggered because the control never actually does lose focus. The form is closed, but the form control still "has focus" on the closed form.)
Closing a form does not necessarily fire the on-exit event of a TControl. The user could hit Alt-F4, for example.
I'd suggest moving the validation to a separate proc, and call that separate proc from both the on-exit and on-close events.
The below code should work without too much modification:
function TDBCalendar.UpdateSuccessful: boolean;
begin
{ tell data link to update database }
{ if successful, return True, else return False }
{ this function must be Public if a form is gonna check this value }
Result := True;
try
FDataLink.UpdateRecord;
except on Exception do
Result := False;
end;
inherited;
end;
procedure TDBCalendar.CMExit(var Message: TWMNoParams);
begin
//if not valid, then don't let them leave
if not(UpdateSuccessful) then begin
SetFocus;
end;
end;
///////////////////////////////////////////
//on the form that contains your control...
///////////////////////////////////////////
procedure TMyForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
//if not valid, then don't let them close the form
if not(dbcal.ControlIsValid) then begin
Action := caNone;
end
else begin
inherited;
end;
end;