How to keep only single form open in delphi - delphi

I have VCL application in delphi. One main form and many child forms.
How to ensure only one form opened at a time. In other words, If a form is opened, it will close previous form. Or, if user try to open form that same with previous, it will do nothing.
Code to open form in my main form:
procedure TFMainForm.OpenForm(const classname: string);
var
c: TPersistentClass;
f: TForm;
begin
c := GetClass(classname);
if c <> nil then
begin
f := TForm(TControlClass(c).Create(nil));
f.Parent := Self;
f.Show;
end;
end;
The child form is self-freed on close event.

If you make 'f' a variable in your mainform, instead of a local variable, you will have a reference to the currently open form. You can use that reference to close that form or to check its class.
Two notes:
You'll have to reset the variable if you close the form without opening another.
I wonder why you would want to do this. Isn't it more friendly to let your users decide whether they want to open multiple forms?
As an alternative, you could make the child forms modal (use ShowModal instead of Show), but that would block access to the main form when the child form is open.

Maybe not the same scenario but you could have a SubscriptionList, where every created form is Subscribed on create and unsubscribed on destroy. In that way you can check if your window is in the list, if it doesn't exist create, if already exist move focus to the form.
We use this approach to open the same form with diferent data (an invoice for example) and track them to not open the same invoice twice.
If you are doing for example a Dashboard and using a form for each piece of info you can track them all with the Subscription list instead of having a variable for each one.

Intercept "new form is getting active" event - http://docwiki.embarcadero.com/Libraries/en/Vcl.Forms.TScreen_Events
Enumerate the forms and ensure for all the other forms .Visible is false - http://docwiki.embarcadero.com/Libraries/en/Vcl.Forms.TScreen.CustomForms
Or, if user try to open form that same with previous, it will do nothing
What do you mean ? and how can user do it if other forms are hidden and he can no more see buttons/menus to open other forms?
Maybe all you need is TForm.ShowModal ?

Related

Create datamodule instance multiple times for same form

Good morning, I have an MDI application which I have a search form with a grid (frmCustomerSearch) and a view form (frmCustomerView), these are both child MDI forms.
The search form can only be opened once but the view form multiple times
Search is connected to dmCustomerSearch and view to dmCustomerView, I create an instance of the dmCustomerView on the create of frmCustomerView, filter the query by parameter and then open the query as below.
dmCustomerViewFrm := TdmCustomerViewFrm.create(self);
dmCustomerViewFrm.fdqCustView.ParamByName('REF').Value := fAccountRef;
dmCustomerViewFrm.fdqCustView.Open;
// Set dataModule to an empty string, prevents other forms using it.
dmCustomerViewFrm.Name := '';
The form opens on the correct record and I can edit the record without an issue.... my issue is when I open more than one record, I can edit the last record opened but anything prior I get the following error:
FDQCustView: Dataset not in edit or insert mode.
Is the new instance taking the original out of edit mode? or am I missing something?
Any advice greatly appreciated!

Do I need to free dynamically created forms?

