Indy Query Using Delphi - sockets

I have two TIdTCPClient components (TCPClient1 and TCPClient2) that share the same OnStatus property (see below).
The components are identified by their Tag property. How do I identify the calling TCPClient in the shared OnStatus event?
procedure TForm2.TCPClientStatus(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
begin
end;

In all event handlers, in every component, the Sender argument is always the object instance which is triggering the event.
To get a hand on that object, you can do a simple cast or a safe cast. In the case of the TIdTCPClient you use and the Tag property you want to check, it would become:
TIdTCPClient(Sender).Tag
or
(Sender as TIdTCPClient).Tag
The second form is safer because if, for any reason, Sender is not of the assumed type, you'll get an exception at runtime that you can manage/detect using a try/except construct.
You can even share the same event handler between different kinds of components. I don't know Indy enough to give a related example, so I'll use the OnClick event for a TButton and a TEdit. You can use the is operator to check the type:
if Sender is TButton then
// Do something using TButton(Sender).SomeProperty
else if Sender is TEdit then
// Do something using TEdit(Sender).SomeProperty

The ASender parameter points to the object that is firing the event. For a TIdTCPClient, that sender can be either:
the TIdTCPClient object itself
the client's IOHandler object
In the latter case, the TIdTCPClient.OnStatus event is hooked up to the IOHandler object only if that object is created implicitly within the client's Connect() method when the IOHandler property is nil. In that scenario, the TIdTCPClient is set as the Owner of that new object. On the other hand, if you explicitly assign your own TIdIOHandler-derived component to the IOHandler property before calling Connect(), the TIdTCPClient.OnStatus event will not be hooked up to the IOHandler component automatically, so you will have to assign a handler to that component's own OnStatus event separately, if needed.
So, inside of your TIdTCPClient.OnStatus event handler, assuming an implicit IOHandler object is being used, you can get back to the TIdTCPClient object like this:
procedure TForm2.TCPClientStatus(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
var
Client: TIdTCPClient;
IO: TIdIOHandler;
begin
Client := nil;
if ASender is TIdTCPClient then begin
Client := TIdTCPClient(ASender);
end
else if ASender is TIdIOHandler then begin
IO := TIdIOHandler(ASender);
if IO.Owner is TIdTCPClient then
Client := TIdTCPClient(IO.Owner);
end;
if Client <> nil then begin
// use Client.Tag as needed...
end;
end;

Related

OnKeyPress for Escape closes form by default

I have a question: How to have every newly created form in my application have implemented an OnKeyPress procedure and KeyPreview set to True?
I wish not to constantly add the procedure to the OnKeyPress event and set the KeyPreview to True manually.
Define your own form TKPForm which inherits from TForm and add a constructor which sets KeyPrview to True and have a keypress to do what you want in it.
Of course you'll have to override the keyprss if you need code unique to the new instance of TKPForm.
It's been a very long time since I did any Delphi but this shouldn't be too far off.
unit KPForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons;
type
TKPForm = class(TForm)
private
{ Private declarations }
public
constructor CreateNew(AOwner: TComponent); override;
end;
var
TKPForm: TKPForm
implementation
{$R *.dfm}
constructor KPForm.CreateNew(AOwner: TComponent);
begin
inherited Create(AOwner);
Self.KeyPreview = true;
Self.OnKeyPress = YourKeyPressEventProcedure;
end;
So then when ever you need a new form, you use a new KPForm.
Look at Visual Form Inheritance.
Hope it helps.
The best way I found is to create a template of a form and add it to the repository. Afterwards, set it as a "New form" in the repository.
In this case whenever you create a new form it will be that very template. All you have to do is change the name and caption.
This helped me in Delphi 7.

Create Form // Tabsheet // access Methods on Form

I am writing an application like this:
There is a main form with a PageControl on it,
In the Page Control I create Tabsheets,
In the Tabsheets, forms are placed.
This helps because the user can create one type of form more than once, like a standard pdf viewer opened in more than one tab, but displaying different data, in essence its the same form.
This works very well and is very useful! Unfortunately something important isn't working, this is the problem:
This code will work fine:
procedure TForm1.Button1Click(Sender: TObject);
begin
Label1.Caption := 'Hello';
end;
The caption will change, however, if I would like to call a method instead, like this:
Procedure changeLabel(str : String);
Begin
Form1.Label1.Caption := str;
End;
procedure TForm1.Button1Click(Sender: TObject);
begin
changeLabel('Hello');
end;
I get an access violation at Form1.Label1.Caption := str; in the procedure
Here is how I created the form:
procedure TfrmPage.CDMA1Click(Sender: TObject);
var
TabSheet: TTabSheet;
frmTest : TForm;
begin
TabSheet := TTabSheet.Create(PageControl1);
TabSheet.Caption := 'kjklhhj';
TabSheet.PageControl := PageControl1;
frmTest := TForm1.Create(Nil);
frmTest.Show;
frmTest.Parent := TabSheet;
end;
I did everything as everyone said here: another stackoverflow question
My Question, how can the newly created form, pinned to the Tabsheet, access its own procedures without throwing exceptions? Another piece that might be important: When I have delphi Auto-Create the form, theres no access violation but the method does nothing to the form, so I think the procedure might be changing things on the wrong form, one thats not created yet (which gives AV), and not to the one i just created, or the application is not calling the method on my new form, but calling one somewhere where that form isn't created yet.
If I auto-create the form, calling the procedure probably changes the label on the form that was created when the application started and not the new form..
Any help would be appreciated, as I've been googling for hours now with no real help to this problem
Thanks in advance :)
If you plan to create several instances of the TForm1 class, it is correct to not auto-create Form1. In fact you should delete the Form1: TForm1 declaration altogether to reduce the risk of errors.
You probably want to keep references to the forms you create f.ex. in a TList, possibly hosted by the TfrmPage instance depending on what TfrmPage is. (See below in discussion about AV)
Access Violation
When you created an instance of TForm1 in TfrmPage.CDMAClick() you assigned the reference of the created form to a local variable frmTest and then you show it in a tabsheet. You can click Button1 and change caption of Label1. However when you call the ChangeLabel procedure you state
Form1.Label1.Caption := str;
but the Form1 variable has never been assigned, therefore is nil and the AV is triggered. Now, since you assigned the forms reference to the local frmTest variable in TfrmPage.CDMAClick() you don't really have any reference anymore to the form, and cannot access it (actually, since you parented it to the tabsheet, you can search for it in the tabsheets controls). This is why I suggested to keep references to the forms you create in a list. Then you can access the forms through that list when needed.
My Question, how can the newly created form, pinned to the Tabsheet,
access its own procedures without throwing exceptions?
You can always access an objects methods, fields and properties from the object itself without using a reference variable. In fact, you should never use the objects reference variable from within the object:
Label1.Caption := 'Whatever'; // correct from within the form
Sometimes it may be necessary to use a reference within the object and for that purpose you can use self f.ex.:
self.Label1.Caption := 'Whatever'; // self can be used if needed
From an external method/procedure/function you need to use a reference to the object
Form1.Label1.Caption := 'Whatever'; // assuming Form1 has been assigned

Delphi create button with image

I have problem with creating button with Image and Label inside it.
Here my code:
Class:
type
Folder = class(TButton)
AName:TLabel;
AImage:TImage;
constructor Create(Nme:String;Path:String;Handle:TForm);
end;
Constructor:
constructor Folder.Create(Nme:String;Path:String;Handle:TForm);
begin
AImage:=Timage.Create(Self);
AName:=TLabel.Create(Self);
AImage.Parent:=Self;
AName.Parent:=Self;
AName.Caption:=Nme;
AImage.Picture.LoadFromFile(Path);
end;`
And event where I create this button:
procedure TForm3.Button1Click(Sender: TObject);
var Fld:Folder;
begin
Fld:=Folder.Create('It','D:\image.bmp',Form3);
Fld.Parent:=Form3;
Fld.Width:=100;
Fld.Height:=100;
end;
But when I'm creating this button it causes acess violation!What I must to do with it?
Problem:
The problem is that you have declared a customized version of constructor, but you are not calling the parent constructor of TButton class.
You need to change the constructor like this:
constructor Folder.Create(Nme: String; Path: String; Handle: TForm);
begin
inherited Create(Handle); // <- Add this line
AImage := TImage.Create(Self);
AName := TLabel.Create(Self);
AImage.Parent := Self;
AName.Parent := Self;
AName.Caption := Nme;
AImage.Picture.LoadFromFile(Path);
end;
General advice:
You need to learn how to debug such problems yourself.
Put a breakpoint on line Fld:=Folder.Create('It','D:\image.bmp',Form3); and use Step Over F8 / Trace Into F7 from Run menu to check your code line by line.
You will see that once you reach the line AImage.Parent:=Self; the exception occurs. This is because Self, which points to your Folder object, was not initialized correctly, and is not a proper TButton descendant.
You need to learn how to do that to progress any further with Delphi, and you will very soon be able to solve such problems yourself.
Also if you need to write a custom component for Delphi, invest some time learning more about the way components work and are being used. I would recommend the following guides on component writing:
Official Component Writer’s Guide
Introduction to Component Building by Ray Konopka
Also consult a guide on Delphi Coding Style.
At first glance:
Class names should begin with T
Class fields should begin with F instead of A
constructor should be in public section and fields in private or protected
You should use spaces around parameters, after variables in declarations and around operators

How to create a shared code unit for other units?

I have two VCL Forms: Form1 in Unit1 and Form2 in Unit2.
I also added another Unit into the Project, the Unit3.
In the Unit3, the Unit1 and Unit2 were added in the uses list. I have managed to create a class that may manipulate other units (Unit1 & Unit2):
unit Unit3;
interface
uses
Vcl.Forms, Unit1, Unit2;
type
Tmain = class
private
procedure openForm1;
procedure openForm2;
end;
var
Form1: TForm1;
Form2: TForm2;
implementation
procedure Tmain.openForm1;
begin
//I will create code to open Form1 soon
end;
procedure Tmain.openForm2;
begin
//I will create code to open Form2 soon
end;
end.
How can I run (or create properly) a Shared Code Unit for managing the Form1 & Form2 if the Project1's Source Code seemed not to run my Shared Code Unit (Unit3)?
program Project1;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2},
Unit3 in 'Unit3.pas';
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm2, Form2);
Application.Run;
end.
I am trying these algorithms to avoid circular references as my level of understanding through Lieven Keersmaekers's answer on How to manage circular references in delphi units?
First some basics in Delphi VCL applications.
At the top is the Application: TApplication object, which is created very early during initialization of your program (.exe).
As you have seen, it is first referred to in your program files, in the .dpr:
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm2, Form2);
Application.Run;
end.
The Application object is not visible. Therefore, Delphi has a concept of MainForm. The main form is intended to be the primary UI for the user, and is created by the first call to Application.CreateForm(). Unless prevented by the programmer, the main form is made visible, so the user can interact with the application. When the main form is closed, the application is terminated. Broadly taken, we can say that the main form controls the lifetime of the application.
The main form usually provides menus or other UI elements with wich the user can activate and/or show other forms and perform the tasks of the program.
Delphi names the form types, as you have seen, TForm1, TForm2 ... etc, and also declares a global variable for each form type to hold an instance of the form: Form1, Form2 ... etc. And once again, with these standard names Form1 is the main form of the application if it is the first to be created with a call to Application.CreateForm().
In your case it would be natural to design three forms:
Form1, a new form that replaces your current Unit3 and acts as the main form, with f.ex. two
buttons, each one to show Form2 and Form3 respectively.
Form2, actually current Form1 simply renamed as Form2.
Form3, actually current Form2 simply renamed as Form3.
In this case you would need to add Unit2 and Unit3 to the uses clause of Unit1:
implementation
uses Unit2, Unit3;
The event handlers for the buttons on Form1 look like this:
procedure TForm1.Button1Click(Sender: TObject);
begin
Form2.Show;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Form3.Show;
end;

Checking form's existence doesn't work

I have a Form for which I coded my own constructor:
constructor Create(AOwner: TComponent; AParent: TWinControl; ASender: TMyClass;
ATab: String); reintroduce; overload;
To create such a form I use the following:
try
MyForm := TMyClassForm.Create(nil, Self.Parent, Self as TMyClass, 'FirstTab');
MyForm.ShowModal;
finally
MyForm.Free;
end;
Somewhere else, before starting a procedure, I need to check whether this form is opened or not, so I check its existence with:
if (Assigned(MyForm)) and (MyForm.Active) and (MyForm.Showing) then
// Don't do the procedure
else
// Do the procedure
Now, if I open the form and close it, and I check this conditional statement, everytime I get true, but the Form is not opened and not showing anymore, because I freed it after creating.
Any idea of what could be the problem?
You called Free on the MyForm global method, but you did not modify the reference. So, MyForm still refers to the now destroyed form. And so Assigned(MyForm) evaluates as True, and then the other two tests operate on the stale object reference. Anything could happen when you operate on a stale object reference. In the situations that you have tried, it seems that the operations both return True. But another time you might get an access violation.
You'll want to set the MyForm variable to nil after call Free. At the risk of re-starting the great religious Delphi war of our times, you might contemplate using FreeAndNil(MyForm) in place of MyForm.Free.
You might find my answer here to be instructive: Why should I not use "if Assigned()" before using or freeing things?
Init:
MyForm := nil;
User FreeAndNil(MyForm)
if (Assigned(MyForm)) then
DON'T DO THE PROCEDURE
else
DO THE PROCEDURE