Secondary Shortcut does not fire - delphi

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;

Related

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.

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.

Create Simple Component that cannot be added multiple times on a form

How to write a simple component that never allows it to drop multiple time on the form?
let say, I already drop my TMyComponent on the form and if i will drop another TMyComponent it notifies 'Component already exist!' and will cancel the dropping of the component on the form.
The components Constructor takes an Owner parameter: that'll normally be your Form (or DataModule). Simply walk the list of Owner.Components and see if there's an other one of yours. If there is, raise an Exception.
This would block your component from being added, both at design time and at runtime.

How to keep only single form open in 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 ?

What is the purpose of the 'Tag' property of Delphi VCL components?

Is there any specific purpose for the 'Tag' property of Delphi VCL components?
I have Googled a few examples using it as, for example, a 'color' property or using the value as a pointer address, but is it 'good practice' to use it, or is it considered 'bad practice' as it ties the program logic to the UI?
The "tag" property is there as a "cargo container" for whatever you might want to do with it.
Something it's often used for is in event handlers when you have a lot of similar components sharing one event handler. The event handler can find its caller and then query its tag value to get some more information about what it's supposed to be acting on.
EDIT:
Example: A calculator app might tag the number buttons with their respective numbers... silly and incomplete example, but you get the idea. The event handler could then pull the number to add into the display and accumulator right out of the tag instead of having to go figure out which button is meant to do what.
It is a place to add a piece of information to any component, even if you don't have the source for that component. It should be used carefully, because you can use it for only one purpose per component. For that reason Libraries should never use it.
I have some fundamental problems with the Tag property.
Well not exactly this property itself because it works as intended.
In general I consider using any universal/general/multi-purpose variables as a 'bad practice'.
They can be useful during debugging but are very harmful in production/mission critical environment.
They reduce code readability and understandability because nobody knows what a 'Tag' named attribute or property does. Of course you know why you are using this variable. But sooner or later you will forget (I know you will) and relying on this value makes anything more complicated.
This is why we should properly name every variable and property to help us understand what the code does.
Using Tag property is just a workaround/shortcut to avoid implementing understandable and well written code.
This is the PRACTICE and it is addictive.
Next time you need to store a new integer value bound to a component you will use the Tag property without considering any other way to store the desired values.
And storing a pointer in Tag property is a horrible idea: you have to cast this value every time you debug pointers.
Tell me: how many times did you find yourself in a situation where you wanted to store a new value in the Tag property but you realized this property is already used for a different purpose (if only there would be a 'Tag2' property in every component...).
As others have said, it's a place to put anything. Typically this comes in handy when associating two objects via an object reference or pointer. The tag happens to be perfectly sized to hold a pointer, so if you need to, say, keep an object tied to an item in a listbox, it becomes pretty straightforward.
Also it can be used for grouping purposes, say you'd want to access all components with a specific tag value, regardless of the component's type.
It's great! A freebie. I use it all the time to store one additional piece of information associated with the object.
Often I store a pointer to an associated data structure, or sometimes an integer which may be an index into some other array.
You can use it as a counter for times the object is accessed, or whatever.
The only downside is if your program uses lots of memory and you have millions of objects, those 4 bytes for each tag add up, especially if you're not using it. In that case, for your most prolific object type, you may want to create your own version without the tag.
You have 2 buttons on your form, on one you set the Tag = 1, and the other one Tag = 2. Now you assign the same OnClick event to both buttons and writhe the code like this:
procedure TForm28.Button1Click(Sender: TObject);
begin
case (Sender as TButton).Tag of
1: Caption := 'you pressed button 1';
2: Caption := 'you pressed button 2';
end;
end;
or more compact:
procedure TForm28.Button1Click(Sender: TObject);
begin
Caption := 'you pressed button ' + IntToStr((Sender as TButton).Tag);
end;
Basically,Tag will let you identify what control fired the event. Think if you have a form with dynamically created buttons... a list with users from the database, and on each record you put a button "Delete User". In this situation you can't create an event for each button, you will create one event that will assigned to all the buttons... and you can put in the Tag the userid for example. That way when you implement the event to handle all the buttons, you'll know what user to delete.
I Use tags all the time.
here are some examples;
a simple sample: you have a notebook (Like a pagecontroll without tabs)
so you can define buttons as tabs and write
NoteBook.ActivePage := TButton(Sender).Tag;
A more complicated sample;
an integer can hold 16 bitwise bolleans;
I can then check the senders up to 16 conditions to decide how to continue the pricedure
If (BitCheck (Bit2,TButton(sender).tag=True) And BitCheck(bit12,TButton(Sender).Tag=False) Then
Begin
end;
If (BitCheck (Bit9,TButton(sender).tag=True) Or BitCheck(bit14,TButton(Sender).Tag=True) Then
Begin
end;
You Get the idea

Resources