If I dynamically create a TForm using TForm.CreateNew(Application) to make a free-floating window do I have to keep track of these objects and free them upon Application close?
Or will delphi automatically free all forms on Application close?
Also, what happens if I have a free-floating dynamically created form and the user hits the close button? Do I need to call some code somewhere to free those?
If so, how? I assume I can't place it in any of the form's events.
Just to touch on an important point not covered in the other answers...
Yes, when you create a form (or any other component) with an owner, it will be destroyed when the owner is destroyed.
But, very NB: This does not mean you won't have a leak. To clarify:
If every time you create a form you set Application as the owner, then the forms will be destroyed by the Application object when your app closes.
But (unless you code something extra), those forms will be destroyed only when the application closes.
In other words, if every time your user selects a particular menu item you create a particular form owned by the application, then more memory will be consumed over time. Depending how much memory is used each time you create the form, your application may run out of memory.
So, provided you don't keep re-creating the objects, the model is perfectly acceptable. However, this means that you do want to keep track of these objects. But not so you can free them yourself, rather so you can re-Show them instead of re-creating them.
Also to cover some of the other points in your question:
What happens if I have a free-floating dynamically created form window and the user hits the close button? Will do I need to call some code somewhere to free those?
You don't need to free them if next time your user shows the form you reuse the existing instance. If you're going to create a new instance, then you should free the form when it's closed. Otherwise all the old instances only get destroyed when your application closes.
How? I assume I can't place it in any of the form's events.
It just so happens that Delphi provides an ideal event: OnClose. If you hook that event, then you can set the var Action: TCloseAction to indicate what should happen when the form closes. By default:
An MDI form will be minimised (caMinimize).
And an SDI form will be hidden (caHide).
You can change this to destroy the form (caFree).
NOTE: If you do decide to destroy the form, be careful to not try reusing it after it has been destroyed. Any variable you had that pointed to the form will be pointing to the same location in memory, but the form is no longer there.
Do I need to free dynamically created forms?
No you don't, except if you create the form with TForm.CreateNew(nil), that is passing no owner to the constructor.
The parameter for CreateNew is the owner, if the owner (Application/Form/WhatEverYouLike) gets deleted all owned objects will be freed. Give it a try.
procedure TMainfom.Button1Click(Sender: TObject);
var
a:TForm;
begin
With Tform.CreateNew(Application) do
begin
OnClose := MyAllFormClose;
OnDestroy := AllMyFormDestroy;
Show;
end;
end;
procedure TMainfom.AllMyFormDestroy(Sender: TObject);
begin
Showmessage(':( I''m going to be destroyed')
end;
procedure TMainfom.MyAllFormClose(Sender: TObject; var Action: TCloseAction);
begin
// unremark if you want to get the form freed OnClose already
// Action := caFree;
end;
The parameter you passed to CreateNew is the owner of the component. When a component's owner is destroyed, it destroys all the components that it owns. So, the application object is the owner of your form. When the application closes, the application object is destroyed. And so it destroys all of its owned components. Including your dynamically created form.
I don't know why it is that people seem to think forms are some kind of magical entity that have their own unique behavior and rules.
Forms are regular objects derived from TForm and TCustomForm, which are ultimately derived from TObject, just like every other class in Delphi.
If you create anything derived from TObject dynamically, it gets destroyed when the application terminates. However, if you fail to destroy it yourself and leave it to the system, that's generally regarded as a "memory leak". It's not a big deal for programs that run once and terminate quickly. But for things that users will leave open for hours or days on end, memory leaks can become quite an annoyance.
As mentioned earlier, TForms have an OnClose event that has a parameter Action. You can set Action to caFree and the form will be destroyed upon return to the Show or ShowModal call that displayed it. But if you use it, you'll need to create the form object yourself rather than use the default auto-create mechanism.
Other types of objects don't have this, like TStringList. You just need to practice "safe object management" and ensure that objects that get created also get destroyed in a timely manner, including forms. This can get into a whole rat's nest of a discussion about stuff related to garbage collection, Interfaces, and a whole lot of related stuff. Suffice it to say, you need to be aware of the options and manage your object lifetimes appropriately, rather than just leaving them to get destroyed when the application terminates.

delphi using dbgrid to view a record on a detail page

I have a form with a dbgrid and a second form with dbedits. My form with the dbgrid has a double click event(code below is the event) the dbedits on the second form has a data source that is connected to the first form's CDS and when I compile and run the program and open the form with db grid I can double click any record and it is display in the dbedits on the second form, but if I close the forms and reopen the form the only record that will display in the second form dbedits is the first record in the table. I have to open and close CDS and that is not working. what else would I need to do to correct this problem.
procedure TFRM_ADMIN.DBGrid1DblClick(Sender: TObject);
BEGIN
frm_LEADDETAILADMINLEAD := tfrm_LEADDETAILADMINLEAD.Create(FRM_ADMIN);
frm_LEADDETAILADMINLEAD.SHOW;
END;
The site will not allow me to add the dmf text. It is to large. I am using sqlconnection, sqlquery, data set provider, client data set, data source set up if this helps any.
This is a very wild quess, but I suspect the following at play:
You use the public variables for the forms that the IDE automatically has added to each unit.
You create FRM_ADMIN the way you create frm_LEADDETAILADMINLEAD, something like:
FRM_ADMIN := TFRM_ADMIN.Create(MainForm);
FRM_ADMIN.Show;
You don't close this form, but hide it (the default close action of a form).
The second time you create your second form, the designtime set DataSet property of the DataSource of your second form is automatically resolved to the ClientDataSet on the first instance of the first form.
So in the second run you are editing the record that was selected in the first run.
Solution and recommendations:
Destroy forms (set Action := caFree) in the OnClose event.
Do not use the public variables, remove them. Keep you form reference in a separate private field, unique to each instance.
Assign DataSource and/or DataSet properties at runtime explicitly.
Use DataModules.
Further reading:
Binding a second instance of a form to a second instance of a data module?
Will there be any detrimental effects if I accidentally remove something from a forms uses list which a control is referencing?
Separate DataSet instances using DataModules.
Show message when Tform2 is created?

