dynamically placing forms on a pagecontrol - delphi

as an extension of my previous post forms an a pagecontrol at runtime
I need a solution how to pass a buttonclick event back to the parent pagecontrol.
Do I have a assign a click function as a property and assign a new click function to all my forms for all the buttons i have placed :-( .... much work , any better solution
MyMainForm = CLass( )
....
aPagecontrol : TPageControl;
aTabForm_1 : TTabForm_1 ; // in the real case I use an dynamic array
aTabForm_2 : TTabForm_2 ;
aTabForm_3 : TTabForm_3 ;
....
UserData : TUserdata ; // lot of user data ....
function MyMainForm.CreateTabAndForm: TTabForm_1;
var
tabSheet : TTabSheet;
begin
//Create a new tab sheet
tabSheet := TTabSheet.Create(PageControl1) ;
tabSheet.PageControl := PageControl1;
//create a form
Result := TTabForm_1.Create(tabSheet) ;
Result.Parent := tabSheet;
Result.Align := alClient;
Result.BorderStyle := bsNone;
Result.Visible := true;
tabSheet.Caption := Result.Caption;
//activate the sheet
PageControl1.ActiveSheet := tabSheet;
end;
// program code , now failing :
aTabForm_1 := CreateTabAndForm;
aTabForm_1.onclick := MyButtonOnclick; // here AV happens !!
....
end;
the definition of the form
//
TTabForm_1 = class(TForm)
...
property clickButton1 : TClickfunction .......
end;
Solution #1 -> pass all the data to TTabForm_1 using properties
Solution #2 -> pass Button Click event to Mainform
target : readable code - good design

One way to expose events raised inside a form or control is like this :
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
FButton1Clicked : TNotifyEvent; //Create a private TNotifyEvent field
public
// ...and expose it as a property
property OnButton1Click : TNotifyEvent read FButton1Clicked
write FButton1Clicked;
end;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
// Execute the method if it has been assigned when Button1 is clicked.
if Assigned(FButton1Clicked) then FButton1Clicked(Sender);
end;
end.
Which you would consume like :
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Unit2;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
FForm2 : TForm2;
procedure Form2ButtonClick(sender : TObject);//Create a TNotifyEvent handler
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FForm2 := TForm2.Create(self);
//Assign a method to your custom event property
FForm2.OnButton1Click := Form2ButtonClick;
FForm2.Show;
end;
procedure TForm1.Form2ButtonClick(sender: TObject);
begin
// Do Something...
end;
end.
Of course, you don't have to use a TNotifyEvent, you can create any custom event, with parameters, that you like. For example
type
TFooEvent = procedure(ANumber : double; Sender : TObject) of object;
Which you could then use to send data with the click event :
if Assigned(FButton1Clicked) then FButton1Clicked(1.23, Button1);

Related

How to make use of a dynamic button

Changing properties on a component is in my Delphi vocabulary. I created a button by writing code and it appears on the Form as its Parent, but I do not know how to execute anything with it.
Sample - create runtime TButton and set him event OnClick...
unit Unit1;
interface
uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;
type
{ TForm1 }
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
FButton : TButton;
procedure OnButtonClickTest(Sender: TObject);
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
FButton := TButton.Create(nil);
FButton.Parent := self;
FButton.Left := 10;
FButton.Top := 10;
FButton.Width := 75;
FButton.Height := 25;
FButton.Caption := 'Click';
FButton.OnClick := OnButtonClickTest;
end;
procedure TForm1.OnButtonClickTest(Sender: TObject);
begin
FButton.Caption := 'Test OK';
end;
end.
I create a dynamic button FButton. Place it on the main form (Parent: Self) and set the event handler to click on it (method: OnButtonClickTest). When you click on button, on her caption change text to "Test OK"

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.

Accept Drop in an embedde form

I can setup a drag and drop simple example as outlined in the following code
(excerpted from http://www.chami.com/tips/delphi/111196D.html)
But if I use an embedded form (a form contained in another form I am unable to drop a file on an embedded form: the embedded form does not act as a drop target
unit dropfile;
interface
uses
Windows, Messages, SysUtils, Classes,
Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
//>>>
// declare our DROPFILES message handler
procedure AcceptFiles( var msg : TMessage );
message WM_DROPFILES;
//<<<
end;
var
Form1: TForm1;
implementation
uses
//>>>
//
// this unit contains certain
// functions that we'll be using
//
ShellAPI;
//<<<
{$R *.DFM}
//>>>
procedure TForm1.AcceptFiles( var msg : TMessage );
const
cnMaxFileNameLen = 255;
var
i,
nCount : integer;
acFileName : array [0..cnMaxFileNameLen] of char;
begin
// find out how many files we're accepting
nCount := DragQueryFile( msg.WParam,
$FFFFFFFF,
acFileName,
cnMaxFileNameLen );
// query Windows one at a time for the file name
for i := 0 to nCount-1 do
begin
DragQueryFile( msg.WParam, i,
acFileName, cnMaxFileNameLen );
// do your thing with the acFileName
MessageBox( Handle, acFileName, '', MB_OK );
end;
// let Windows know that you're done
DragFinish( msg.WParam );
end;
//<<<
procedure TForm1.FormCreate(Sender: TObject);
begin
//>>>
//
// tell Windows that you're
// accepting drag and drop files
//
DragAcceptFiles( Handle, True );
//<<<
end;
end.
You are calling DragAcceptFiles() in the Form's OnCreate event. That event is called only one time during a Form's lifetime. But the Form's window may be recreated multiple times during the Form's lifetime. And that is certainly the case when embedding a Form inside another window. The Form's window gets recreated, but you are not calling DragAcceptFiles() on the newly recreated Form window. That is why your WM_DROPFILES message handler stops working.
To account for window recreation, you need to override the Form's virtual CreateWnd() and call DragAcceptFiles() from there instead.
unit dropfile;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
private
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
public
// declare our DROPFILES message handler
procedure AcceptFiles(var msg: TMessage); message WM_DROPFILES;
end;
var
Form1: TForm1;
implementation
uses
ShellAPI;
{$R *.DFM}
procedure TForm1.AcceptFiles(var msg: TMessage);
var
i, nCount: Integer;
acFileName: array [0..MAX_PATH-1] of Char;
begin
// find out how many files we're accepting
nCount := DragQueryFile(msg.WParam, $FFFFFFFF, nil, 0);
// query Windows one at a time for the file name
for i := 0 to nCount-1 do
begin
DragQueryFile(msg.WParam, i, acFileName, MAX_PATH);
// do your thing with the acFileName
MessageBox(Handle, acFileName, '', MB_OK);
end;
// let Windows know that you're done
DragFinish(msg.WParam);
end;
procedure TForm1.CreateWnd;
begin
inherited;
// tell Windows that you're
// accepting drag and drop files
DragAcceptFiles(Handle, True);
end;
procedure TForm1.DestroyWnd;
begin
// tell Windows that you're no
// longer accepting drag and drop files
DragAcceptFiles(Handle, False);
inherited;
end;
end.

Double click event on object created at run time - Delphi

I have created a simple Delphi form with a button that, when pressed, creates a label object in run time. I have created an on double click event for the label that shows a message to the screen. The problem is that after creating the label, I have to double click on the form before the double click event works on the label. Obviously this is not ideal as I would like to be able to double click on the label and trigger the event without having to first double click the form.
Here is the code for my form:
unit Unit4;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm4 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormDblClick(Sender: TObject);
procedure MyLabelDblClick(Sender:TObject);
private
{ Private declarations }
LabelObject: TLabel;
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.Button1Click(Sender: TObject);
begin
LabelObject := TLabel.Create(Self);
LabelObject.Left := 100;
LabelObject.Top := 100;
LabelObject.Width := 200;
LabelObject.Height := 20;
LabelObject.Visible := True;
LabelObject.Parent := Self;
LabelObject.Caption := 'My Run Time Label';
LabelObject.Cursor := crHandPoint;
end;
procedure TForm4.FormDblClick(Sender: TObject);
begin
LabelObject.OnDblClick := MyLabelDblClick;
end;
procedure TForm4.MyLabelDblClick(Sender: TObject);
begin
showmessage('You double clicked My Run Time Label');
end;
end.
Thanks in advance for any help with this matter.
The problem is that after creating the label, I have to double click on the form before the double click event works on the label.
Assign LabelObject.OnDblClick when creating the label, i.e. inside the Button1Click event.

Why is no output displayed?

I started learning classes and objects programming today. There is code in the handbook that I must copy to run and save. I need to create a class(TLine) and use that class for instantiating an object.
Problem : No output is displayed in my RichEdit component. I copied the code exactly from the book to delphi, but no output is displayed.
How the output should look: "**********"
My class:
unit Lines_U;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
Type
TLine = Class
Public
fSize : integer;
fPattern : char;
public
Constructor Create;
Procedure Draw(Var line: string);
end;
implementation
{ TLine }
Constructor TLine.Create;
begin
fSize := 10;
fPattern := '*';
end;
Procedure TLine.Draw(Var line: string);
Var
loop : integer;
begin
for loop := 1 to fSize do
begin
line := line + fPattern;
end;
end;
end.
Code for instantiating the Object of the TLine Class:
unit UseLine_U;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Lines_U, StdCtrls, ComCtrls;
type
TForm1 = class(TForm)
redOut: TRichEdit;
Procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
line : TLine;
implementation
{$R *.dfm}
Procedure TForm1.FormCreate(Sender: TObject);
Var tempLine : string;
begin
line := TLine.Create;
line.Draw(tempLine);
redOut.Lines.Add(tempLine);
end;
end.
The reason your code is not running is that your event handler Form1.FormCreate is not linked to the OnCreate event. Restore the link in the object inspector.
About event handlers
Never write event handlers (all those procedures starting with On...) manually. Always use the Object inspector to create them.
If you double click on an event, Delphi will create a code template for you that you can fill with data.
Make sure your event handlers are filled in the object inspector. If not they will not work (as you've seen).
If you want to remove an event handler do not remove it in the object inspector, but reduce the code inside the event handling procedure back to the empty template.
Delphi will see that it is empty and remove it on the next compile.
About your code
Other than the missing link there is nothing wrong with your code. It runs just fine.
There are a few style issues though, these have no bearing on the operation, but are important none the less.
Here's how I would rewrite your code.
unit Lines_U;
interface
//only import units that you actually use.
type //please type reserved words in all lowercase, this is Pascal not VB.
TLine = class
private //make data members private.
fSize : integer;
fPattern : char;
public
constructor Create;
procedure Draw(var line: string);
property Size: integer read fSize write fSize; //Use properties to expose data members.
property Pattern: char read fPattern write fPattern;
end;
implementation
{ TLine }
constructor TLine.Create;
begin
inherited; //make the inherited call in your constructor explicit.
fSize := 10;
fPattern := '*';
end;
procedure TLine.Draw(var line: string);
//var
//loop : integer; //use consistent indentation
begin
//Changing a string ten times in a row is inefficient.
//try to do your changes all at once.
//for loop := 1 to fSize do begin
// line := line + fPattern;
//end;
Line:= Line + StringOfChar(fPattern, fSize);
end;
end.
Your form:
unit UseLine_U;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, Lines_U;
//put your own unit last, to prevent name clashes with built in classes and functions.
type
TForm1 = class(TForm)
//note that the {nothing} line is really **published**.
//And data members should be private
//Line : TLine; //Line should be private.
RedOut: TRichEdit;
procedure FormCreate(Sender: TObject);
private
//Prefix all private data with `F` for Field.
FLine: TLine; //Line should be a item in the form, not a global var.
public
property Line: TLine read FLine; //read only access to line.
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
tempLine : string;
i: integer;
begin
//tempLine:= ''; //local variables should be initialized.
//However strings are always initialized to '', because they are managed types.
//everything else will contain random data unless you fill it!
FLine := TLine.Create;
Line.Draw(tempLine);
i:= 0; //init i, otherwise it will be random!
while i < 5 do begin //always use `begin-end` in loops, never a naked `do`
RedOut.Lines.Add(tempLine);
i:= i + 1;
end; {while} //I like to annotate my loop `end`s, but that's just me.
FreeAndNil(FLine); //Dispose of TLine when you're done with it.
end;
end.
I can think of other things, but I don't want to overload you.

Resources