Delphi Parent Form Buttons - delphi

Is there a way to disable the parent windows buttons? I have a "working" form that is called by a lot of forms, that I would like to disable the parent form buttons until it's finished doing it's thing. Then turn them back on.
I'd like to do something that is attached to the OnShow event and onClose event.
Thanks
-Brad

Create the form you want to call, as in:
unit fMyModalForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TfrmMyModalForm = class(TForm)
procedure FormShow(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
fCallingForm: TForm;
{ Private declarations }
public
{ Public declarations }
property CallingForm: TForm read fCallingForm write fCallingForm;
end;
(*
var
frmMyModalForm: TfrmMyModalForm;
*)
implementation
{$R *.dfm}
procedure TfrmMyModalForm.FormShow(Sender: TObject);
begin
fCallingForm.Enabled := False;
end;
procedure TfrmMyModalForm.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
fCallingForm.Enabled := True;
end;
end.
Then after the button where you want to call this modal form:
unit fMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,
fMyModalForm;
type
TfrmMain = class(TForm)
btnCall: TButton;
btn1: TButton;
btn2: TButton;
procedure btnCallClick(Sender: TObject);
private
{ Private declarations }
f : TfrmMyModalForm;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.btnCallClick(Sender: TObject);
begin
if not Assigned(f)
then begin
f := TfrmMyModalForm.Create(Self);
f.CallingForm := Self;
end;
f.Show();
end;
end.
If you just want to disable all buttons you can iterate through them and in stead of disabling the CallingForm only disable the buttons on the CallingForm. See the Stack Overflow topic (and my answer) at :Cast a form dynamically EDITED: or see answer of _J_ (which basically show the topic).
I would use actions in stead of buttons though.

If the secondary window opens, does something and closes, then it would make sense to open it with ShowModal instead of Show, that way the user can't use the main form until the second form has closed.
If you want to iterate though all the buttons and disable or enable them, the code would look something like this:
var
i: Integer;
begin
for i := 0 to MainForm.ComponentCount - 1 do
if (MainForm.Components[i] is TButton) then
TButton(MainForm.Components[i]).Enabled := False;
end;

For stuf like this you will need only 1 line of code and an TActionList component.
Create an actionlist with an action and link the action to the button. An action has an OnUpdate event which lets you determine if the action (and thus the linked button) should be enabled. The OnUpdate event is triggered everytime the action should know if it must be enabled or not.

Related

How to replace a TDBNavigator with a TSpeedButton?

I did:
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
DataTable.qOrders.Next;
end;
It works, but the problem is when I click the button to reach the last record, the button is not disabled, like in a TDBNavigator.
How did I make the TSpeedButton disable and enable automatically like the TDBNavigator does?
Drop a TActionList onto your form and add the standard dataset actions to it. Connect these actions to your dataset and your speedbuttons to the appropriate actions. These standard actions will handle the enable state according to the current dataset state.
Here is a simple solution, that works perfectly for me.
I have a form (frmMain), dataset (dsWork), datasource (srcWork), grid and two speedbuttons (btnNext and btnPrior). The important part is in "OnDataChange" event of TDataSource. Here is the source code:
unit MainForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Grids, DBGrids, DB, DBTables, StdCtrls, ExtCtrls;
type
TfrmMain = class(TForm)
btnNext: TButton;
srcWork: TDataSource;
dsWork: TTable;
btnPrior: TButton;
grdWork: TDBGrid;
procedure btnNextClick(Sender: TObject);
procedure btnPriorClick(Sender: TObject);
procedure srcWorkDataChange(Sender: TObject; Field: TField);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.btnNextClick(Sender: TObject);
begin
if not dsWork.Eof then dsWork.Next;
end;
procedure TfrmMain.btnPriorClick(Sender: TObject);
begin
if not dsWork.Bof then dsWork.Prior;
end;
procedure TfrmMain.srcWorkDataChange(Sender: TObject; Field: TField);
begin
btnNext.Enabled := not dsWork.Eof;
btnPrior.Enabled := not dsWork.Bof;
end;
end.

Delphi update a parent form control from a child form

From a form, I create and show a second form. From the second form I want to update a control on the first form. But I get access violations. I can get it to work with the form in the autocreate, but not when I create the form with the create method I get violations.
Below is an example. If I run it as it is with form 11 in autocreate it works (I update a button caption in the first form). But, if in unit 10, if I comment out form11.show;, and I uncomment the create and the show and then take Form11 out of autocreate, I get an access violation.
Question - How can I update the parent form from the showed form when I create the form with a create method.
Unit10
unit Unit10;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm10 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form10: TForm10;
implementation
uses Unit11;
{$R *.dfm}
procedure TForm10.Button1Click(Sender: TObject);
var
fForm : TForm11;
Begin
// fForm := Form11.Create(Self); //This and next line give me access violation
// fForm.Show; // with form11 out of autocreate
form11.show; //This works with form11 in the autocreate.
end;
end.
Unit11
unit Unit11;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm11 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form11: TForm11;
implementation
uses unit10;
{$R *.dfm}
procedure TForm11.Button1Click(Sender: TObject);
begin
form10.button1.caption := 'Changed';
end;
end.
This is incorrect:
fForm := Form11.Create(Self)
It should be like this:
fForm := TForm11.Create(Self)
That is, TForm11, not Form11. To create an object, you have to call the constructor via the class.
I've always just had my forms auto-creating and can't think of a reason to not do so, but here's the likely cause of your problem:
The Create method needs to be called on a class, not on a variable.
This line would probably work to create a new instance of TForm11:
fForm := TForm11.Create(Self);

TButton + VK_Return

Here I have reproduced situation I have encounter couple of times. I have two forms. Form1 and Form2. Form1 has one edit field and OnKeyup event hooked up. Form2 has only one button and OnClick hooked up. When in Form1 user press VK_Retrun in field of type TEdit, Form2.Show is executed. Form2 shows up with focus on the button. The event OnClick is hooked up with the code "Close" inside. If user hit VK_RETURN key on the keyboard, Form2 closes as expected.. but here come the problem, Form2 got fired straight up again. It seems as when Form1 get focus the key is still in "a queue" and the edit field will proceed with VK_RETURN.
Here is a full listings of this situation:
unit UTestButton;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
procedure Edit1KeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses UTestButton2;
{$R *.dfm}
procedure TForm1.Edit1KeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_Return) THEN
Form2.Show;
end;
end.
This is the second unit.
unit UTestButton2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
Close;
end;
end.
In praxes I use sometimes simple form with labels or informations but one or two buttons and focus on button. User can hit key on keyboard as expected. If he use the vk_return and the underlying control use the key, I have to do some workaround to clear keys of buffer as with
PeekMessage(Mgs, 0, WM_CHAR, WM_CHAR, PM_REMOVE);
That´s not totally satisfying for me. Has anyone solution for this situation?
Simply handle the OnKeyDown event instead of OnKeyUp for your initial edit box:
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_RETURN) then
Form2.Show;
end;

