Getting data from Modal forms before it closes - delphi

I have a Modal form, and in the Ok button it processes some information, that I need in the form that called the modal form.
How can I get it out before it closes?
Or delay the close till I say it can close.

I expect that your OK button has ModalResult set to mrOK. If you want to add error checking to the OK button then change that to mrNone. Add an OnClick handler to the button which does whatever checking or processing you need. If it determines that the form can close, set Self.ModalResult := mrOK in the OnClick handler.

Do you really need to access the information before the form is closed? Delaying the closing of a form will affect the users experience of the app (unless it's fast enough that they don't notice - in which case why delay it at all?)
A closed form is still available in memory for the caller (unless the close action is caFreeOnClose). So you should be able to add public properties to the form which you can then access within the caller.
e.g
Type Form2 = Class(TForm)
public
//Add a public property here
end;
From the caller:
if Form2.ShowModal = mrOk then
begin
InformationIWant = Form2.PublicProperty;
end;

Just a combination of what others are saying.
It is a good idea to formalize how to validate and get data out from a modal dialog.
Using the same technique over and over again makes everything easier to maintain and read.
An example :
Type TFormModal = Class(TForm)
procedure OnOkClick( Sender : TObject);
function ValidateInterface : boolean;
public
procedure SetInterface( input data here);
procedure GetInterface( output data here);
end;
procedure TFormModal.OnOkClick( Sender : TObject);
begin
if ValidateInterface
then modalResult := mrOk
else modalResult := mrNone;
end;
from your main form :
procedure MainForm.OnShowMyModalFormClick( sender : TObject);
var
myModal : TFormModal;
begin
...
myModal := TFormModal.Create( nil);
try
myModal.SetInterface( ...);
if (myModal.ShowModal = mrOk) then myModal.GetInterface(...);
finally
myModal.Free;
end;
...
end;

An alternative to David’s answer is to use either OnClose or OnCloseQuery events. With OnCloseQuery you can prevent the form from closing by setting CanClose := false;

As addition to JamesB's answer.
You must call Form2.Free, áfter you take the information you want.
I generally add a new function to the second form's unit, something like:
type
TForm2 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
InformationIWant : SomeType;
end;
var
Form2: TForm2;
function ReturnValue : SomeType
implementation
function ReturnValue : Sometype;
begin
try
if Form2 = nil then
Form2 := TForm2.Create(nil);
Form2.Windowstate := wsNormal;
Form2.BringToFront;
SetForegroundWindow(Application.Handle);
if Form2.ShowModal then
Result := InformationIWant
finally
FreeAndNil(Form2);
end;
end;

Let's say you want to do more than to simply know if the user pressed the OK or Cancel button in your modal from.
Let's suppose you need to set some parameters for the MainFrom in FromSettings.
Create and show the FormSettings modal
When the user presses the "OK" (or "Apply") button in FormSettings to close the form, you transfer all your data from FormSettings to MainForm
Finally call FormSettings.Close or better FormSettings.Release (not FormSettings.Free) to close the form.
Of course the MainFrom has to have some exposed (public) fields in which you receive the data from FormSettings, like:
FormMain.AlphaBlendValue := FormSettings.spnTransparency;
Hint 1:
You don't necessary have to transfer data from FormSettings into MainForm. If it better suits you, you can also save the data in a global variable, or record.
Hint 2:
I personally don't use the method described above, which is designed to save RAM when the FormSettings is freed.
I actually never destroy the SettingsForm. Some people would say that this is "horror" but computers today have 4GB or RAM (at least) and a form with some controls on it will "waste" a very very little amount of that RAM. So, the FormSettings in is memory all the time. When I need some values, I just let the MainForm to read them from FormSettings.
Again... this is definitively the recommended way to do it. It is the convenient way :) You have been warned!

Related

Derived TClientdataset; defining an always-run OnAfterPost

