I have a main form (MainForm) and a MDI child window (TFormChild).
I want to create multiple TFormChild forms, but the first one must behave in a certain way so I need to detect if a TFormChild window already exists.
I use this code but it is not working:
function FindChildWindowByClass(CONST aParent: HWnd; CONST aClass: string): THandle;
begin
Result:= FindWindowEx(aParent, 0, PChar(aClass), NIL);
end;
I call it like this:
Found:= FindChildWindowByClass(MainForm.Handle, 'TFormChild')> 0;
In a form, you can refer to the MDIChildCount and MDIChildren properties.
for example :
var
i: integer;
begin
for i:= 0 to MainForm.MDIChildCount-1 do
begin
if MainForm.MDIChildren[i] is TFormChild then
...
end;
...
end;
Call it like
Found:= FindChildWindowByClass(MainForm.ClientHandle, 'TFormChild')> 0;
MDI child windows are children of the 'MDICLIENT', ClientHandle property of TCustomFrom holds the handle.
The best way to accomplish this is to have the form you want to open actually check to see if it already exists. To do so your, form must declare a class procedure. Declared as a class procedure, the proc can be called regardless of whether the form exists or not.
Add to your form's public section
class procedure OpenCheck;
then the procedure looks like this
Class procedure TForm1.OpenCheck;
var
f: TForm1;
N: Integer;
begin
F := Nil;
With Application.MainForm do
begin
For N := 0 to MDIChildCount - 1 do
begin
If MDIChildren[N] is TForm1 then
F := MDIChildren[N] as TForm1;
end;
end;
if F = Nil then //we know the form doesn't exist
//open the form as the 1st instance/add a new constructor to open as 1st
else
//open form as subsequent instance/add new constructor to open as subsqt instance
end;
Add Form1's unit to your mdiframe's uses clause.
To open the form, call your class procedure, which in turn will call the form's constructor.
TForm1.OpenCheck;
One word of warning using class procedures, do not access any of the components/properties of the form. Since the form does not actually have to be instantiated, accessing them would produce an access violation/that is until you know F is not nil. Then you can use F. to access form components/properties.
Related
I have two different units in Delphi.
The first unit has a procedure named ApplyUpdates and all it does is run a query.
I need to access this procedure from another unit, and when I do so I get an error message that says "Access violation"".
The procedure in the first unit is:
procedure TForm1.ApplyUpdates ( var AppType: string);
begin
qryApplyUpdates.ParamByName('type').DataType := ftString;
qryApplyUpdates.ParamByName('type').ParamType := ptInput;
qryApplyUpdates.ParamByName('type').AsString := AppType;
qryApplyUpdates.ExecSQL;
end;
From the second unit I call this procedure as:
var
UserForm: TForm1;
begin
UserForm.ApplyUpdates (AppType );
end;
When I debug it, it stops right at the first line of the procedure.
My question is: What am I doing wrong that I cannot access this procedure from the first unit?
If the form is autocreated, don't use the local variable at all:
// The default declared variable for an autocreated form is the classname without the prefix
Form1.ApplyUpdates(AppType);
If the form is not autocreated, you have to create the form before you can use it.
var
UserForm: TForm1;
begin
UserForm := TForm1.Create(nil);
try
UserForm.ApplyUpdates(AppType);
finally
UserForm.Free;
end;
end;
I have ComboBox4,ComboBox1 and Button5
When I click Button5 program should remove component selected in combobox4 from the ComboBox4 and ComboBox1 components' list. But I get list out of bounds error with the following code...
procedure TForm1.Button5Click(Sender: TObject);
var
cat : Integer;
trinti: TComponent;
catT : String;
begin
catT := ComboBox4.Text;
cat := ComboBox4.Items.IndexOf(catT);
trinti := ComboBox4.Components[cat];
ComboBox1.Items.BeginUpdate;
ComboBox4.Items.BeginUpdate;
ComboBox4.RemoveComponent(trinti);
ComboBox1.RemoveComponent(trinti);
ComboBox1.Items.EndUpdate;
ComboBox4.Items.EndUpdate;
removeCat(catT);
end;
Please help :(
The Components property, and the RemoveComponent method are the wrong things to use here. These are for ownership and lifetime management. Typically the only thing on your form that owns anything is the form itself. So using Components on the combo box will always results in an error.
Instead you need to use the Items property of the combo box, and its Delete method. It might look like this:
var
Index: Integer;
....
catT := ComboBox4.Text;
Index := ComboBox4.Items.IndexOf(catT);
if Index <> -1 then
ComboBox4.Items.Delete(Index);
Edit the body for more details.
I have a Form called ENP, defined in the EnpView unit. The ENP form, is created and shown from the handler event of a click a toolbar item in the Main Form (TPrincipal).
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
TENP.Create(self).Show();
end;
Enp form knows (in public declaration) clearGrid() message.
ENP Form have a TStringGrid called StringGrid. And a button called "Add". When click "Add" button other Form is created and shown: AddEnp form, defined in EnpViewAdd unit.
procedure TENP.opAgregarClick(Sender: TObject);
begin
TAddEnp.Create(self).Show();
end;
The AddEnp form, have any TEdits. The values of the inputs must be adding in the EnpView.StringGrid.
I try this:
implementation
uses
EnpView, Main;
procedure TAddEnp.AgregarClick(Sender: TObject);
begin
{ Agrego el nodo xml }
Globals.Xml.agregarMuestra(muestra.Text);
Globals.Xml.insertEnp(muestra.Text,golpes.Text,metros.Text);
{ Send messages to EnpView Form }
ENP.clearGrid();
ENP.populateGrid();
end;
ClearGrid messages fails in line 1, with access violation:
procedure TENP.clearGrid();
begin
Self.StringGrid.RowCount := 2;
Self.StringGrid.Rows[1].Clear();
end;
The clearGrid methods works if is send within the class. Any ideas ?.
Create a property named, for instance, ENPForm in TAddENP and assign the ENP form just after creating it. Declare it as follows:
TAddENP = class(TForm)
private
FENPForm: TENP;
// all of your already existing declarations
public
property ENPForm: TENP read FENPForm write FENPForm;
end;
Now that you have a possible reference to ENP form, you can use it as you like.
During the creation of TAddENP form, do as follows:
procedure TENP.opAgregarClick(Sender: TObject);
var
addForm: TAddENP;
begin
addForm := TAddEnp.Create(Self);
addForm.EnpForm := Self;
addForm.Show;
end;
Now you created the second form and gave it a secure reference to the first one. They can now talk to each other safely.
I advise you to avoid having one form operating other oneĀ“s components, because this increses the dependency between them. Instead, declare public methods to do that, so the forms will depend on their interfaces, not their implementations.
I hope this helps.
From your question (I added code-style to make it more clear):
I have a Form called ENP, defined in the EnpView unit. The ENP form,
is created and shown from the handler event of a click a toolbar item
in the Main Form (TPrincipal).
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
TENP.Create(self).Show();
end;
This does nothing with your ENP form variable.
You create an instance of the TENP form class and show it using Show, but the ENP variable is not assigned.
You cannot assign the instance to the ENP variable, as each button click creates a new instance (so you have multiple instances of TENP) around.
Then you create a convoluted depedency of a TAddEnp instance and the (never assinged ENP variable).
You do this by creating a TAddEnp instance (why TAddEnp here, and not TAddENP?) show it using Show (giving the users the opportunity to go back to the TENP instance and again click on the opAgregar button to create more instances of TAddEnp):
procedure TENP.opAgregarClick(Sender: TObject);
begin
TAddEnp.Create(self).Show();
end;
Followed by having the TAddEnp depend on the ENP variable:
procedure TAddEnp.AgregarClick(Sender: TObject);
begin
//...
ENP.clearGrid();
ENP.populateGrid();
end;
This will indeed fail:
ClearGrid messages fails in line 1, with access violation:
procedure TENP.clearGrid();
begin
Self.StringGrid.RowCount := 2;
Self.StringGrid.Rows[1].Clear();
end;
The reason is that ENP is not assigned (by default it will be nil), so inside clearGrid, the Self will also be nil.
Solutions you could implement
Keep a single instance of TENP and TAddEnp around, using ShowModal to force modality to prevent the user from clicking on the same buttons multiple times.
Keep your existing Show behaviour, but binding each TAddEnp instance to the TENP instance it was created from.
For the first solution, your code will become this:
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
ENP := TENP.Create(Application);
ENP.ShowModal();
ENP.Release();
ENP := nil;
end;
and
procedure TENP.opAgregarClick(Sender: TObject);
begin
AddEnp := TAddEnp.Create(Application);
AddEnp.ShowModal();
AddEnp.Release();
AddEnp := nil;
end;
The second will take more effort, as you need to prevent the use of the existing variables.
Delete the ENP variable.
Delete the AddENP variable.
Fix the self -> Self in the methods
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
TENP.Create(Self).Show();
end;
and
procedure TENP.opAgregarClick(Sender: TObject);
begin
TAddEnp.Create(Self).Show();
end;
Here add the dependency:
procedure TAddEnp.AgregarClick(Sender: TObject);
var
ENP: TENP;
begin
//...
ENP := Owner as TENP; // Owner will be of type TENP because of the Create(Self) in the ENP1Click method
ENP.clearGrid();
ENP.populateGrid();
end;
I have a design and run-time component that contains a large number of event handlers. I'll call it TNewComp for now. I create an instance of TNewComp on a TForm and fill in the event stubs with specific code via the property editor at design time, and realize I would like to be able to create new instances of TNewcomp that use the current set of event handler code.
To do this now, I call TNewComp's constructor and then "manually" assign each of the new instance's event handlers the corresponding event stub code resident on the form that contains the TNewComp instance created at design time. So if I have an instance of TNewComp assigned to a variable named FNewComp on a form called TNewForm, for each event handler I would do:
FNewComp.onSomething = TNewform.onSomething
(... repeat for each event handler belonging to TNewComp ...)
This works fine, but it is cumbersome and worse, if I add a new event handler to TNewComp, I have to remember to update my "newTComp()" function to make the event handler assignment. Rinse and repeat this process for every unique component type that I create new instances of dynamically.
Is there way to automate this process, perhaps using property inspection or some other Delphi 6 introspection technique?
-- roschler
I used the following code. Be careful with the Dest owner when creating, the safest way is to pass Nil and free the component by yourself later.
implementation uses typinfo;
procedure CopyComponent(Source, Dest: TComponent);
var
Stream: TMemoryStream;
TypeData : PTypeData;
PropList: PPropList;
i, APropCount: integer;
begin
Stream:=TMemoryStream.Create;
try
Stream.WriteComponent(Source);
Stream.Position:=0;
Stream.ReadComponent(Dest);
finally
Stream.Free;
end;
TypeData := GetTypeData(Source.ClassInfo);
if (TypeData <> nil) then
begin
GetMem(PropList, SizeOf(PPropInfo)*TypeData^.PropCount);
try
APropCount:=GetPropList(Source.ClassInfo, [tkMethod], PropList);
for i:=0 to APropCount-1 do
SetMethodProp(Dest, PropList[i], GetMethodProp(Source, PropList[i]))
finally
FreeMem(PropList);
end;
end;
end;
One option would be to save "the properly set up component" into stream and then load that strem into new, dynamically created component as if it is done by Delphi IDE/runtime.
Another option is to use RTTI, the TypInfo unit. There you have function GetPropList witch will enable you to query for available events (TypeKind tkMethod) and then you can use GetMethodProp and SetMethodProp to copy eventhandlers from one component to other.
I tweaked Maksee's solution to the following:
function CopyComponent(Source: TComponent; Owner: TComponent = nil): TComponent;
var
Stream: TMemoryStream;
TypeData : PTypeData;
PropList: PPropList;
i, APropCount: integer;
begin
if not Assigned(Source) then
raise Exception.Create('(CopyComponent) The Source component is not assigned.');
Result := TComponent.Create(Owner);
Stream := TMemoryStream.Create;
try
Stream.WriteComponent(Source);
Stream.Position := 0;
Stream.ReadComponent(Result);
finally
Stream.Free;
end; // try()
// Get the type data for the Source component.
TypeData := GetTypeData(Source.ClassInfo);
if (TypeData <> nil) then
begin
// Get the property information for the source component.
GetMem(PropList, SizeOf(PPropInfo) * TypeData^.PropCount);
try
// Get the properties count.
APropCount := GetPropList(Source.ClassInfo, [tkMethod], PropList);
// Assign the source property methods to the destination.
for i := 0 to APropCount - 1 do
SetMethodProp(Result, PropList[i], GetMethodProp(Source, PropList[i]))
finally
// Free the property information object.
FreeMem(PropList);
end; // try()
end; // if (TypeData <> nil) then
end;
So that a new component is returned by the function rather than passing in an existing component reference (the Dest parameter in Maksee's version). If anyone can see a flaw or problem that will result from this variant please comment.
I want to list name of all forms exist in my project in a ListBox Dynamically, then by clicking on each of them, list all buttons exist on that form in another ListBox.
But I don't know if it can be implemented and how it can.
In case you are on Delphi 2010 you can use RTTI to list all registered ( = somehow used in the application) form classes:
uses
TypInfo, RTTI;
procedure ListAllFormClasses(Target: TStrings);
var
aClass: TClass;
context: TRttiContext;
types: TArray<TRttiType>;
aType: TRttiType;
begin
context := TRttiContext.Create;
types := context.GetTypes;
for aType in types do begin
if aType.TypeKind = tkClass then begin
aClass := aType.AsInstance.MetaclassType;
if (aClass <> TForm) and aClass.InheritsFrom(TForm) then begin
Target.Add(aClass.ClassName);
end;
end;
end;
end;
You must somehow take care that the class is not completely removed by the linker (therefor the registered hint above). Otherwise you cannot get hands on that class with the method described.
The forms are usually listed using Screen.Forms property, ex:
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
Memo1.Lines.Clear;
for I:= 0 to Screen.CustomFormCount - 1 do
Memo1.Lines.Add(Screen.Forms[I].Caption);
end;
sabri.arslan's answer is the way to go to find all instantiated forms at run-time.
In the comments Hamid asked for a way to find unassigned forms as well. Assuming that by unassigned he means uninstantiated forms, there would be only one way to do so and that is to iterate over the registry of classes used by the vcl streaming system to instantiate components by name when a dfm is streamed in.
However, IIRC, forms are not automatically added to the registry. In fact, if you want to instantiate forms based on a string of their name, you need(ed) to add them to the class registry yourself. OP could of course do that for each of the forms in his project himself. But, that leaves the problem that the class registry used by the streaming system is implemented using var's in the implementation section of the classes unit. And therefore can't be iterated over (easily) from the outside.
So the solution would be to use the initialization section of all form units in the project and register each form in a "roll-your-own" registry with their name and class and have the registry provide the methods to iterate over the registered forms. These method can be used to populate the listbox mentioned by the OP.
To get at the TButtons on a form would then require instantiating the form (it could remain hidden) and iterating over the components using code similar to sabri.arslan's answer to find the TButton instances.
Instantiating the form would require getting the class of the form from the registry based on the form's name selected in the listbox.
Example of a simple roll-your-own form registry:
unit Unit1;
interface
uses
Classes
, Forms
, SysUtils
;
procedure RegisterForm(aName: string; aClass: TFormClass);
procedure ListForms(aNames: TStrings);
function InstantiateForm(aName: string): TCustomForm;
implementation
var
FormRegistry: TStringList;
procedure RegisterForm(aName: string; aClass: TFormClass);
begin
FormRegistry.AddObject(aName, Pointer(aClass));
end;
procedure ListForms(aNames: TStrings);
var
i: Integer;
begin
for i := 0 to FormRegistry.Count - 1 do begin
aNames.Add(FormRegistry[i]);
end;
end;
function InstantiateForm(aName: string): TCustomForm;
var
idx: Integer;
frmClass: TFormClass;
begin
Result := nil;
idx := FormRegistry.IndexOf(aName);
if idx > -1 then begin
frmClass := TFormClass(FormRegistry.Objects[idx]);
Result := frmClass.Create(nil);
end;
end;
initialization
FormRegistry := TStringList.Create;
FormRegistry.Duplicates := dupError;
FormRegistry.Sorted := True;
finalization
FreeAndNil(FormRegistry);
end.
you can use "for" loop.
procedure ListForms(lbForms:TListBox);
var
i,j:integer;
begin
for i:=0 to application.ComponentCount-1 do
if application.components[i] is tform then
begin
lbForms.add(tform(application.components[i]).Name);
end;
end;
procedure ListBox1Click(Sender:TObject);
var
ix,j,i:integer;
begin
ix:=ListBox1.ItemIndex;
if ix>=0 then
begin
for i:=0 to application.componentcount-1 do
if application.components[i] is tform then
begin
if tform(application.components[i]).name=listbox1.items.strings[ix] then
begin
for j:=0 to tform(application.components[i]).controlcount - 1 do
if tform(application.components[i]).controls[i] is tbutton then
begin
listbox2.add(tbutton(tform(application.components[i]).controls[i]).caption);
end;
break;
end;
end;
end;
end;
There is no way (easy) to find the included forms.
But if you loop through the RCdata of the resources (See (1) (2) (3)), you can find the names of the forms. But that dosn't help you creating them.
In order to make forms "findable" the have to "register" them yourself, using RegisterCLass og finding them again using FindClass. See an example here: http://www.obsof.com/delphi_tips/delphi_tips.html#Button
Do you need this to be built at run time, or would compile time information work for you?
In recent versions (Delphi 2006 and higher?), you can set a compiler option to generate XML documentation for your project. A separate XML file is generated for each unit. You could parse this XML to find and forms and look at the members for any buttons.