I have the code which shows a search form for a specific DBGrid which is placed in another form (the caller Form of TSearchGridForm):
procedure TSearchGridForm.FormDeactivate(Sender: TObject);
begin
// Pseudo
if NewActiveControl <> CallerForm.DBGrid then
Close;
end;
The TSearchGridForm is activated by the caller form with .Show (not Modal) and when it is deactivated I want to close/hide it only if the new active control <> CallerForm.DBGrid.
Only if the user clicked on DBGrid on the caller form the search form should remain visible, otherwise I need to close it.
How can I do this?
Delphi's TScreen object has events OnActiveControlChange and OnActiveFormChange. You can set up event handlers for these to monitor changes and react to them.
See the D7 Online Help for more info. There are Delphi VCL code examples of using both events.
Related
I have a modal form (A) that shows another modal form (B). B displays a dataset and allows the user to interact with it. My problem is that one action requires that A becomes again the focused form so the user can input certain values without closing B. I have tried A.BringToFront and A.SetFocus and it indeed is shown at front, but the input focus remains in B and any click or the like in A results in the windows "ding" when you click where you should not. The code is some how like
A.ShowModal;
.
.
. inside an event of A:
B.ShowModal();
.
.
. inside an event of B:
someobject.someMethodThatRequiresAFocused;
My guess is that some obscure and strange API call could make A modal again ¿Any ideas?
Regards
When a modal form is shown, all currently visible forms including other modal forms are disabled. As such, it is not possible to switch between multiple modal forms. You need to re-think your UI design so that B does not go back to A for new input. At the very least, you could have B open a new modal form C that prompts the user for just the needed values and gives them to B, and then either B or C can update A with the new values afterwards.
There's no API that toggles modality between windows. In any case the API you're looking for your case is EnableWindow. That's how modality works, windows other than the one that the user should be working with are disabled so that he/she cannot interact with them. This is also the reason for the 'ding' sound, to provide feedback to the user.
So while letting the user work with a window that's been disabled in favor of another modal window is technically easy, handling states may not be straight forward. I present a bare minimum example below for what it would seem to take.
'FormB' first. Lets suppose you pass a reference of 'FormA' in the 'Owner' parameter while 'FormA' is constructing 'FormB'. The below is what the code that should make 'FormA' modal again could look like:
procedure TFormB.BtnMakeFormAModalAgainClick(Sender: TObject);
begin
Enabled := False; // so that 'A' will behave like it's modal
EnableWindow(TFormA(Owner).Handle, True); // so that 'A' could be interacted
TFormA(Owner).SetFocus;
end;
When this code runs, what happens is 'FormA' is enabled and brought to front, and 'FormB' is disabled - will produce a 'ding' when clicked on.
But we're not done yet. Because we have modified the meaning of modality - now we don't want 'FormA' to be closed when the user is done with it. Below is how could the code in 'FormA's unit could look like:
type
TFormA = class(TForm)
BtnShowModalB: TButton;
BtnOk: TButton;
procedure BtnShowModalBClick(Sender: TObject);
procedure BtnOkClick(Sender: TObject);
private
FModalB: TForm;
end;
implementation
uses
unitOfFormB;
{$R *.dfm}
procedure TFormA.BtnShowModalBClick(Sender: TObject);
begin
FModalB := TFormB.Create(Self); // so that FormB can find FormA from the Owner
FModalB.ShowModal;
FModalB.Free;
FModalB := nil; // Need this if we're going to decide if FormB is showing
// by testing against this reference
end;
procedure TFormA.BtnOkClick(Sender: TObject);
begin
if Assigned(FModalB) then begin // is FormB the actual modal form?
EnableWindow(Handle, False); // disable this form so it would 'ding'
FModalB.Enabled := True; // enable FormB, so user can interact with it
FModalB.SetFocus;
ModalResult := mrNone; // don't close, FormB is the first one to be closed
end else
ModalResult := mrOk;
end;
I'm nearly positive that this example is not complete, but here's the API that you're looking for.
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.
I have a MDI main (parent) form and a MDI child form. I create the child at runtime like this:
VAR
FrmDereplic: TFrmDereplic;
procedure TMainFrm.Button2Click(Sender: TObject);
begin
FrmDereplic:= TFrmDereplic.Create(MainFrm);
FrmDereplic.Show;
end;
Steps to reproduce the error:
I start the app, I press the button to create the child, I press the 'x' button on main (parent) form to close the application and I get an "Cannot create form. No MDI forms are currently active" error.
The line on which the error appears is in the child form:
procedure TFrmDereplic.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:= caFree;
end;
procedure TFrmDereplic.FormDestroy(Sender: TObject);
VAR MyIniFile: TCubicIniFile;
begin
MyIniFile:= TCubicIniFile.Create(AppINIFile);
TRY
with MyIniFile DO
begin
if WindowState<> wsMaximized then
begin
// save form's screen pos
...
end;
WriteInteger ('Dereplicator', 'fltExtensions', fltExtensions.ItemIndex); <----- HERE
FINALLY
FreeAndNil(MyIniFile);
END;
end;
I save lots of form's properties (and other's controls properties) to the INI file. But it only fails when I try to save fltExtensions.ItemIndex (which is a TFilterComboBox). If I comment that line it works perfectly.
Any idea why it tries to create a form when I actually closed the application?????????
I look on some web sites and just found the problem. It looks like it is preferably to have the Owner set to Application, instead of the main form. Remy Lebeau suggests that the real problem is in the OnDestroy of the the child form. There is no valid handle to the window that holds the filter then the OnDestroy is called. So, changing the destruction order gives a chance to TFrmDereplic.OnDestroy to execute properly.
So, here is the solution:
SOLUTION(S)
FrmDereplic:=
TFrmDereplic.Create(Application);
or
Do not save form's properties in
OnDestroy
The second one requires few extra lines of code as the OnClose even is not always called.
This was extracted from Delphi HELP:
Note: When the application shuts
down, the main form receives an
OnClose event, but any child forms do not receive the OnClose event.
If you use Application.Terminate, then onCloseQuery and onClose will not be called. Same for Halt (but... this is way too extreme, right?).
The error occurs when reading the fltExtensions.ItemIndex property because it requires fltExtensions to have an HWND, which requires its parent TFrmDereplic form to have a HWND, which requires the project's MainForm to have an HWND. But the app is in a state of shutdown, and the MainForm cannot allocate its HWND anymore, so TFrmDereplic raises an exception when it cannot obtain an HWND for itself.
Saving your INI data in the form's OnDestroy event is too late. You need to the OnClose event instead.
If the code you provided in your question is the real one then I guess the error is in this line:
FrmDereplic:= TFrmDereplic.Create(TMainFrm);
I never tried this and I am not sure if the compiler really buys it (can't test it now), but you are trying to set a class as owner of the MDI child form. Instead of that you should do either
FrmDereplic:= TFrmDereplic.Create(Application);
or
FrmDereplic:= TFrmDereplic.Create(self);
The first option sets the application as owner of the MDI child form, while the second one sets the instance of the MDI main form as owner.
Hope that helps. :-)
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.
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.