I'm deriving from TClientdataset and trying to define an 'always run' AfterPost event. I have tried assign my AfterPost event at the constructor, but the derived component does not seem to pick it up
TMyClientDataset = class(TClientDataset)
private
FInserting: Boolean; //set to True at the MyOnNewRecord event
property FuserAfterPost: TDataSetNotifyEvent;
...
public
constructor Create(AOwner: TComponent);
...
implementation
constructor TMyClientDataset.Create(AOwner: TComponent)
begin
...
if Assigned(AfterPost) then
FuserAfterPost := AfterPost;
Afterpost := MyAfterPost;
...
end;
procedure TMyClientDataset.MyAfterPost(Dataset: TDataset);
begin
If Assigned(FuserAfterPost) then
FuserAfterPost(Dataset);
FInserting := False;
end;
What I'm trying to do: On new record, set Finserting := True; On after post, run the user supplied OnAfterPost and set FInserting := False; But the MyAfterpost event won't even run. I'm assuming the constructor is not the right place to do AfterPost := MyAfterPost;? Where should it go?
There's no good place for what you want to do. Because a user of the component may attach a handler or nil to the event handler anytime while the program is running, not just at design time. May detach an existing handler too. Then your code will not run.
For this reason, VCL employs a two step call to event handlers. First is a virtual procedure which, generally, does nothing more than to call a possible event handler. In your case, this is DoAfterPost.
TMyClientDataset = class(TClientDataset)
..
protected
procedure DoAfterPost; override;
...
procedure TMyClientDataset.DoAfterPost;
begin
inherited;
FInserting := False;
end;
For a case when no such opportunity exists, there would be no chance but properly document and hope for the user of the component reads and complies with it. Overriding Loaded would be the right place to backup an existing design-time attached handler.
Sertac's answer is excellent guidance on this type of problem. And yes it does answer the question you asked, but it's missing something.
It seems to me that you have an XY problem, and failed to ask the correct question. There is no need for you to try to manually track FInserting. Delphi already does this. Take a look at TDataSet.State and TDataSetState.
Basically your FInserting is equivalent to State = dsInsert.
Although, as you have pointed out, your FInserting flag is True in OnAfterPost (which makes it misleading, and on that basis is technically a bug).

Shortcut triggers TAction on first created form instead of form with focus

I found (in Delphi 2010) that shortcuts always end up on first form (as owned by main form) that has that action, but not the currently focused form. My TMainFrm owns several TViewFrm. Each has a TActionManager with the same TActons.
I see some ways out, but wonder whats the best fix.. (and not a bad hack)
The forms are navigated using a tabset which calls their Hide() and Show(). I'd did not expect hidden forms to receive keypresses. Am i doing something wrong?
It seems that action shortcuts are always start at the main form, and using TCustomForm.IsShortCut() get distributed to owned forms. I see no logic there to respect hidden windows, should i override it and have it trigger the focused form first?
Disabling all TActions in TViewFrm.Hide() .. ?
Moving the TActionToolBar to TMainFrm but that is a pit of snakes and last resort.
I have found a workaround thats good enough for me; my main form now overrides TCustomForm.IsShortcut() and first checks visible windows from my list of editor tabs.
A list which i conveniently already have, so this might not work for everyone.
// Override TCustomForm and make it check the currently focused tab/window first.
function TFormMain.IsShortCut(var Message: TWMKey): Boolean;
function DispatchShortCut(const Owner: TComponent) : Boolean; // copied function unchanged
var
I: Integer;
Component: TComponent;
begin
Result := False;
{ Dispatch to all children }
for I := 0 to Owner.ComponentCount - 1 do
begin
Component := Owner.Components[I];
if Component is TCustomActionList then
begin
if TCustomActionList(Component).IsShortCut(Message) then
begin
Result := True;
Exit;
end
end
else
begin
Result := DispatchShortCut(Component);
if Result then
Break;
end
end;
end;
var
form : TForm;
begin
Result := False;
// Check my menu
Result := Result or (Menu <> nil) and (Menu.WindowHandle <> 0) and
Menu.IsShortCut(Message);
// Check currently focused form <------------------- (the fix)
for form in FEditorTabs do
if form.Visible then
begin
Result := DispatchShortCut(form);
if Result then Break;
end;
// ^ wont work using GetActiveWindow() because it always returns Self.
// Check all owned components/forms (the normal behaviour)
if not Result then
Result := inherited IsShortCut(Message);
end;
Another solution would be to change DispatchShortCut() to check for components being visible and/or enabled, but that might impact more than i'd like. I wonder whether the original code architects had a reason not to -- by design. Best would be have it called twice: first to give priority to visible+enabled components, and second call as fallback to normal behavior.