A form that is able to be Modal after it is created

I have a PopForm that I want to stay over MainForm in any case.
The cases are:
PopForm.ShowModal and PopForm.Show.
Whilst ShowModal is completely works fine, the Show method is needed to interact with MainForm for doing 'drag-drop' things.
Main duty of the PopForm is to import data from some files to a dataset on the MainForm. There is two ways: drag-drop of the concrete data (selected rows while in Show-mode) and transfering all data from PopForm (while in ShowModal-mode). All data in the PopForm stored in something like ClientDataSet. I developed methods that are alike ClientDataSet's: First, Eof, FieldByName, Next, etc. and implemented them into PopForm.
After the PopForm is closed (after setting the ModalResult), the calling procedure uses a while not PopForm.eof do PopForm.Next... to import data into MainForm's dataset or whatever would be a user-programmers method.
For showing some progress I used to recreate PopForm with Show method and show a progress form above PopForm, while a cycle is being made in the calling procedure. After that - PopForm is closed.
This works fine, but the only problem is about a Show method - I need the PopForm to be over MainForm all the time. In most cases it is, but there are some applications, that somehow doesn't follow this rule. I've tried to use PopupParent & PopupMode properties, but they make popForm to recreate again on any assignment (and as I've just found - you need to assign a CustomForm on show and then - Nil it on close, because with common TForm.Close method it won't hide no way)
There would be no any problem about that, if there would be no need to Nil PopupParent on FormClose method. The other routine is to assign
...FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
...
end;
It would work if Assigned(PopForm) return false, but it returns true.
If there is any case to discover if the Form is Freed - that may help. What do you think?
If you set your form style to fsStayOnTop the form will always stay on top unles you there is another form with same property. In that case the active one of them will be on top.
As for moving data from your PopForm which you use for importing data you could folow similar approach as it is used in creating of custom Dialogs.
Custom dialogs are usually designed like so:
You have dialog class which contains fields for dialg settings (to change hoe the dialog woul look like), and even a field for dialog output (selected FileMame/s, etc.).
Within this class there is a dialog form class which esentially is a modal form. This dialog form uses its OnClose event to automatically feed necessary information into Dialog output field before closing.

Secondary Shortcut does not fire