Delphi TFrame Create/Destroy

How to create (when I want to show it) and destroy (when I want to hide it) frames on the main TForm? Frames' align = alClient.
I tried this:
The form:
unit main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, uFrame1, uFrame2;
type
TFormMain = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
f1: TFrame1;
f2: TFrame2;
end;
var
FormMain: TFormMain;
implementation
{$R *.dfm}
procedure TFormMain.FormCreate(Sender: TObject);
begin
f1 := TFrame1.Create(Self);
f1.Parent := Self;
end;
end.
First frame:
unit uFrame1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TFrame1 = class(TFrame)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
uses main, uFrame2;
procedure TFrame1.btn1Click(Sender: TObject);
begin
Self.Free;
FormMain.f2 := TFrame2.Create(FormMain);
FormMain.f2.Parent := FormMain;
end;
end.
Second frame:
unit uFrame2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TFrame2 = class(TFrame)
lbl1: TLabel;
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
uses main, uFrame1;
procedure TFrame2.btn1Click(Sender: TObject);
begin
Self.Free;
FormMain.f1 := TFrame1.Create(FormMain);
FormMain.f1.Parent := FormMain;
end;
end.
but it crashes with access vialataions when I click button on FrameStart or Frame1 (TForm FormCreate works fine i.e. it creates and shows FrameStart).
Delphi 7.
You can't call Self.Free in those event handlers. When the event handler returns, the VCL code that executes next still uses a reference to an object that you just freed. And that's where the access violation comes from. If you had been running with FastMM in full debug mode then you would have been shown a helpful diagnostic message.
These frames will have to kill themselves in a more roundabout manner. Post a CM_RELEASE message to the frame asking it to call Free on the frame. You post the message, rather than sending it, so that all the in flight messages are processed first. You'll need to add a message handler to the frame to respond to the message.
You've got some of it.
The basic idea behind this sort of stuff.
add a private property to your mainform to hold the frame.
in the button click handler assuming you only want one at a time do
if assigned(fMyFrame) then
begin
fMyFrame.Free;
fMyFrame := nil;
end;
fMyFrame := TSomeFrame.Create(self);
fMyFrame.Parent := self;
fMyFrame.blah...
When you just want to get rid of it as opposed to replacing it
if assigned(fMyFrame) then
begin
fMyFrame.Free;
fMyFrame := nil;
end;
If you want your frame to raise another frame, repeat the above in there.
If you want the frame you raise in a frame to be a sibling, e.g. have the same Parent, then don't use Form1 var.
fMyNextFrame.Parent = self.Parent;
There's a huge number of ways you could improve on this once you get it working, it's a classic scenario for interfaces and or inheritance, but figure this bit out first.
mySomething := TMySomething.Create();
you can now do something with something.
After you called free, it's not can't, it's don't and don't let anything else either.
Don't do self.free, it's like playing with matches in barrel of petrol. It will hurt....

Destroy object during event called by said object

I have a button. Its OnClick event calls a procedure which destroys the button, but then the "thread" wants to return to the OnClick event and I get an access violation.
I'm completely stumped!
You need to destroy the button after all its code is finished executing. The standard way to do this is by posting a user-defined message to the form and giving the form a message method that will interpret it. For example:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
WM_KILLCONTROL = WM_USER + 1;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
procedure KillControl(var message: TMessage); message WM_KILLCONTROL;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
PostMessage(self.Handle, WM_KILLCONTROL, 0, integer(Button1))
end;
procedure TForm1.KillControl(var message: TMessage);
var
control: TControl;
begin
control := TObject(message.LParam) as TControl;
assert(control.Owner = self);
control.Free;
end;
end.
This works because the message gets put into the Windows Message Queue and doesn't come out until everything before it (including the Click message that the button is currently responding to) is finished processing.
You could instead just enable a timer in the OnClick event, then write the Timer event first to disable the timer and then call the procedure you are currently calling from the OnClick event. Set up the timer disabled and with a short interval time.

Resources