Delphi disable form while loading

In my application, I have a main form, with the ability to load some images in database.
While images is loading, I want to show a form, with the progress indicator (with bsNone border style).
But, if I show with form with ShowModal, execution of main form is stopped, so I can't to that.
If I call Show, user have access to all other form components, and it can be dangerous, while photo is not loaded completely.
I need to get the way, to disable everything on main form, while loading isn't completed.
Please, advice me, how it is possible.
Set the MainForm as the PopupParent for the progress form so that the MainForm can never appear on top of the progress form. Then simply set MainForm.Enabled := False while the progress form is open and set MainForm.Enabled := True when the progress form is closed.
procedure TMainForm.ShowProgressForm;
begin
with TProgressForm.Create(nil) do
begin
PopupParent := Self;
OnClose := ProgressFormClose;
Show;
end;
Enabled := False;
end;
procedure TMainForm.ProgressFormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
Enabled := True;
end;
This simulates howShowModal() behaves to the user (the MainForm is not user-interactive while the progress form is open), but without blocking the code.
First of all, the direct answer to your question.
I need to get the way, to disable everything on main form.
Set MainForm.Enabled to False to disable the window associated with the main form. And to re-enable set it to True instead.
Your fundamental problem however, is that you are executing long running tasks in the GUI thread. That's always a bad idea and the way out is to execute those long running tasks in a separate thread.
Once you move the long running tasks to a separate thread then you will find that ShowModal is exactly what you need to show your progress form.
As I explained in my other answer, putting the long running task into a thread other than the GUI thread is the ideal solution. The GUI thread should handle short running tasks so that it is always able to service the message queue in a timely fashion.
However, if you already have code that assumes that the long running task runs on the GUI thread, you may prefer to take a more expedient approach and postpone the re-factoring to threaded code. In which case, in my view, it is still better to use ShowModal to display your progress form.
But in order to make that work, you need to find a let the long running task execute inside the ShowModal call. You can do that as follows:
Before you call ShowModal, pass the task to the form. For example, pass a TProc to the constructor of the progress form.
Override the progress form's Activate method. This will get executed just before the ShowModal function starts its modal message loop. In the implementation of Activate, post a message to the form.
When the form handles that message, invoke the task that was passed to the constructor.
Obviously you'll need to call ProcessMessages in your long running task, in order to keep the main GUI thread message queue serviced. Clearly you must already be doing that.
Set the PopupParent of child form = ParentForm
procedure TParentForm.Button1Click(Sender: TObject);
begin
ParentForm.Enabled:=False;
with Tform1.create(nil) do show;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ParentForm.Enabled := true;
form1.free;
end;
You may want to use DisableTaskWindows and EnableTaskWindows functions.
If you use simple ParentForm.Enabled := False for the parent form, you can still access all other forms, like the main form if it differs from ParentForm. It is obviously still dangerous.
Here is short sample:
interface
uses
Vcl.Forms, Winapi.Windows, ...;
type
TPleaseWait = class(TObject)
private
fWindowList: TTaskWindowList;
fActiveWindow: HWND;
fDialog: TPleaseWaitForm;
public
constructor Create;
destructor Destroy; override;
end;
implementation
constructor TPleaseWait.Create;
begin
// Disable all displayed windows
fWindowList := DisableTaskWindows(0);
// Save the last active window
fActiveWindow := GetActiveWindow;
fDialog := TPleaseWaitForm.Create(nil);
fDialog.Show;
end;
destructor TPleaseWait.Destroy;
const
INVALID_HANDLE = 0;
begin
fDialog.Close;
fDialog.Free;
// All windows are enabled now
EnableTaskWindows(fWindowList);
// That helps by enabling the last one form
if (fActiveWindow <> INVALID_HANDLE) and IsWindow(fActiveWindow) then
SetActiveWindow(fActiveWindow);
inherited;
end;
end.

