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.
Related
I have the following DLL procedure:
procedure OPENDB(IBTrans: TIBTransaction; IBQuery: TIBQuery;
SQL: String); stdcall;
begin
if IBQuery.Active = True then IBQuery.Active := False;
if IBTrans.Active = True then IBTrans.Active := False;
IBQuery.SQL.Clear;
IBQuery.SQL.Add(SQL);
IBTrans.Active := true;
IBQuery.Active := True;
end;
In my program I call the ddl that is in the \'winnt\\system32\' folder as follows:
procedure OPENDB(IBTrans: TIBTransaction; IBQuery: TIBQuery;
SQL: String); stdcall; external \'saudedll.dll\';
when i'm going to use the procedure to open the database i see an error. I only happens with procedures that use interbase components. If I declare the procedure in my own executable, it doesn't make any kind of mistake, it works perfectly. Can someone give me a hint? Thank you in advance!!
UPDATE -----------------
library Biblio;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES Project-View Source) USES clause if your DLL exports procedures or functions that pass strings as parameters or function results. This to all strings passed to and from your DLL—even those are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed
with your DLL. To avoid using BORLNDMM.DLL, pass string
using PChar or ShortString parameters. }
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, DB, Grids, DBGrids, Buttons, StdCtrls, Mask, DBCtrls, ExtCtrls,
ActnList, ComCtrls, FMTBcd, Provider, DBClient;
{$R *.res}
procedure LimpaCampos(vForm: TForm; vFormHandle: THandle);
var i : Integer;
begin
for i := 0 to (vForm.ControlCount -1) do
if vForm.Controls[i].ClassName = ´TEdit´ then
begin
TEdit(vForm.Controls[i]).Text:=´´;
end;
for i := 0 to (vForm.ControlCount -1) do
if vForm.Controls[i].ClassName = ´TDBEdit´ then
begin
TDBEdit(vForm.Controls[i]).Text:=´´;
end;
for i := 0 to (vForm.ControlCount -1) do
if vForm.Controls[i].ClassName = ´TCheckBox´ then
begin
TCheckBox(vForm.Controls[i]).Checked:= False;
end;
for i := 0 to (vForm.ControlCount -1) do
if vForm.Controls[i].ClassName = ´TDBCheckBox´ then
begin
TDBCheckBox(vForm.Controls[i]).Checked:= False;
end;
Exports
LimpaCampos;
begin
end.
And in the Form frmCadKits I declare the procedure and the dll.
...
public
{ Public declarations }
end;
procedure LimpaCampos(vForm: TForm; vFormHandle: THandle); external ´biblio.dll´;
var
...
And I make a call in the Form show
procedure TfrmCadKits.FormShow(Sender: TObject);
begin
LimpaCampos(frmCadKits,frmCadKits.Handle);
end;
And I get the error:
Project Application.exe raised class EAccessViolation with message 'Access violation
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);
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....
Summarization:
1. Manual typecast when debugging, as LachlanG and Ken pointed out.
2. Make use of the concept of Debugger Visualizers introduced since Delphi 2010.
3. Switch to generics counterparts.
=========================================
Take the following code for example:
If breakpoints are set at the end of TestRegular, and at the end of TestGenerics, respectively, one can see the items of the generic list(and even the content of the items) through the debug inspector, but nothing meaningful (not even the count) for the regular tobjectlist, when one hovers the mouse on the tmp variable. I am wondering if there is some way to achieve similar debug-time functionality for regular tobjectlist?
unit Unit2;
interface
uses
Contnrs, Generics.Collections,
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TMyItem = class;
TMyItemList = class;
TForm2 = class;
TMyItem = class
private
fname: string;
public
property name: string read fname;
constructor Create(aName: string);
end;
TMyItemList = class(TObjectList)
protected
procedure SetObject (Index: Integer; Item: TMyItem);
function GetObject (Index: Integer): TMyItem;
public
function Add (Obj: TMyItem): Integer;
procedure Insert (Index: Integer; Obj: TMyItem);
property Objects [Index: Integer]: TMyItem
read GetObject write SetObject; default;
end;
TForm2 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure TestRegular;
procedure TestGenerics;
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
{ TMyItem }
constructor TMyItem.Create(aName: string);
begin
fname := aName;
end;
{ TMyItemList }
function TMyItemList.Add(Obj: TMyItem): Integer;
begin
Result := inherited Add (Obj);
end;
procedure TMyItemList.SetObject(Index: Integer; Item: TMyItem);
begin
inherited SetItem (Index, Item);
end;
function TMyItemList.GetObject(Index: Integer): TMyItem;
begin
Result := inherited GetItem (Index) as TMyItem;
end;
procedure TMyItemList.Insert(Index: Integer; Obj: TMyItem);
begin
inherited Insert(Index, Obj);
end;
{TForm2}
procedure TForm2.FormCreate(Sender: TObject);
begin
TestGenerics;
TestRegular;
end;
procedure TForm2.TestRegular;
var
tmp: TMyItemList;
begin
tmp := TMyItemList.Create;
tmp.Add(TMyItem.Create('1'));
tmp.Add(TMyItem.Create('2'));
tmp.Free;
end;
procedure TForm2.TestGenerics;
var
tmp: TObjectList<TMyItem>;
begin
tmp := TObjectList<TMyItem>.Create;
tmp.Add(TMyItem.Create('1'));
tmp.Add(TMyItem.Create('2'));
tmp.Free;
end;
end.
I don't think you'll be able to improve what appear in the mouse cursor hover hint.
You can however use typecasts inside Debug windows just as you can within source code.
For example you could typecast the tmp variable to TObjectList(tmp) from within the Evaluation Window (Ctrl F7) or create a Watch (Ctrl F5) on the typecasted variable.
There are Debugger Visualizers that allow you to customise the debugger's visualization capabilities. I've never used them, but it is my understanding that you could combine them with some RTTI and give richer information about a TObject instance.
However, using generics is what you want here. It gives compile time typing which has manifest advantages. I'd simply do it that way.
I have to create an array and place all controls there in order to access them.Here's a short example:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
const Test:Array[0..2] of TButton = (Button1,Button2,Button3);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
end.
Undeclarated idenitifier 'Button1' at the line where I declarated my array.But it's declarated three lines above.
Where's the problem,how to put all controls in an array?
EDIT:
Thank you for your answers,but I've got problems:
var TestA:TObjectList<TButton>;
var index:TComponent;
begin
TestA := TObjectList<TButton>.Create(false);
for index in Form7 do
if pos(index.name, 'Button') = 1 then
TestA.add(TButton(index));
TestA[0].Caption := 'Test'; //Exception out of range.
Ben's right. You can't set up a control array in the form designer. But if you have 110 images, for this specific case you can put them into a TImageList component and treat its collection of images as an array.
If you've got a bunch of more normal controls, like buttons, you'll have to create an array and load them into it in code. There are two ways to do this. The simple way, for small arrays at least, is Ben's answer. For large control sets, or ones that change frequently, (where your design is not finished, for example,) as long as you make sure to give them all serial names (Button1, Button2, Button3...), you can try something like this:
var
index: TComponent;
list: TObjectList;
begin
list := TObjectList.Create(false); //DO NOT take ownership
for index in frmMyForm do
if pos('Button', index.name) = 1 then
list.add(index);
//do more stuff once the list is built
end;
(Use a TObjectList<TComponent>, or something even more specific, if you're using D2009.) Build the list, based on the code above, then write a sorting function callback that will sort them based on name and use it to sort the list, and you've got your "array."
You may not be able to reference public properties of your form in an array constant like that. Try doing it in your form constructor/OnCreate event instead.
procedure TForm1.FormCreate(Sender: TObject);
begin
Test[0] := Button1;
Test[1] := Button2;
Test[2] := Button3;
end;
This function will iterate over all the controls on a specified container, like a particular TPanel or even the entire form, and populate a specified TObjectList with your TImage controls.
procedure TForm1.AddImageControlsToList(AParent: TWinControl; AList: TObjectList; Recursive: boolean);
var
Index: integer;
AChild: TControl;
begin
for Index := 0 to AParent.ControlCount - 1 do
begin
AChild := AParent.Controls[Index];
if AChild is TImage then // Or whatever test you want to use
AList.Add(AChild)
else if Recursive and (AChild is TWinControl) then
AddImageControlsToList(TWinControl(AChild), AList, True);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
// Call like this or similar to get your list of images
// (assumes MyImageList is declared in Form)
MyImageList := TObjectList.Create(False);
AddImageControlsToList(Self, MyImageList, True);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// Destroy the list
FreeAndNil(MyImageList);
end;
How about this?
procedure TForm1.FormCreate(Sender: TObject);
begin
for b := 1 to 110 do
Test[b] := FindComponent('Button' + IntToStr(b)) as TButton;
end;
You'll have to declare the array as a variable rather than a constant and it will have to go from 1 to 110 rather than 0 to 109 but that's no problem.
I use this all the time - it is simple and fast (despite Mr Wheeler's comment)- declare the maxbuttons as a constant
var
Form1: TForm1;
pbutton:array[1..maxbuttons] of ^tbutton;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
(* Exit *)
var k:integer;
begin
for k:=1 to maxbuttons do dispose(pbutton[k]);
close;
end;
procedure TForm1.FormActivate(Sender: TObject);
var k:integer;
begin
(*note the buttons must be Button1, Button2 etc in sequence or you need to
allocate them manually eg pbutton[1]^:=exitbtn etc *)
for k:=1 to maxbuttons do
begin
new(pbutton[k]);
pbutton[k]^:= tbutton(FindComponent('Button'+IntToStr(k)));
end;
end;
procedure TForm1.ButtonMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var k:integer;
b:boolean;
begin
b:=false;
k:=1;
while (k<= maxbuttons) and (not b) do
begin
if pbutton[k]^ = sender then (Note sender indicates which button has been clicked)
begin
{ found it so do something}
b:=true;
end;
k:=k+1;
end;
end;
Try this
var
TestA:TObjectList;
index:TComponent;
begin
TestA := TObjectList<TButton>.Create(false);
try
for index in Form7 do
if (pos is TButton) OR {or/and} (pos.tag and 8=8) then
TestA.add(TButton(index));
if TestA.Count>0 then //Fix:Exception out of range.
TestA[0].Caption := 'Test';
finally
TestA.Free;
end;
end;