This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Delphi application with login / logout - how to implement?
I am trying to switch between two forms in my delphi application, first, a login screen appears and then the main form of the application appears.
I am using formx.hide and .show to switch between the forms.
eg.
//after password checking
form1.hide;
form2.show;
The second form appears, but cannot be interacted with, as if it is disabled.
Why would it be doing this?
Since you have not provided any code, we have to guess at what your problem is. So here goes.
Forms get disabled when other forms are shown modally, and then re-enabled when the modal form is closed. So most likely you show the login form modally and then hide it rather than close. To close the modal form you need to set the modal form's ModalResult property. If you hide rather than close, then the main form will still be disabled. The key is that you must properly close the modal form before the main form can become usable.
Typically for an app with an initial login form you organise your application's .dpr file like this:
var
LoginForm: TLoginForm;
MainForm: TMainForm;
LoginSucceeded: Boolean;
begin
Application.Initialize;
LoginForm := TLoginForm.Create(nil);
try
LoginForm.ShowModal;
LoginSucceeded := LoginForm.Successful;
finally
LoginForm.Free;
end;
if LoginSucceeded then
begin
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end;
end;
The first form created using Application.CreateForm becomes your applications's main form. When the main form is closed, the entire application goes down with it. In my opinion, you should use Application.CreateForm only for creating the main form. Any other forms can be created using the TMyForm.Create syntax. If you follow that policy then you don't need to worry about which order your forms are created in.
Is the main form actually the first form to be created? The first form to be created with Application.CreateForm (check the source of your .dpr file), is regarded the main form. Closing that form essentially closes the application.
Related
I use embedded forms in my application and I was trying something like this:
At designtime the form is a normal form which don't know anything about embedding/docking.
With a ButtonClick I can make the form visible by calling the Show() method of the form.
At runtime it can happen that the form gets docked into another form and becomes an embedded form.
When I press the Button for showing the form again (which is now embedded) nothing happens because
I have to call Show() for the form which contains the embedded form now.
I am lookin for a method to force showing the parent form when the Show() method of the embedded form is called. I could handle this by checking the existance of a parent form before calling Show() but I don't want to include this specific handling.
I would prefer to do this handling in the parent form wich gets notified when a form is docked.
The Show() method only sets the Visible property to true (RAD Studio Help), so I don't think that a message is fired ...?
Do you know a method to realize something like this?
Edit
I want to put some information about my application because I guess that it is more a designing problem than a programming problem.
The application uses several plugins to adapt to the connected hardware. The exe provides a drag&dock environment and contains a base class for dockable forms. The plugins don't have any knowledge about the docking implementation. By creating a dockable form and embedding a form from the plugin the plugin form becomes dockable. This is the reason why I want do get the parent form shown when somewhere the method Show() of the embedded form is called.
You could create a common ancestor for your embedded forms or even for all forms in the application, and then derive your forms from it:
type
TEmbeddedForm = class(TForm)
public
procedure Show;
end;
procedure TEmbeddedForm.Show;
var
ParentForm: TCustomForm;
begin
inherited Show;
ParentForm := GetParentForm(Self);
if ParentForm <> Self then
begin
// Form is Embedded
Update;
ParentForm.Show;
// You might alternatively consider to send custom
// message to the Parent form, and let it decide what to do e.g.
// SendMessage(ParentForm.Handle, CM_MY_EMBEDED_FORM_SHOW, 0, 0);
end;
end;
Then when you call:
procedure TForm1.Button1Click(Sender: TObject);
begin
// MyEmbeddedForm is derived from TEmbeddedForm
MyEmbeddedForm.Align := alClient;
MyEmbeddedForm.BorderStyle := bsNone;
MyEmbeddedForm.Parent := Form3.Panel1;
MyEmbeddedForm.Show;
end;
Form3 is showing.
Another option (which I only confirmed with Spy++) is to intercept WM_CHILDACTIVATE or WM_WINDOWPOSCHANGING in the TEmbeddedForm. it is sent to it when the child form is calling TEmbeddedForm.Show, and act accordingly i.e. GetParentForm(Self).Show.
Use SetFocus instead of Show on the embedded form.
This will enforce showing of the parent form too.
How can I solve this problem "Cannot Create Forms. No MDI forms are currently active". I want to make a simple program that wod require a Login Form before it can acess the main form. I got three forms: Main Form (MDI Form), Form2 (MDIChild) and Login Form (Normal). Login form would appear first then the Main Form, then when I try to call on Form 2 from the Main form, an error would display "Cannot Create Forms. No MDI forms are currently active".
I am using Delphi 7 on windows XP. I'm a beginner. Thank you very much sir.
It sounds like you're letting your LoginForm be auto-created, and it's being created first. This won't work, because the first form created by Application.CreateForm in the project file becomes the Application.MainForm. In order by be an MDI application, the MainForm must be a MDI parent window.
The solution is usually to not auto-create your login form, and instead create it yourself. To do so, you need to edit your .dpr file (Project->View Source from the IDE's main menu).
Your project source should look something like this now (obviously, using your classes in the Application.CreateForm calls):
begin
Application.Initialize;
Application.CreateForm(TLoginForm, LoginForm);
Application.CreateForm(TMainForm, MainForm);
Application.CreateForm(TChildForm, ChildForm);
Application.Run;
end.
You need to modify it so that the LoginForm isn't created first.
var
LoginOK: Boolean = False;
begin
LoginForm := TLoginForm.Create(nil);
try
// Show login form. When it closes, see if login worked.
LoginForm.ShowModal;
LoginOK := LoginForm.CanLogin; // However you know login is OK or not here
finally
LoginForm.Free;
end;
if not LoginOK then
Halt; // Login failed - terminate application.
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
{
I normally do not auto-create anything but the main form
and maybe a datamodule (which you **can** autocreate first -
it is not a form), but a MDI application is pretty useless
without at least one open child window, IMO.
}
Application.CreateForm(TChildForm, ChildForm);
Application.Run;
end.
I would like to have a seperate form that shows "along" with my main form, so it does not overlap the main form.
Here's an example:
Notice how the main program, overlaps the log? I can't figure out how to do that in Delphi.
Thanks!
The answers to this question lie in the very useful Window Features MSDN topic.
The pertinent information is:
An overlapped or pop-up window can be
owned by another overlapped or pop-up
window. Being owned places several
constraints on a window.
An owned window is always above its owner in the z-order.
The system automatically destroys an owned window when its owner is
destroyed.
An owned window is hidden when its owner is minimized.
The main form in your app is the owner (in Windows terminology rather than Delphi terminology) of the other popup windows. The first bullet point above implies that the owned windows always appear above the main form (the owner).
Try creating an app with 3 forms and show them all. The .dpr would look like this:
program OwnedWindows;
uses
Forms,
Main in 'Main.pas' {MainForm},
Popup1 in 'Popup1.pas' {PopupForm1},
Popup2 in 'Popup2.pas' {PopupForm2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, Main);
Application.CreateForm(TPopupForm1, PopupForm1);
Application.CreateForm(TPopupForm2, PopupForm2);
PopupForm1.Show;
PopupForm2.Show;
Application.Run;
end.
You will see that the main form is always underneath the other two forms, but these other owned forms can be above or below each other. When you minimize the main form they all disappear.
You could if you want make all of your forms top-level unowned windows:
procedure TPopupForm1.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := 0;
end;
And like wise for TPopupForm2 in my example. This would result in all 3 windows having taskbar buttons.
One other approach is to revert to the pre-Vista way of things and make the Application's hidden window be the top-level owner window. You do this by making sure that Application.MainFormOnTaskbar is False. Skip all the CreateParams code and you'll now have a single window on the taskbar and any of your windows can be above any other because the top-level owner window is the hidden window Application.Handle. Of course the downside is that you lose your Aero Peek.
So, I guess what you need to do is to make the main form appear on the taskbar as usual, but ensure that the other forms are not owned (in the Windows sense) by the main form. But they need to be owned to avoid having them in the taskbar. So you can make the hidden application window be the owner using the CreateParams method, like so:
procedure TOverlappedPopupForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := Application.Handle;
end;
Although you state otherwise in the comments, when I do this I find that the popup form is indeed hidden when I minimize the main form. And it is shown again when the main form is restored. Thus I think this does solve your problem completely.
I haven't got Delphi open now, but would setting
mainform.formstyle := fsStayOnTop
and show the child form with
childform.show;
work?
or else try using SetWindowPos() and setting the hWndInsertAfter property to something like HWND_TOPMOST on the main form
I keep running into this strange problem. It almost seems to be random but I run my application and open a form, do some work with it and close it. The next time I go to open another isntance of that form I get an error message about the form already existing. This is very odd and I haven't been able to constantly reproduce the error.
If it helps I'm using Delphi 6 still. Is there some known reason why this is happening or what I can do to prevent it?
Are you sure the form is not being hidden when it is closed?
That is the default for MDI forms, but I have seen other people do the same (to speed up re-showing the form).
Edit (thanks Cosmin Prund for the comment!):
Hook the OnClose event of your form, and look what the value of the CloseAction parameter is. If it is caHide, then the form is hidden.
A temporary hack might be to assign caFree to the CloseAction, but a better solution is to watch the stack in your OnClose event handler to see how you ended up there, and what is causing the CloseAction to be caHide in the first place.
Note: in these situations you often want to see what the VCL does. So it is wise to enable the debug DCUs for your project; see this blog article how to do that (search for debug DCUs in the link).
--jeroen
You haven't provided the code, but it seems you are giving both form instances the same component name, and the owner of both forms is the same (probably Application object).
You cannot have components with the same name owned by another component. You should either give different names to each form instance, or just don't give any value to Name property, and let RTL choose a unique component name for your newly created instances.
If this is not the case with you, please provide the code by which you create your form instances, so that we can check what else might be wrong with the form.
function ShowOnce( AFormClass:TFormClass;AShowing:Boolean=True):TForm;
var
i : integer;
begin
Result := nil;
for i := 0 to Screen.FormCount -1 do
if Screen.Forms[i] is AFormClass then Result := Screen.Forms[i] as TForm;
if not assigned(Result) then
Result := AFormClass.Create(Application.MainForm); // Application or a parameter
if Showing then
Result.Show;
end;
sample 1:
ShowOnce(TForm3);
sample 2:
Form3:=ShowOnce(TForm3,False) as TForm3;
Form3.SomeProperty:=32;
Form3.Show;
We have a Delphi 6 application that uses a non modal form with in-grid editing. Within the FormClose event we check that the entries are square and prevent closure if they're not.
However, if the user clicks on the main form behind then the original form disappears behind (as you'd expect) but this allows the user to move to a new record on the main screen, without their changes in the grid having been validated.
I've tried the FormDeactivate event, which does fire but doesn't seem to have any mechanism to prevent deactivation (unlike the FormClose events Action parameter).
I tried the OnExit from the grid, but it doesn't fire on deactivation.
I tried trapping the WM_ACTIVATE message and setting Msg.Result = 1 but this has no effect (possibly because another WM_ACTIVATE message is being sent to the main form?).
So, I'm looking for ideas on how to (conditionally) prevent the deactivation of a form when the user clicks on another form.
(PS I don't want to change the form style to fsStayOnTop)
Thanks
A classic rule in Windows is that you can't change the focus during a focus-changing event. The OnDeactivate event occurs during a focus-changing event. Your form is being told that it is being deactivated — the OS is not asking permission — and at the same time, the other form is being told that it is being activated. Neither window has any say in the matter, and attempting to change the focus while these events are going on will only get all the windows confused. Symptoms include having two windows painting themselves as though they have focus, and having keyboard messages go nowhere despite the input cursor blinking. MSDN is even more dire, although I've never witnessed anything that bad:
While processing this message [WM_KILLFOCUS], 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.
Since you can't deny a focus change after it's already started, the thing to do is to delay handling of the event until after things have settled down. When your editing form gets deactivated and the data on it isn't valid yet, post the form a message. Posting puts the message on the end of the message queue, so it won't get handled until all previous messages — the focus-changing notifications in particular — have already been handled. When the message arrives, indicate that data is invalid and set focus back to the editing form:
const
efm_InvalidData = wm_User + 1;
type
TEditForm = class(TForm)
...
private
procedure EFMInvalidData(var Msg: TMessage); message efm_InvalidData;
end;
procedure TEditForm.FormDeactivate(Sender: TObject);
begin
if DataNotValid then
PostMessage(Handle, efm_InvalidData, 0, 0);
end;
procedure TEditForm.EFMInvalidData(var Msg: TMessage);
begin
Self.SetFocus;
ShowMessage('Invalid data');
end;
I should point out that this answer doesn't technically answer your question since it does nothing to prevent form deactivation, but you rejected my other answer that really does prevent deactivation.
When you call ShowModal, all the forms except the one being shown get disabled. They're re-enabled just before ShowModal returns.
Display your editing form nonmodally, and when data starts being edited, have the form make itself modal by disabling the other form. Enable the other form when editing is complete. Apparently, disabling windows isn't always quite as simple as setting the Enabled property. I'd suggest using DisableTaskWindows, but it would disable all windows, including your editing form. Nonetheless, take a look at how it's implemented in Forms.pas. It keeps a list of all the windows it disables so that only they get re-enabled afterward.
Delphi 2006 introduced OnMouseActivate events. The main form's OnMouseActivate would let you prevent the main form from being activated if the other form is visible.
This doesn't work with D6 of course.
It not a helpful answer David, but I think I would have to agree with other respondents that this is not the correct approach. There are so many ways that it can all go wrong that stopping and taking a look at a different way to solve your problem might be better.
Even if you did manage to find the event/method/message that does what you want, you would still need to be able to deal with the scenario where the electricity goes off.
On a slightly more useful note, have you tried disabling your mainform until ready? You could put all controls on a panel and then just do
panel1.enabled := false;
You may also introduce a state in your model that tracks if the window needs the focus as you describe it here and use onFocus handlers on the other forms that programmatically set the focus back to your grid window.
[Edit] Copy of my Comment:
You could register for the onShow Event of the forms with the grid. (If you implement it be sure to make it somehow configurable to minimize the grid dependency on the present layout of the application. Maybe by providing a method that is called by the forms which in turn triggers the event registration of the grid at the calling form for the onShow event)
To elaborate on the event registration:
You can attach event handlers programatically. There are plenty howtos in the net about this. I have no Delphi available here, so I can't copy working code now.
Pseudocode for programatically attaching the event!
myform.onShow=myGrid.formOnShowHandler;
formOnShowHandler has the same signature as the functions that are generated by the IDE for onShow Events. It has a parameter that you can use to figure out which form has called the handler so you can reuse the function and simply put the form in the background and show your gridform (which will be for example the parent of the grid) again.
Thanks to everyone for their help and suggestions.
Here's the solution I went with:
In the 'Grid Form' (eg Form2) ...
public
PricesNotSquare: boolean;
In the FormDeactivate event, set PricesNotSquare to true if they don't match.
In the OnActivate event of the Main Form, ...
if Assigned(Form2) and (Form2.PricesNotSquare) then
begin
ShowMessage( 'Please ensure the total Prices match before leaving the form' );
Form2.Show;
exit;
end;
// other form activate stuff here
Turned out to be a simple solution, just took a while to get it.
Seems to work fine, but if it has issues then I'll incorporate the idea of sending a message.