Passing Tobject to another form?

I'm writing a touchscreen enabled application in Delphi XE2.
I have a form with TEdits. When I click on them, I call the procedure I've written to show another maximized always on top form with a TTouchkeyboard with a label (for caption) and a TEdit for keyboard input.
My procedure (vkeyboard is my form name with the TTouchkeyboard):
procedure TLogin.showkeyboard(numeric,password: Boolean;
caption,value:string;Sender:TObject);
begin
if numeric then
vkeyboard.TouchKeyboard1.Layout := 'NumPad' // make the TTouchkeyboard on the form numeric or alpha
else
vkeyboard.TouchKeyboard1.Layout := 'Standard';
if password then
vkeyboard.input.PasswordChar := '*' //make the TEdit show * or normal characters
else
vkeyboard.input.PasswordChar := #0;
vkeyboard.title.Caption := caption;
vkeyboard.input.Text := value;
vkeyboard.Show;
end;
I'm trying to send Form1.Edit1 object to the form vkeyboard but i don't know how to do it properly!
Why? Because i want to be able to click Done on the input form (vkeyboard) then trace back who was the sender then update the text in the main form edit!
procedure Tvkeyboard.sButton1Click(Sender: TObject);
begin
(temp as TEdit).Text := input.Text; // send back the text to the right object
vkeyboard.Hide;
end;
This little part of course didn't work... I think i need to specified that the temp object belong the X form ?
To be clear, i want to trace back who called the procedure or at least be able to specified it in the procedure and then return back the text (from the 2nd form to the main one) to the right TEdit!
You're welcome to pass whatever arguments you want to whatever function you want. If you need to use the passed value in yet another function, you'll need to save it somewhere so the later function can still access it.
Using your example, you appear to have provided a Sender parameter for your showkeyboard function. I assume that's where you're passing a reference to the TEdit control that triggered the keyboard to show. The Tvkeyboard object stored in vkeyboard will need to use that value later, so give a copy of that value to the Tvkeyboard object. Declare a TEdit field:
type
Tvkeyboard = class(...)
...
public
EditSender: TEdit;
Then, in showkeyboard, set that field:
vkeyboard.EditSender := Sender;
Finally, use that field when you set the text:
procedure Tvkeyboard.sButton1Click(Sender: TObject);
begin
EditSender.Text := input.Text; // send back the text to the right object
Self.Hide;
end;
Since you know it will always be a TEdit control, you can change the type of the Sender parameter in showkeyboard to reflect that specific type:
procedure TLogin.showkeyboard(..., Sender: TEdit);

How can a form send a message to its owner?

