I create many forms on runtime using
Application.CreateForm(TForm2, Form2);
Form2.Show;
Now I need to change the Memo1.Font of them at once.
Form2.Memo1.Font:=newfont;
But only the latest created form's Memo1.Font changes. How can I change all?
I guess you're calling Application.CreateForm(TForm2, Form2); multiple times which reassigns the newly created form to your global Form2 variable, so later when you refer to Form2 you're referring to the last-created instance.
To access all instances of TForm2 in your application, you can use Screen.Forms property:
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I] is TForm2 then
TForm2(Screen.Forms[I]).Memo1.Font := ...
The reason for this behaviour is, that component names should be unique,
You can not address multiple components by just one name!
In this case You will have to iterate through all components to find all TMemos.
This could look something like
var i,j: integer;
begin
// first find all Forms in Application
for i:=0 to Application.ComponentCount - 1 do
begin
if Application.Components[i] is TForm then
begin
with (Application.Components[i] as TForm) do
begin
// now find all TMemos and change the font
for j:=0 to ComponentCount-1 do
begin
if (Components[j] is TMemo) and (Components[j].Name = 'Memo1') then (Components[j] as TMemo).Font.Name := 'Arial';
end;
end;
end;
end;
This is a very general approach and You can easily adapt it to other components within Your application.
Another approach would be to memorize all created TMemos in an Objectlist when they are created the first time (then You could easily iterate the object list and change TMemos properties) but without knowing more about Your implementation it is hard to give a good advice.
Related
I would like to create a non visual component (like TTimer for example) that I can drop on the form and that I can set up directly from the Object Inspector, but I don't want to see its icon on the form (it'd just obstruct anything). For example TFloatAnimation works like this but I don't understand how.
The GExperts library (http://www.gexperts.org/) has a plug-in which can toggle the visibility
of non-visual components on a form, and it is apparently not Delphi-version-specific but it is
not exactly trivial.
The method which does this is
procedure THideNonVisualCompsExpert.ToggleNonVisualVisible(Form: TCustomForm);
const
NonVisualClassName = 'TContainer';
var
VisibleState: Boolean;
FormHandle: THandle;
CompHandle: THandle;
WindowClass: string;
FirstCompFound: Boolean;
WinControl: TWinControl;
ChildControl: TWinControl;
i: Integer;
begin
Assert(Assigned(Form));
Assert(Form.Handle > 0);
FirstCompFound := False;
WinControl := Form;
if InheritsFromClass(WinControl.ClassType, 'TWinControlForm') then
begin
for i := WinControl.ComponentCount - 1 downto 0 do
begin
if WinControl.Controls[i] is TWinControl then
begin
ChildControl := WinControl.Controls[i] as TWinControl;
if InheritsFromClass(ChildControl.ClassType, 'TCustomFrame') then
begin
WinControl := ChildControl;
Break;
end;
end;
end;
end;
FormHandle := GetWindow(WinControl.Handle, GW_CHILD);
CompHandle := GetWindow(FormHandle, GW_HWNDLAST);
VisibleState := False;
GxOtaClearSelectionOnCurrentForm;
while (CompHandle <> 0) do
begin
WindowClass := GetWindowClassName(CompHandle);
if AnsiSameText(WindowClass, NonVisualClassName) then
begin
if not FirstCompFound then
begin
VisibleState := not IsWindowVisible(CompHandle);
FirstCompFound := True;
end;
if VisibleState then
ShowWindow(CompHandle, SW_SHOW)
else
ShowWindow(CompHandle, SW_HIDE);
end;
CompHandle := GetWindow(CompHandle, GW_HWNDPREV);
end;
end;
in the unit GX_HideNonVisualComps.Pas.
As written, it toggles the visibility of all the non-visual components on the
target form, but looking at the code of the ToggleNonVisualVisible method it looks like it
ought to be possible (but I have not tried) to adapt it to operate on a selected component class and
force instances of the class to a non-visible state. Once you have done that, you would probably
need to experiment with how and when to invoke the method at design-time; if I was doing it, I would probably start
with somewhere like the target component's Loaded method.
(I would feel more comfortable posting this "answer" as a comment but obviously it would be too long)
I have thought about this. A Non Visual Component does not do any painting, in a Windows environment (like the IDE) it has no Window, and therefore cannot influence how the IDE chooses to render it.
One approach would be to derive from TWinControl, making your component a Visual Component, and then to ensure that it is not drawn. Try setting the positioning properties to be non-published, and when you are parented, always set your position outside the parent window. This means that your control is always clipped and never painted.
I haven't tried this, but I can see no reason why it wouldn't work.
You can also use this approach to have an apparently non visual component that renders information in the IDE at designtime, but not at runtime.
I have a few forms where I have a TListBox component that I fill runtime.
My question is how can I best free the items that I add runtime?
By using the owner? Form or ListBox?
Or freeing them myself?
or a different way?
Below an example how I fill my listbox:
procedure TForm1.LoadList;
var
item: TListBoxItem;
begin
myList.Clear;
myList.BeginUpdate;
try
with myQuery do
begin
First;
while not eof do
begin
item := TListBoxItem.Create(nil);
try
item.Tag := FieldByName(myIDField).AsInteger;
item.Text := FieldByName(myDescriptionField).AsString;
myList.AddObject(item);
finally
Next;
end;
end;
end;
finally
myList.EndUpdate;
end;
end;
I do have noticed that creating the list may take a bit longer when I set the owner of the item.
Also when the I call ListBox.Clear and the list was filled with items with no owners the list still gets cleared correctly. So does this mean the owner of the items get set when I use AddObject to add them to the ListBox?
Also I Free the form with the close action:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := TCloseAction.CaFree;
end;
I don't know if this changes things how to free the items in my list?
When you do a .AddObject you are just giving that object a parent.
My suggestion would be to create the objects as you do now, only giving them a parent. Instead of doing a .AddObject you can also do :
item.parent := mylist
And as has been mentioned, ARC is used on mobile platforms but on desktop it is still business as usual in regards to working with objects.
To free your items (regardless of platform) you could do this :
var
I: Integer;
begin
for I := Mylist.Count-1 downto 0 do
begin
{$IFNDEF AUTOREFCOUNT}
Mylist.ItemByIndex(I).DisposeOf
{$ELSE}
Mylist.ItemByIndex(I).parent := nil;
{$ENDIF}
end;
end;
Because what you are doing then is setting the parent to nil for ARC enabled platforms, resulting in the object being freed once its reference count reaches 0 (given that there are no other references to the object like an owner etc).
And on non-ARC platforms the .DisposeOf will simply be a call to free.
You can however also use .DisposeOf in ARC platforms too, but this will leave your objects in a "Zombie" state and is not considered the "ideal" solution to memory management regarding objects.
I have two identical statusbars (AdvOfficeStatusBar) on each form. That means Form1 has the same status bar as the Form2.Now,before I close the Form1 I would like all the values from the status bar to be transfered to that one on the form2. I suppose I could do it one by one like... :
procedure TForm2.FormShow(Sender: TObject);
begin
AdvOfficeStatusBar1.Panels[0].Text := Form1.AdvOfficeStatusBar1.Panels[0].Text;
AdvOfficeStatusBar1.Panels[1].Text := Form1.AdvOfficeStatusBar1.Panels[1].Text;
AdvOfficeStatusBar1.Panels[2].Text := Form1.AdvOfficeStatusBar1.Panels[2].Text;
AdvOfficeStatusBar1.Panels[4].Text := Form1.AdvOfficeStatusBar1.Panels[4].Text;
AdvOfficeStatusBar1.Panels[5].Text := Form1.AdvOfficeStatusBar1.Panels[5].Text;
AdvOfficeStatusBar1.Panels[6].Text := Form1.AdvOfficeStatusBar1.Panels[6].Text;
end;
I was wondering if there's a more simple way?Less code...
You're suffering from an anti-pattern called copy-paste-programming.
It makes for very easy programming, but difficult maintenance.
Every time you add a line to one statusbar, you have to go back and update to code to have it be linked into the other statusbar.
It's easy to forget updating the code and ehm well it's work, which is why this is bad practice.
A better way is to use Assign or if that does not work a loop. Both are demonstrated below.
Note that the Panel is an array property.
Normally every array_property has a associated count property.
I'm not sure what it is in this instance, but I'm guessing it's called PanelCount.
As per David's suggestion it's better to store the state somewhere inside your program, because you might redesign the form and lose the StatusBar, in which case you'd also lose the storage.
type
TForm2 = class(TForm)
private
StatusStore: array of string;
.....
end;
implementation
procedure TForm2.FormCreate(Sender: TObject);
begin
//Initialisation, you cannot use a loop, unless you'd read it from a file.
SetLength(StatusStore,6);
StatusStore[0]:= 'a';
StatusStore[1]:= 'b';
StatusStore[2]:= 'c';
StatusStore[3]:= 'd';
StatusStore[4]:= 'e';
StatusStore[5]:= 'f';
end;
procedure TForm2.FormShow(Sender: TObject);
var
i,maxi: integer;
begin
StatusStore[0]:= 'Showing Form2';
Maxi:= SizeOf(StatusStore);
i:= 0;
AdvOfficeStatusBar1.PanelCount:= Maxi;
while (i < Maxi) do begin
AdvOfficeStatusBar1.Panels[i].Text:= StatusStore[i];
end; {while}
Form1.AdvOfficeStatusBar1.Panels.Assign(Form2.AdvOfficeStatusBar1.Panels);
end;
Now whatever data is to be displayed and however many items there are, the display will update.
You can even program the loop to skip an item if you want the first or last item to be different for each form.
i have a little problem. I'm trying to create in Delphi7 a list of components at run-time and to resize them with form's .OnResize event but no use... i can't figure out how to do it.
Here's my code:
procedure TForm1.Button1Click(Sender: TObject);
var
//ExtCtrls
panel: TPanel;
memo: TMemo;
splitter: TSplitter;
list: TListBox;
begin
panel := TPanel.Create(Self);
list := TListBox.Create(Self);
splitter := TSplitter.Create(Self);
memo := TMemo.Create(Self);
with panel do
begin
Parent := Form1;
BevelOuter := bvNone;
Top := 12;
Left := 12;
Height := Form1.Clientheight - 24;
Width := Form1.Clientwidth - 24;
end;
with list do
begin
Parent := panel;
Align := alClient;
Top := 0;
Height := panel.Height;
end;
with splitter do
begin
Parent := panel;
Top := 0;
Width := 12;
Align := alLeft;
end;
with memo do
begin
Parent := panel;
Top := 0;
Left := 0;
Width := round(panel.Width / 4) * 3;
Height := panel.Height;
Align := alLeft;
end;
end;
Do i have to somehow register their names in order to use them in form's event? Or maybe, to create a class and include them?
Any kind of help is really appreciated! Thank you in advance.
Your variables are local to the procedure where they are created so you can't refer to them using those variables when outside that procedure. The solution is to make them fields of the form class.
type
TForm1 = class(TForm)
procedure FormResize(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
FPanel: TPanel;
FMemo: TMemo;
FSplitter: TSplitter;
FList: TListBox;
end;
Then your FormResize event handler can refer to them.
procedure TForm1.FormResize(Sender: TObject);
begin
if Assigned(FPanel) then
begin
...
end;
end;
Don't forget to remove the local variables from Button1Click and use the fields instead.
procedure TForm1.Button1Click(Sender: TObject);
begin
FPanel := TPanel.Create(Self);
...
end;
Although David's answer is also very correct, I thought I would take a moment and go into some more detail. By the looks of it, you seem to be very new with Delphi. There is a very common issue with beginners, which David doesn't address in his answer, pertaining to creating and freeing these objects. Any and every time you ever call 'Create' on a class, at some point, when you're done with it, you have to also 'Free' that class. Failure to free anything will result in a memory leak, and no one wants that. Freeing is just as simple as creating - until you get into the subject of keeping a list of objects (which you don't need right now).
Let's say you wanted to create a text box (TEdit) control and place it in the center of your form. Now first of all, the Delphi IDE allows you to simply drop these controls in your form, just making sure you know. You don't necessarily need to create/free them yourself, unless there's some special scenario. But doing this is dangerous. For the sake of this example, we're assuming that this TEdit control will be there for the entire duration of your application.
First, you need to declare a variable somewhere for this control. The most reasonable place for this is inside the class where it will be used (in this case, your form which we'll call Form1). When working with variables (aka Fields) in your form, make sure you do not put anything above the private section. Everything above private is intended for auto-generated code by Delphi for anything which has been dropped (and is visual) in your form. Otherwise, any manually created things must go under either private or under public. The public area would be a good place for your control...
type
TForm1 = class(TForm)
private
public
MyEdit: TEdit;
end;
Now that it's declared, we have to create (and free) it. It's a good practice that any and every time you ever create something, that you immediately put the code to also free it before you continue working. Make an event handler for your form's OnCreate and OnDestroy events...
procedure TForm1.FormCreate(Sender: TObject);
begin
MyEdit:= TMyEdit.Create(nil);
MyEdit.Parent:= Self;
MyEdit.Left:= (ClientWidth div 2) - (Width div 2);
MyEdit.Top:= (ClientHeight div 2) - (Height div 2);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if assigned(MyEdit) then MyEdit.Free;
end;
If this object is not created (before creation or after destruction), then you will get an "Access Violation" when trying to use it. This is because your application tries to access an area of the computer's memory which is not allocated or not matching with the type you meant to get.
Well, that's the basics to fix your scenario. However one more thing to show you. Suppose you need to just create an object for a short time, for the duration of a procedure. There's a different approach for this. In your code above, you declared your variable directly within the procedure. This example will show you when it is necessary to do this...
procedure TForm1.Button1Click(Sender: TObject);
var
MyObject: TMyObject;
begin
MyObject:= TMyObject.Create;
try
MyObject.DoSomething;
Caption:= MyObject.GetSomething;
finally
MyObject.Free;
end;
end;
You see, as long as MyObject will only be used in this one call to this procedure, then you can declare it here. But if the object is expected to stay in memory after this procedure is over and done with, then things get more complicated. Again, in your case, stick with putting this in the form's class until you're more familiar with dynamically creating objects.
A final note, as mentioned above, you do have the ability to place the TEdit control directly on your form in design-time without writing your own code. If you do this, you need to remember NOT to try to create or free these ones. This is also the case when Delphi will automatically put the code above the private section - is when there's something which you're not supposed to play with.
I don't think I am eligible to "comment", so I'm phrasing this as an "answer". If you want to resize your runtime components when their parent changes size, take a good look at the Anchors property. It can save you a lot of work.
We have a HUGE Delphi 2005 application with LOTS of ADO components (TADODataset, TADOStoredPRoc, TADOCommand...) spread on hundreads of forms. All of them are connected to a SINGLE TADOConnection.
Most of these components have their CommandTimeout property set to the default (30s) but a few have it set to 5 minutes (300s) and some are set to never timeout (0s).
I'd like to be able to globally change this setting for all ADO components application-wide. I'd prefer to do it programmatically at runtime so that I could tweak the timeouts on a per-installation basis if I need to.
I was hoping I could find a global event on the connection when an ADO component is created/attached, where I could tweak the commandtimeout, or hack my way into injecting my code in the components themselves, but came up blank.
I don't want to create decendants because I'll have to search/replace trought all the components, and if I ever forget to use the descendants instead of the regular ADO components my timeout wont follow the rest of the application.
Anybody has an idea how we could do this ?
If all of you ADO components are placed on a form, you can iterate over all forms using the Screen.Forms and Screen.FormCount properties. For each form iterate over its ComponentCount/Components property and check for TADOCommand, TADODataSet, TADOQuery, TADOStoredProc and TADOTable. Then you can set the timeout as you wish. Of course, if you create forms dynamically you have to take this into account separately.
The following code may guide you.
procedure SetADOTimeout(ATimeout: Integer);
var
cmp: TComponent;
frm: TForm;
I: Integer;
J: Integer;
begin
for I := 0 to Screen.FormCount - 1 do begin
frm := Screen.Forms[I];
for J := 0 to frm.ComponentCount - 1 do begin
cmp := frm.Components[J];
if cmp is TADOCommand then
TADOCommand(cmp).CommandTimeout := ATimeout
else if cmp is TADODataSet then
TADODataSet(cmp).CommandTimeout := ATimeout
else if cmp is TADOQuery then
TADOQuery(cmp).CommandTimeout := ATimeout
else if cmp is TADOStoredProc then
TADOStoredProc(cmp).CommandTimeout := ATimeout
else if cmp is TADOTable then
TADOTable(cmp).CommandTimeout := ATimeout;
end;
end;
end;
Greetings to all Argentinian solutions!
Just define the OnWillExecute event handler for the TADOConnection you have and write following code:
type
TCustomADODataSetAccess = class(TCustomADODataSet);
procedure TYourDataModule.ADOConnectionWillExecute(...);
var
i: Integer;
begin
for i := 0 to ADOConnection.DataSetCount - 1 do
TCustomADODataSetAccess(ADOConnection.DataSets[i]).CommandTimeout := Connection.CommandTimeout;
end;
This will set the command timeout for any query/table/stored procedure which uses your ADO connection.
According to the documentation, you can use CommandCount and Commands to locate all the open components attached to your TADOConnection.
Your problem is likely to be dynamically created forms. You'll need to find "something" to hook when a form is created and check for ADO components on that form.
If your forms descend from a custom form class, you could do this in the form's constructor or OnCreate event.
If not, you could look at TApplicationEvents and using the TApplication's OnIdle event.
Since CommandTimeout is introduced in the TCustomADODataset class, you can iterate for each form/datamodule, find a TCustomADODataset and it's descendants (ADODataset, ADOTable, ADOQuery) then set the Property.
procedure SetADOCommandTimeOut(aTimeOut: integer);
var
i, j: integer;
begin
for i:= 0 to Screen.FormCount-1 do
begin
for j:= 0 to Forms[i].ComponentCount-1 do
if Forms[i].Components[j] is TCustomADODataset then
TCustomADODataset1(Forms[i].Components[j]).CommandTimeOut:= aTimeOut;
end;
for i:= 0 to Screen.DataModuleCount-1 do
begin
for j:= 0 to Datamodules[i].ComponentCount-1 do
if Datamodules[i].Components[j] is TCustomADODataset then
TCustomADODataset1(Datamodules[i].Components[j]).CommandTimeOut:= aTimeOut;
end;
end;
Note: TCustomADODataset1 is exactly TCustomADODataset, only it has a published CommandTimeOut property :
TCustomADODataset1 = class(TCustomADODataset)
published
property CommandTimeOut;
end;
But it's only applied to forms/datamodules which are already created. If you create your forms/datamodules dynamically, then you must apply it whenever a new form/datamodule is created.
One way to do it is by override Notification in your Mainform, checking for a new a creation of form/datamodule, but this a little bit tricky since at the the creation time, all the components are not created yet. You trick it by delay it for a while using a timer (I don't know a more elegant way - just to show the idea)
Procedure TMainForm.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opInsert) and (
((AComponent is TForm) and not (aComponent is TMainForm)) // exclude MainForm
or (AComponent is TDataModule)
) then
begin
Timer1.Interval:= 2000; // 2 seconds ?
Timer1.Enabled:= True;
end;
end;
Procedure TMainForm.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled:= False;
SetADOCommandTimeOut(MyTimeOut);
end;
You create datamodule hierarchies? If so, you can use code like Uwe's answer on your patriarch form (which all other datamodules inherit).