Have a main form (Form1) where is created a MDIForm (Form2) and a MDIChild (Form3) form respectivelly in execution time. In my tests the MDIForm (Form2) is show like expected but when try show the MDIChild (Form3) i get the following error that say:
Cannot create form. No mdi forms are currently active
Some idea about how fix this?
program Project1;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2},
Unit3 in 'Unit3.pas' {Form3};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Form:
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Unit2, Unit3;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Form2 := TForm2.Create(Self);
Form2.Show;
Form3 := TForm3.Create(Form2);
Form3.Show;
end;
end.
The VCL (not the Win32 API) is hard-coded to allow only the Application.MainForm to be set to fsMDIForm for hosting fsMDIChild Forms. Your MainForm is not the fsMDIForm parent Form, which is why you are getting the error.
Using a secondary Form as the fsMDIForm parent is technically possible, but not out of the box. It requires a bit of manual work hacking up the VCL's internals to make it work, and even then there are holes and gotchas. See my Multiple MDI Parent Forms in a single Application submission on CodeCentral for an example (I haven't updated it in over a decade, so it may need some tweaking for modern VCL versions). The old Quality Central (not Quality Portal!) ticket it refers to can be found on archive.org: #12006: Hosting MDI child forms in non-MainForm forms.
That being said, MDI is a dead technology, Microsoft abandoned it a long time ago, and modern Windows versions have poor support for MDI, especially when Visual Styles are used. You are best off not even bothering with MDI in modern software, there are other/better UI design choices available.
Related
I'm trying to automate an application (Windows 8, Delphi XE.) For my testing I'm doing the following:
Created a small test application, consisting of a form and a memo (Form1)
Added a new ActiveX Object, CoClass name TestOLE, Threading mode Apartment, Instancing Multiple (as per this article.)
Added one method Method1 which only adds some text to the memo control in Form1
I then start the application and double click on a file named test.vbs which contains the following code:
dim obj
set obj = GetObject("", "Project1.TestOLE")
obj.AddSomeText "Hola mundo"
When the application is running, I see that a new form is created, the text is added and then it exits.
What I want to accomplish is that the opened application should have its memo text changed.
I've repeated creating new projects with both MultipleInstance and SingleInstance, and in an outburst of heuristic anger, I even changed the threading model to single, to no avail.
I see two flags in the type library editor: "Replaceable" and "Aggregatable." However, selecting "Replaceable" ends up in an error in the generated RIDL file.
I've been reading a lot about GetObject. It appears that its documentation is even wrong (it says you can omit the first parameter but I've found that doesn't work).
Is this the right way to write an automation server in Delphi that can be reused?
Well, I got it working (I hope.)
Reading more of the same article cited above, found the following:
Know how to implement servers that support GetActiveObject.
Adding a global object, and registering in the Running Object Table (ROT) accomplishes the desired task of having the COM call passed to the running application:
Project file:
program TestOLEProject3;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
TestOLEProject3_TLB in 'TestOLEProject3_TLB.pas',
Unit2 in 'Unit2.pas' {TestOLE: CoClass},
Unit3 in 'Unit3.pas';
{$R *.TLB}
{$R *.res}
begin
Application.Initialize;
RegisterGlobalTestOLE;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Unit2.pas:
unit Unit2;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, TestOLEProject3_TLB, StdVcl;
type
TTestOLE = class(TAutoObject, ITestOLE)
protected
procedure Method1; safecall;
procedure Quit; safecall;
end;
implementation
uses ComServ, Unit1, Unit3;
procedure TTestOLE.Method1;
begin
Form1.Memo1.Lines.Add('Wheeee');
end;
procedure TTestOLE.Quit;
begin
RevokeGlobalTestOLE;
end;
initialization
TAutoObjectFactory.Create(ComServer, TTestOLE, CLASS_TestOLE, ciMultiInstance,
tmApartment);
end.
Unit3.pas (functions to register and unregister the global object):
unit Unit3;
interface
procedure RegisterGlobalTestOLE;
procedure RevokeGlobalTestOLE;
implementation
uses TestOLEProject3_TLB, ComObj, ActiveX;
var
GlobalTestOLEHandle: longint = 0;
procedure RegisterGlobalTestOLE;
var
GlobalTestOLE: ITestOLE;
begin
GlobalTestOLE := CoTestOLE.Create;
OleCheck(RegisterActiveObject(GlobalTestOLE, CLASS_TestOLE,
ACTIVEOBJECT_STRONG, GlobalTestOLEHandle));
end;
procedure RevokeGlobalTestOLE;
begin
if (GlobalTestOLEHandle <> 0) then
begin
OleCheck(RevokeActiveObject(GlobalTestOLEHandle, nil));
GlobalTestOLEHandle := 0;
end;
end;
end.
I would like to hide an application from the Windows 7 taskbar.
I want to make something like a toolbar on the edge of the screen which does certain things when the user clicks on it, but I don't want it to show in the taskbar, since its a thing that i want to stay in the background.
I tried the instructions in the following post, but it did not work on my application:
How to hide a taskbar entry but keep the window form
Then i tried it on a new empty VCL Forms Application and it still did not work. I searched for other solutions, but they all do very much the same like in the linked post.
Has something changed, that makes that impossible in windows 7? Or is there anything you
could think of, that could prevent it from working?
You can override the main form's CreateParam to remove the flag that forces the taskbar button (WS_EX_APPWINDOW) and additionally make the form owned by the application window. That's doing the opposite of the requirement for the shell to place a taskbar button for a window. From "Managing Taskbar Buttons":
[..] To ensure that the window button is placed on the taskbar, create an
unowned window with the WS_EX_APPWINDOW extended style. [..]
Sample:
type
TForm1 = class(TForm)
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle and not WS_EX_APPWINDOW;
Params.WndParent := Application.Handle;
end;
Don't change the state of MainFormOnTaskbar property of the 'Application' from its default 'True' if you use this method.
You can also remove the second line (..WndParent := ..) and instead set PopupMode of the form to pmExplicit in the object inspector to same effect.
BTW, here's the documentation quote from the same topic for the solution TLama posted:
To prevent the window button from being placed on the taskbar, [...]
As an alternative, you can create a hidden window and make this hidden
window the owner of your visible window.
When you set MainFormOnTaskbar to false, the main form is owned by the application window by VCL design. And if you hide the application window, the requirement is fulfilled.
Try to use a tricky way described in this article:
Set the MainFormOnTaskBar to False in your project file. Then try to hide the application window from the main form's OnShow and OnActivate event handlers. So your project might look like follows:
Project1.dpr:
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := False;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Unit1.pas:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormShow(Sender: TObject);
procedure FormActivate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormShow(Sender: TObject);
begin
ShowWindow(Application.Handle, SW_HIDE);
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
ShowWindow(Application.Handle, SW_HIDE);
end;
end.
your application main form is normally created in the dpr so open the dpr and look for the line that creates the main form.
// add this line first
// blank app title will prevent app from showing in the applications list in task manager
Application.Title := '';
// this line is already in the dpr and creates the main form, the class will differ
Application.CreateForm(TMainForm, Result);
// make the main form invisible to windows taskbar/task switcher
i := GetWindowLong(Application.Handle, GWL_EXSTYLE);
SetWindowLong(Application.Handle, GWL_EXSTYLE, i OR WS_EX_TOOLWINDOW AND NOT WS_EX_APPWINDOW);
i know this works on XP and 7. i'm guessing it's good for 8 as well. this adds the tool window flag and removes the appwindow flag so i guess if you're not interested in the toolwindow flag you can leave out the following part
i OR WS_EX_TOOLWINDOW
I built the code below using Delphi XE2. It creates Form1, and Form1 immediately creates an instance of Form2. When I press the button on Form2 a second Form2 is created.
Now if I hover the mouse over the button on this second, topmost, Form2 and wait for the tooltip to appear, the moment the tooltip appears, the first Form2 comes to the front, stealing focus.
The problem occurs only if Application.MainFormOnTaskbar is True. It also relies on the first Form2 being created from Form1's FormCreate method. If I use PostMessage() to delay the creation of the first Form2 until the application has finished initialising, the problem goes away.
I'd like to understand why this is happening. I have already learned that Delphi's Application object handles a lot of things including hint display, and I know that Delphi can recreate a window's handle during initialisation, but I haven't been able to follow this through to explain fully the behaviour described above (or indeed whether the above two facts are even relevant).
Project1.dpr
program Project1;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True; // False makes problem go away
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Unit1.pas
unit Unit1;
interface
uses
Vcl.Forms, Unit2;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
public
procedure CreateForm2;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
CreateForm2;
end;
procedure TForm1.CreateForm2;
var
frm : TForm2;
begin
frm := TForm2.Create(Application); // (Could pass Self - makes no difference)
frm.Show;
end;
end.
Unit2.pas
unit Unit2;
interface
uses
Vcl.Forms, System.Classes, Vcl.Controls, Vcl.StdCtrls, WinApi.Windows;
type
TForm2 = class(TForm)
Button1: TButton; // This button has a hint
procedure Button1Click(Sender: TObject);
end;
var
Form2: TForm2;
implementation
uses
System.SysUtils, Unit1;
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
Form1.CreateForm2;
end;
end.
The key issue here is that the first instance of TForm2 is created as window that is owned by the application window, Application.Handle. And here I am referring to the Windows meaning of owner. In VCL language this is known as the popup parent.
Now, when you create that first TForm2 instance, the Application.MainForm property is still nil. And because you did not explicitly assign PopupParent, the code in TCustomForm.CreateParams sets the owner to be the application window.
You simply do not want your windows to be owned by the hidden application window. This is the reason why that first TForm2 instance sometimes appears behind all the other windows, in particular behind your main form. It has simply been created with the wrong owner.
The form that is owned by Application.Handle gets shown in THintWindow.ActivateHint. That happens due to the line that reads ParentWindow := Application.Handle. This is followed by a call to SetWindowPos(Handle, ...) which results in the incorrectly owned form coming to the front. Presumably that form comes to the front because it is also owned by Application.Handle. Right now I don't have a clear explanation for the precise mechanism, but I don't find that terribly interesting because the form is clearly setup wrongly.
In any case, the fundamental problem is that you have created a window that is incorrectly owned. The solution therefore is to make sure that the window is owned correctly. Do that by assigning the PopupParent. For example:
procedure TForm1.CreateForm2;
var
frm : TForm2;
begin
frm := TForm2.Create(Application); // (Could pass Self - makes no difference)
frm.PopupParent := Self;
frm.Show;
end;
i have 2 Form (Form1 and Form2) in the my project, Form1 is Auto-create forms, but Form2 is Available forms.
how i can to create Form2 and unload Form1?
I received a "Access validation" Error in this code.
Here is Form1 code:
1. uses Unit2;
//*********
2. procedure TForm1.FormCreate(Sender: TObject);
3. var a:TForm2;
4. begin
5. a := TForm2.Create(self);
6. a.Show;
7. self.free; // Or self.destory;
8. end;
Thanks.
I modified that "Serg" code to this :
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses Unit2;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);
Release;
end;
end.
///
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Form1:= TForm1.Create(Application);
Application.Run;
end.
but this project start and then exit automatically, Why?
i want to show Form1 and when we click Button1 then show Form2 and free(Release) Form1. how i can to this?
When you destroy a form, it's better to use Release.
Release is almost the same as free, but it waits for pending messages to avoid crashes.
You should never use Destroy. Free/Release calls the destructor.
Self is the current object (in your code Form1, so self.Free kills the current form. Which results in the access violation. Form1 is auto created, it is also auto destroyed so you shouldn't destroy it yourself. If you don't want it, hide it.
And you should keep a reference to the newly created form if you want to handle it later.
Your modified code should be like:
uses Unit2;
TForm1 = class (TForm)
private
FChild : TForm2;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FChild := TForm2.Create(nil);
Hide; // Hides form 1
FChild.Show;
end;
procedure TForm2.FormDestroy(Sender: TObject);
begin
FChild.Release;
end;
But Why do you want to create another form in the form create of the first form. Why not remove the first form entirely and only use the second one (auto created)?
You are trying to do something strange.
You cannot free main form without closing application, so your Form1 should not be autocreated form, both Form1 and Form2 should be created manually.
First, you should edit your project source like this to create Form1 manually:
program Project9;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Form1:= TForm1.Create(Application);
Application.Run;
end.
Form1.OnCreate should be written as
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);
Release;
end;
that will make Form2 the main form of your application. As already answered you should use Release method to free the form.
If Form1 is 'autocreate', it's owned by the application object - you shouldn't free it in your code. If Form1 owns Form2, application cleans up both.
I'd do it like this, but not sure it meets your requirements:
procedure TForm1.FormCreate(Sender: TObject);
var a:TForm2;
begin
a := TForm2.Create(nil);
try
a.Show;
finally
freeandNil(a);
end;
end;
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
...
procedure TForm1.FormClose(Sender: TObject; var CanClose: Boolean);
begin
if MessageDlg ('Are you want to exit?', mtConfirmation,
[mbYes, mbNo], 0) = mrNo then
CanClose := False;
end;
So, that is all...
If all Form1 should do is initialize something but not being shown, consider using a datamodule instead. These cannot be shown but can still be autocreated.
If in Delphi 2010 or XE Application.MainFormOnTaskbar is set to true then all secondary forms are always in front of the main window. It does not matter what the Popupmode or PopupParent properties are set to. However I have secondary windows that I want to be able to show behind the the main form.
If I set MainFormOnTaskbar to false it works, but then the Windows 7 features are broken (Alt-tab, Windows bar icon, etc).
How can I keep the Windows 7 features working while still allowing secondary forms to hide behind the main form?
Basically you can't. The whole point of MainFormOnTaskBar is to have Vista compatibility. If you don't set it, compatibility is gone.., if you set it, z-order is done. The following excerpt is from D2007's readme:
The property controls how Window's TaskBar buttons are handled by VCL. This property can be applied to older applications, but it affects the Z-order of your MainForm, so you should ensure that you have no dependencies on the old behavior.
But see this QC report, which describes the exact same problem (and closed as AsDesigned). The report states a workaround involving overriding CreateParams of a form to set the WndParent to '0'. It also describes a few problems which this workaround causes and a possible workaround for those problems too. Beware, it wouldn't be easy/possible to find and workaround all complications. See Steve Trefethen's article to have a feeling of what could be involved.
I would have thought that one approach would be to have a "behind-the-scenes" main form which serves only the following purposes:
To select and show one of the other forms as the main form and then perpetually hide itself (Visible:=FALSE) like the good old-fashioned "flash" screens.
To act as an application terminator when the form it selected as the main form is closed (just wire the appropriate OnClose events).
To open other forms on behalf of the designated pseudo-main-form such that the hidden real main form is the "owner" of the other forms, not the "pseudo-main-form". It appears that this will happen anyway if all your other forms have a 'non' pop-up style and are visible via Show calls rather than ShowModal.
This small restructuring of the application's behavior may then get you the kind user interaction that you are looking for.
unit FlashForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TFlash = class(TForm)
lblTitle: TLabel;
lblCopyright: TLabel;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
public
procedure CloseApp;
end;
var
Flash: TFlash;
implementation
{$R *.dfm}
uses Main;
procedure TFlash.CloseApp; // Call this from the Main.Form1.OnClose or CanClose (OnFormCloseQuery) event handlers
begin
close
end;
procedure TFlash.FormCreate(Sender: TObject); // You can get rid of the standard border icons if you want to
begin
lblCopyright.Caption := 'Copyright (c) 2016 AT Software Engineering Ltd';
Refresh;
Show;
BringToFront;
end;
procedure TFlash.Timer1Timer(Sender: TObject);
begin
Application.MainFormOnTaskBar := FALSE; // This keeps the taskbar icon alive
if assigned(Main.MainForm) then
begin
visible := FALSE;
Main.MainForm.Show;
Timer1.Enabled := FALSE;
end else Timer1.Interval := 10; // The initial time is longer than this (flash showing time)
end;
end.
// Finally, make this the FIRST form created by the application in the project file.
i found a way to solve this issue.
on *.dpr
change
Application.MainFormOnTaskbar := true;
to
Application.MainFormOnTaskbar := false;
this will allow you child form behind you main form.