I have written an MDI based application, in which the child forms are of different types. I have now come across a situation where I need one child form to send a message to another child form, telling it to update itself. The first child form is unaware of whether the second child form is being displayed at the moment.
I had thought of having the first child form (form A) send a message to the main MDI form (form 0), which could then check the list of MDI child forms currently being displayed on the screen. If the desired form (form B) is being displayed, then the main form could send a second message to this form (form B).
Unfortunately, I haven't been able to write successfully the code which would enable the first child form to signal the main form. How can a child form send a message to its owner?
TIA,
No'am
The owner of a form isn't necessarily another form. The Owner property is just TComponent, which could be anything, including nil. But if the owner is a form, you can send it a message like this:
if Owner is TForm then
SendMessage(TForm(Owner).Handle, am_Foo, 0, 0);
You might not need to know the owner, though. The MDI parent form is always the main form of the project, and the main form is always designated by Application.MainForm. Send a message to that form's handle.
SendMessage(Application.MainForm.Handle, am_Foo, 0, 0);
The list of MDI children will be in Application.MainForm.MDIChildren. Your child forms can check that list for themselves rather than have the MDI parent do it. Here's a function either of your forms can use to find instances of any MDI child class. (If the forms that want to communicate aren't MDI children, you can still use this technique, but instead of Application.MainForm.MDIChildren, search the Screen.Forms list.)
function FindMDIChild(ChildClass: TFormClass): TForm;
var
i: Integer;
begin
for i := 0 to Pred(Application.MainForm.MDIChildCount) do begin
if Application.MainForm.MDIChild[i].InheritsFrom(ChildClass) then begin
Result := Application.MainForm.MDIChildren[i];
exit;
end;
end;
Result := nil;
end;
Your first child class could use it like this:
var
SecondChild: TForm;
begin
SecondChild := FindMDIChild(TSecondChild);
if Assigned(SecondChild) then begin
SendMessage(SecondChild.Handle, am_Foo, 0, 0);
end;
end;
When window messages are sent to windows in the same thread as the sender (which is always the case for any two VCL forms), their handlers are called immediately while the sender waits for a response. That's just like an ordinary function call, so you might wish to skip the messages and make regular functions in your form classes. Then you could use code like this:
var
SecondForm: TSecondForm;
begin
SecondForm := TSecondForm(FindMDIChild(TSecondForm));
if Assigned(SecondForm) then begin
SecondForm.Foo(0, 0);
end;
end;
Another approach that is worth using here is to use interfaces rather than messages. The advantage is that interfaces are more specific... messages can easily be accidentally re-used, unless your very specific on where your message constants are located.
To use this model, first create a global unit (or add to an existing) the following interface declarations:
type
ISpecificSignal = interface
{type CTRL-SHIFT-G here to generate a new guid}
procedure PerformSignal(Handle:Integer);
end;
Then modify your MAIN form interface, adding following:
TYPE
TMainForm = Class(TForm,ISpecificSignal)
:
private
Procedure PerformSignal(Handle:Integer);
end;
and in the implementation of the PerformSignal procedure looks like the following:
Procedure TMainForm.PerformSignal(Handle:Integer);
var
i: Integer;
Intf : ISpecificSignal;
begin
for i := 0 to Pred(Application.MainForm.MDIChildCount) do begin
if Supports(Application.MainForm.MDIChild[i],ISpecificSignal,Intf) and
(Application.MainForm.MDIChild[i].Handle <> Handle) then
Intf.PerformSignal(Handle);
end;
In your child form which ultimately must handle the signal, perform the same steps as you did for the main form, but change the PerformSignal to invoke the code you desire. Repeat as needed.
In the form which needs to actually start the process add the following code:
procedure DoSomething;
var
Intf : ISpecificSignal;
begin
if Supports(Application.MainForm,ISpecificSignal,Intf) then
Intf.PerformSignal(Handle);
end;
The largest advantage to this approach is your not limited to what parameters are passed (the interface can have any number of parameters, or no parameters), and it works without having to exercise the message pump.
EDIT Added Handle to avoid a situation where the existing form also needs the same notifications from other forms.
I don't know that specific of your problem, so this might not apply to your situation.
I guess your situation is FormA edit some value that affects FormB "rendering". The way I usually deal with this kind of situation is by making the value change to trigger an event. If more than 1 component in the system needs to be modified, I use a "multicasting" event.
A simple multicaster mechanism looks like this.
TMultiCastNotifyEventReceiver = class(TComponent)
private
FEvent : TNotifyEvent
public
property Event : TNotifyEvent read FEvent write FEvent;
end;
TMultiCastNotifyEvent = class(TComponent)
private
//TList or TObjectList to keep a list of Listener.
//Housekeeping code to make sure you don't keep reference to dangling pointers (I derived from TComponent to have access to the FreeNotification mechanis
public
procedure DoEvent(Sender : Tobject); //Same parameters as TNotifyEvent
function AddListener(NotifyEvent : TNotifyEvent) : TMultiCastNotifyEventReceiver
end;
That way, your formA doesn't need to know it's parent... Doesn't need to know FormB. FormB doesn't need to know FormA. Requirement for this to work though is that the FormA AND FormB must know the Value, and Value needs to know it needs to send a notification when it's modified. Usually results in better modularisation and encapsulation.
Then again, I put a lot of assumption about the nature of the problem you were trying to fix. I hope this helps anyway.
Why not just send the message to Application.Mainform.Handle, then in the Main form loop from 0 to MDIChildcount and resend the message to each one. Then, repond to the specific message only in the form class you want. I hope this serves your needs.

Resources