I am using Delpho 2006. The Scenario:
On the data module I have an ActionList. One of the actions has a shortcut Ctrl+F4 and I want to have a secondary shortcut Ctrl+W. I tried all of the following:
Adding Ctrl+W to the actions SecondaryShortcut list in the IDE.
Adding it in the DataModuleCreate procedure using either
ActFileCloseFile.SecondaryShortCuts.Add('Ctrl+W');
or
ActFileCloseFile.SecondaryShortCuts.AddObject('Ctrl+W',
TObject(Menus.ShortCut(87, [ssCtrl])));
Using both of these methods in the Create or FormShow procedure of the form where it will be used.
The primary shortcut always works, but not the secondary.
When I put the ActionList on the main form instead of the data module, it works by just adding Ctrl+W in the IDE. What do I do wrong?
The most elegant solution found so far is this:
On the form you want to handle the SecondaryShortCut, add this to the OnShortCut event:
procedure TMyForm.FormShortCut(var Msg: TWMKey; var Handled: Boolean);
begin
Handled := dmDataModule.ActionList1.IsShortCut(Msg);
end;
Alternative:
(This is not a real solution, but a workaround.)
Put an action list on the form that has an identical action to the one on the data module. In its execute and update events it only forwards the events to the data module action. The menus on the form use the local action.
In this case it suffices to add Ctrl+W to the SecondaryShortCuts property using the IDE.
Obviously, when the action on the data module changes, I have to change all local actions too.
Not a real solution, but if you create the datamodule from within the mainform it works:
procedure TMainForm.FormCreate(Sender: TObject);
begin
FDataModule := TMyDataModule.Create(self);
TMyButton.Action := FDataModule.TheAction;
end;
procedure TMyDataModule.DataModuleCreate(Sender: TObject);
begin
TheAction.SecondaryShortCuts.Add('Ctrl+W');
end;
I think the shortcuts are handled by the form that has the current focus. So yo will probably get the same problems if you are using them in another form.
Short Answer: Actions short-cuts do not automatically fire across forms and data-modules.
If you follow the instructions in the question, you'll find that not even the primary short-cut fires. This is because a key step has been left out of the instructions. One that will serve to explain why OP experienced the primary short-cut firing and not the secondary one.
If you include the extra steps:
Add a menu to the form.
And link a menu-item to the action.
Then the primary short-cut will be able to fire the action. This is because the Action component pushes its settings to the menu item (including the ShortCut property). However, TMenuItem does not implement the concept of secondary short-cuts. Which is why one works and not the other.
Pause to consider an application with many forms and data-modules; and the implication if action short-cuts could fire across all of them. It should be fairly obvious that they should not be able automatically fire without explicit code to permit it. You wouldn't want a background form doing a bunch of things because its configured short-cut keys happen to be pressed in the context of other unrelated work.
The documentation points out the benefit of putting action lists on data-modules. But doesn't seem to offer any explanation how to use actions with short-cuts on a data-module correctly. Certainly nothing is mentioned in the expected places, namely: ShortCut and SecondaryShortcuts. (I'd be disappointed, but my expectations of decent documentation have been dragged quite low.)
So...
What should be done to get actions with short-cuts working across forms and data-modules?
I have done a bit of investigation and found a few options. As always, evaluate the trade-off's relative to what you are trying to achieve.
When you drop an action list on a (non-main) form, all the short-cuts work as expected. This is the most common scenario and applies when the actions are local and form specific.
When you drop an action list on the main form, all those short-cuts will be able to fire from any other form. This is great for application-wide short-cuts such as opening other forms.
NOTE: There is a prioritisation sequence as to where a short-cut is tested first. So if the active form has a short-cut matching one on the main form, the short-cut will be processed locally. And the main form understandably won't get it.
When a form is tested to see if it handles a short-cut, all owned components are also checked. (This is in fact why the first two above work.) This means that simply setting the Owner of your data-module appropriately will allow its short-cuts to apply to your chosen form.
I.e. Instead of:
Application.CreateForm(TDataModule1, DataModule1);
You can use the following:
DataModule1 := TDataModule1.Create(LocalForm);
However, since each instance of a data-module can have only one owner: you would have to create multiple instances to let multiple forms share the short-cuts. Whether this is an option depends on your circumstances. However, you could also make the main form the owner of your data-module which would be somewhat equivalent to the second option above.
The final option which provides the most control is OP's own answer. I.e. Any form that needs to support "external short-cuts" can handle the OnShortCut event with the following code:
As can be seen in the code sample you can delegate to multiple action lists in different locations according to the priority you choose.
procedure TMyForm.FormShortCut(var Msg: TWMKey; var Handled: Boolean);
begin
Handled := DataModule1.ActionList3.IsShortCut(Msg);
Handled := Handled or DataModule2.ActionList1.IsShortCut(Msg);
Handled := Handled or DataModule1.ActionList1.IsShortCut(Msg);
end;
The action is getting swallowed by the form...If you want a secondary form/frame/datamodule to handle the action...You have to disable the Actionlist from the Primary first...
Form1.ActionList1.State := asSuspended;
DataModule1.ActionList1.State := asNormal;

Resources