I'm only using TidPOP3 component from Indy 10 and my code is below:
type
TForm4 = class(TForm)
Button1: TButton;
Pop3: TIdPOP3;
IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
Mensaje : TIdMessage;
RchTMessage: TRichEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.Button1Click(Sender: TObject);
begin
Pop3 := TIdPOP3.Create;
Pop3.Host := 'pop.gmail.com';
Pop3.Port := 995;
Pop3.Username := '**********#gmail.com';
Pop3.Password := '**********';
try
if not Pop3.Connected then
begin
Pop3.Connect;
Application.ProcessMessages;
end;
except on E: Exception do RchTMessage.Lines.Add(E.Message);
end;
end;
end.
When I press the button I got the Error "Connection Closed Gracefully". I've already configured the gmail account i'm using to be ready to accept Pop3 connections and to accept non-secure connections from other apps. Also, I configured TIdSSLIOHandlerSocketOpenSSL component in the form.
Thanks and Happy New Year for everyone.
Your button OnClick handler is creating a new TIdPOP3 object instead of using the one that you dropped on your Form. You are not configuring that object to interact with Gmail using SSL/TLS, as you are not attaching the TIdSSLIOHandlerSocketOpenSSL to it, or setting its UseTLS property (to utUseImplicitTLS) .
Related
I'm using Delphi 10.3 Community Edition. I'm trying to drag and drop files from a Windows folder onto my application but the Windows message handler is not called when I drag and drop a file on the form.
This is what I have at the moment:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
protected
procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
ShellApi;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// Disable drag accept files
DragAcceptFiles(Self.Handle, true);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
// Enable drag accept files
DragAcceptFiles(Self.Handle, true);
end;
procedure TForm1.WMDropFiles(var Msg: TWMDropFiles);
begin
// Show a message
ShowMessage('File dropped');
// Set the message result
Msg.Result := 0;
inherited;
end;
end.
Like I said, when I drag and drop a file on the form, I can see that the file is accepted when dragged onto the form but when I drop the file, the WMDropFiles procedure is not called.
I also tried enabling the DragAcceptFiles in the CreateWnd procedure. But it still does not work.
...
public
procedure CreateWnd; override;
procedure DestroyWnd; override;
...
procedure TForm1.CreateWnd;
begin
inherited;
DragAcceptFiles(Handle, True);
end;
procedure TForm1.DestroyWnd;
begin
DragAcceptFiles(Handle, False);
inherited;
end;
I even tried running the Delpi IDE as Administrator.
Could it be a limitation of the Community Edition or am I missing something?
Addendum
I've now added a button to send a message WM_DROPFILES.
procedure TForm1.Button1Click(Sender: TObject);
begin
SendMessage(Self.Handle, WM_DROPFILES, Integer(self), 0);
end;
When I click the button, the WMDropFiles procedure is called. So then it works.
Ok, very interesting. I found this article:
How to Enable Drag and Drop for an Elevated MFC Application on Vista/Windows 7
So I added the following to my form create procedure:
procedure TForm1.FormCreate(Sender: TObject);
begin
// Enable drag accept files
DragAcceptFiles(Form1.Handle, true);
ChangeWindowMessageFilter (WM_DROPFILES, MSGFLT_ADD);
ChangeWindowMessageFilter (WM_COPYDATA, MSGFLT_ADD);
ChangeWindowMessageFilter ($0049, MSGFLT_ADD);
end;
And now it is working!
I changed the topic to include text "TListView" because I actually want to drop files on a TListView. Since I've solved the problem with dropping files on a form, I still had an issue with dropping files on a TListView.
So to drop the files on a TListView, you have to change the Handle to the listview's handle:
// Enable drag accept files
DragAcceptFiles(MyListview.Handle, true);
and
// Disable drag accept files
DragAcceptFiles(MyListview.Handle, false);
But that alone is not enough. You then need a Application events handler to catch the messages and handle them accordingly. So I just added a TApplicationEvents component to the form and added the following to the OnMessage event:
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
var Handled: Boolean);
begin
// If it is a drop files message and it is for the listview
if ((Msg.message = WM_DROPFILES)and (Msg.hwnd = MyListView.Handle)) then
begin
// Handle your dropped data here
end;
end;
I'm working on an application that requires multiple embedded instances and each of this instance logins to the same external site with different authentication details.
I'm using CEF (Chromium embedded framework ) in Delphi , I have a folder that i stored the cookies on it for different instance of my browsers , So i have this code in the first unit
procedure TForm2.Button1Click(Sender: TObject);
begin
form33 := Tform3.Create(nil);
form33.Show;
end;
when the form33 created
procedure TForm3.FormCreate(Sender: TObject);
var
CookieManager: ICefCookieManager;
folder: string;
begin
Randomize;
Chromium1.SetBrowserID(Random(1244));
folder := Randomtext(5);
if DirectoryExists(folder) = False then
MkDir(folder);
CookieManager := TCefCookieManagerRef.Global;
path := ExtractFilePath(Application.ExeName) + folder;
CookieManager.SetStoragePath(path, true);
end;
The problem is that when i open two or more of instances of the form33 , i can't have for each browser in each form33 a specific cookie ...
You should create a new manager for each instance by TCefCookieManagerRef.New(Path) and store the returned reference into some private field of your form. Then you need to return the stored reference to the Result parameter of the OnGetCookieManager event method:
type
TForm1 = class(TForm)
Chromium1: TChromium;
procedure FormCreate(Sender: TObject);
procedure Chromium1GetCookieManager(Sender: TObject;
out Result: ICefCookieManager);
private
FCookieManager: ICefCookieManager2;
end;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
FCookieManager := TCefCookieManagerRef.New('C:\UniquePathToTheCookieStorage');
end;
procedure TForm1.Chromium1GetCookieManager(Sender: TObject;
out Result: ICefCookieManager);
begin
Result := FCookieManager;
end;
I am modifying an Indy10 TCP/IP application and I would like your suggestions/opinions/sample code on implementing a client side function that does the following
a) on application startup when the splash screen is displayed, it verifies that the client computer has internet access and the TCP Server is up and running and waiting for communication. If this is not the case, the application should terminate.
b) does (a) above before ANY data exchange between the client and the server
In addition, does the server need to repeatedly broadcast some sort of message to inform potential clients that it is up and running?
Thanks for your assistance.
How to verify if it's possible to connect to a TCP server ?
To your first question; definitely wrap the connection attempt to a separate thread, which you'll run when your splash screen shows. In that thread you can simply try to Connect and catch the exception. If the exception is raised, the connection failed. If not, you were able to connect. For the notification about this state I would use custom messages, which you'll send to a splash screen form like shown in the following pseudocode:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdTCPClient;
const
WM_CONNECTION_NOTIFY = WM_USER + 1;
SC_CONNECTION_FAILURE = 0;
SC_CONNECTION_SUCCESS = 1;
type
TConnThread = class(TThread)
private
FMsgHandler: HWND;
FTCPClient: TIdTCPClient;
protected
procedure Execute; override;
public
constructor Create(const AHost: string; APort: Word; ATimeout: Integer;
AMsgHandler: HWND); reintroduce;
destructor Destroy; override;
end;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FConnThread: TConnThread;
procedure WMConnectionNotify(var AMessage: TMessage); message WM_CONNECTION_NOTIFY;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TConnThread }
constructor TConnThread.Create(const AHost: string; APort: Word;
ATimeout: Integer; AMsgHandler: HWND);
begin
inherited Create(False);
FreeOnTerminate := False;
FMsgHandler := AMsgHandler;
FTCPClient := TIdTCPClient.Create(nil);
FTCPClient.Host := AHost;
FTCPClient.Port := APort;
FTCPClient.ConnectTimeout := ATimeout;
end;
destructor TConnThread.Destroy;
begin
FTCPClient.Free;
inherited;
end;
procedure TConnThread.Execute;
begin
try
FTCPClient.Connect;
PostMessage(FMsgHandler, WM_CONNECTION_NOTIFY, 0, SC_CONNECTION_SUCCESS);
except
PostMessage(FMsgHandler, WM_CONNECTION_NOTIFY, 0, SC_CONNECTION_FAILURE);
end;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
FConnThread := TConnThread.Create('123.4.5.6', 123, 5000, Handle);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FConnThread.Free;
end;
procedure TForm1.WMConnectionNotify(var AMessage: TMessage);
begin
case AMessage.LParam of
// the connection failed
SC_CONNECTION_FAILURE: ;
// the connection succeeded
SC_CONNECTION_SUCCESS: ;
end;
end;
end.
Does the server need to repeatedly broadcast some sort of message to inform potential clients that is running ?
No, this works in a different direction - client asks server if it's running. It's like that simply because server doesn't know clients, but client knows server.
On "does the server need to repeatedly broadcast some sort of message":
There are systems (servers, services) which advertise their location (IP adress, port number) and even additional information (for example a status) to interested clients actively using IP Multicast.
It is easy to implement both server- and client side with Internet Direct (Indy) UDP components.
Here is a IP multicast example for Delphi for the open source message broker Apache ActiveMQ with full source code:
Discover ActiveMQ brokers with Delphi XE4 and Indy 10.6
I'm working on something which will require monitoring of many forms. From outside the form, and without putting any code inside the form, I need to somehow capture events from these forms, most likely in the form of windows messages. But how would you capture windows messages from outside the class it's related to?
My project has an object which wraps each form it is monitoring, and I presume this handling will go in this object. Essentially, when I create a form I want to monitor, I create a corresponding object which in turn gets added to a list of all created forms. Most importantly, when that form is closed, I have to know so I can remove this form's wrapper object from the list.
These events include:
Minimize
Maximize
Restore
Close
Focus in/out
What I DON'T want:
Any code inside any forms or form units for this handling
Inheriting the forms from any custom base form
Using the form's events such as OnClose because they will be used for other purposes
What I DO want:
Handling of windows messages for these events
Any tips on how to get windows messages from outside the class
Which windows messages I need to listen for
Question re-written with same information but different approach
You need to listen for particular windows messages being delivered to the form. The easiest way to do this is to assign the WindowProc property of the form. Remember to keep a hold of the previous value of WindowProc and call it from your replacement.
In your wrapper object declare a field like this:
FOriginalWindowProc: TWndMethod;
Then in the wrapper's constructor do this:
FOriginalWindowProc := Form.WindowProc;
Form.WindowProc := NewWindowProc;
Finally, implement the replacement window procedure:
procedure TFormWrapper.NewWindowProc(var Message: TMessage);
begin
//test for and respond to the messages of interest
FOriginalWindowProc(Message);
end;
Here's a more complete example of the solution that David Provided:
private
{ Private declarations }
SaveProc : TWndMethod;
procedure CommonWindowProc(var Message: TMessage);
...
procedure TForm1.Button1Click(Sender: TObject);
var
f : tForm2;
begin
f := tForm2.Create(nil);
SaveProc := f.WindowProc;
f.WindowProc := CommonWindowProc;
f.Show;
end;
procedure TForm1.CommonWindowProc(var Message: TMessage);
begin
case Message.Msg of
WM_SIZE : Memo1.Lines.Add('Resizing');
WM_CLOSE : Memo1.Lines.Add('Closing');
CM_MOUSEENTER : Memo1.Lines.Add('Mouse enter form');
CM_MOUSELEAVE : Memo1.Lines.Add('Mouse leaving form');
// all other messages will be available as needed
end;
SaveProc(Message); // Call the original handler for the other form
end;
A better solution than trying to work outside of the form would be to make every form descend from a common base form that implements the functionality. The form event handlers are exactly the right place to add this code but you'd write it all in the ancestor form. Any descendant form could still use the form events and as long as they always call inherited somewhere in the event handler the ancestor code would still execute.
Another option is create TApplicationEvents and assign a handler to OnMessage event. Once if it fired, use the FindControl function and Msg.hWnd to check if it is the tform type and do what ever you want without hookin
Using Windows Messages can really attain a fine granularity (Yes, its part of your requirements!) but in some user cases where relying just on the VCL Event Framework suffices, a similar solution can be suggested:
unit Host;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
THostForm = class(TForm)
Memo1: TMemo;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
FFormResize: TNotifyEvent;
FFormActivate: TNotifyEvent;
FFormDeactivate: TNotifyEvent;
FFormDestroy: TNotifyEvent;
procedure _FormResize(Sender: TObject);
procedure _FormActivate(Sender: TObject);
procedure _FormDeactivate(Sender: TObject);
procedure InternalEventHandlerInit(const AForm:TForm);
public
procedure Log(const Msg:string);
procedure Logln(const Msg:string);
end;
var
HostForm: THostForm;
implementation
{$R *.dfm}
procedure THostForm.Button1Click(Sender: TObject);
var
frm: TForm;
begin
frm := TForm.Create(nil);
frm.Name := 'EmbeddedForm';
frm.Caption := 'Embedded Form';
//
InternalEventHandlerInit(frm);
//
Logln('<'+frm.Caption+'> created.');
//
frm.Show;
end;
procedure THostForm.InternalEventHandlerInit(const AForm: TForm);
begin
FFormResize := AForm.OnResize;
AForm.OnResize := _FormResize;
//
FFormActivate := AForm.OnActivate;
AForm.OnActivate := _FormActivate;
//
FFormDeactivate := AForm.OnDeactivate;
AForm.OnDeactivate := _FormDeactivate;
end;
procedure THostForm.Log(const Msg: string);
begin
Memo1.Lines.Add(Msg);
end;
procedure THostForm.Logln(const Msg: string);
begin
Memo1.Lines.Add(Msg);
Memo1.Lines.Add('');
end;
procedure THostForm._FormActivate(Sender: TObject);
begin
Log('Before OnActivate <'+(Sender as TCustomForm).Caption+'>');
//
if Assigned(FFormActivate) then
FFormActivate(Sender) // <<<
else
Log('No OnActivate Event Handler attached in <'+(Sender as TCustomForm).Caption+'>');
//
Logln('After OnActivate <'+(Sender as TCustomForm).Caption+'>');
end;
procedure THostForm._FormDeactivate(Sender: TObject);
begin
Log('Before OnDeactivate <'+(Sender as TCustomForm).Caption+'>');
//
if Assigned(FFormDeactivate) then
FFormDeactivate(Sender)
else
Log('No OnDeActivate Event Handler attached in <'+(Sender as TCustomForm).Caption+'>');
//
Logln('After OnDeactivate <'+(Sender as TCustomForm).Caption+'>');
end;
procedure THostForm._FormResize(Sender: TObject);
begin
Log('Before OnResize <'+(Sender as TCustomForm).Caption+'>');
//
if Assigned(FFormResize) then
FFormResize(Sender)
else
Log('No OnResize Event Handler attached in <'+(Sender as TCustomForm).Caption+'>');
//
Logln('After OnResize <'+(Sender as TCustomForm).Caption+'>');
end;
end.
When i send a message from TCPClient to a TCPServer it will be handled using OnExecute event in the server . Now i want to handle the received messages in the Client but TCPClient doesn't have any event for this. So i have to make a thread to handle them manually. how can i do it ?
As others said in response to your question, TCP is not a message oriented protocol, but a stream one. I'll show you how to write and read to a very simple echo server (this is a slightly modified version of a server I did this week to answer other question):
The server OnExecute method looks like this:
procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
var
aByte: Byte;
begin
AContext.Connection.IOHandler.Writeln('Write anything, but A to exit');
repeat
aByte := AContext.Connection.IOHandler.ReadByte;
AContext.Connection.IOHandler.Write(aByte);
until aByte = 65;
AContext.Connection.IOHandler.Writeln('Good Bye');
AContext.Connection.Disconnect;
end;
This server starts with a welcome message, then just reads the connection byte per byte. The server replies the same byte, until the received byte is 65 (the disconnect command) 65 = 0x41 or $41. The server then end with a good bye message.
You can do this in a client:
procedure TForm3.Button1Click(Sender: TObject);
var
AByte: Byte;
begin
IdTCPClient1.Connect;
Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn); //we know there must be a welcome message!
Memo1.Lines.Add('');// a new line to write in!
AByte := 0;
while (IdTCPClient1.Connected) and (AByte <> 65) do
begin
AByte := NextByte;
IdTCPClient1.IOHandler.Write(AByte);
AByte := IdTCPClient1.IOHandler.ReadByte;
Memo1.Lines[Memo1.Lines.Count - 1] := Memo1.Lines[Memo1.Lines.Count - 1] + Chr(AByte);
end;
Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn); //we know there must be a goodbye message!
IdTCPClient1.Disconnect;
end;
The next byte procedure can be anything you want to provide a byte. For example, to get input from the user, you can turn the KeyPreview of your form to true and write a OnKeyPress event handler and the NextByte function like this:
procedure TForm3.FormKeyPress(Sender: TObject; var Key: Char);
begin
FCharBuffer := FCharBuffer + Key;
end;
function TForm3.NextByte: Byte;
begin
Application.ProcessMessages;
while FCharBuffer = '' do //if there is no input pending, just waint until the user adds input
begin
Sleep(10);
//this will allow the user to write the next char and the application to notice that
Application.ProcessMessages;
end;
Result := Byte(AnsiString(FCharBuffer[1])[1]); //just a byte, no UnicodeChars support
Delete(FCharBuffer, 1, 1);
end;
Anything the user writes in the form will be sent to the server and then read from there and added to memo1. If the input focus is already in Memo1 you'll see each character twice, one from the keyboard and the other form the server.
So, in order to write a simple client that gets info from a server, you have to know what to expect from the server. Is it a string? multiple strings? Integer? array? a binary file? encoded file? Is there a mark for the end of the connection? This things are usually defined at the protocol or by you, if you're creating a custom server/client pair.
To write a generic TCP without prior known of what to get from the server is possible, but complex due to the fact that there's no generic message abstraction at this level in the protocol.
Don't get confused by the fact there's transport messages, but a single server response can be split into several transport messages, and then re-assembled client side, your application don't control this. From an application point of view, the socket is a flow (stream) of incoming bytes. The way you interpret this as a message, a command or any kind of response from the server is up to you. The same is applicable server side... for example the onExecute event is a white sheet where you don't have a message abstraction too.
Maybe you're mixing the messages abstraction with the command abstraction... on a command based protocol the client sends strings containing commands and the server replies with strings containing responses (then probably more data). Take a look at the TIdCmdTCPServer/Client components.
EDIT
In comments OP states s/he wants to make this work on a thread, I'm not sure about what's the problem s/he is having with this, but I'm adding a thread example. The server is the same as shown before, just the client part for this simple server:
First, the thread class I'm using:
type
TCommThread = class(TThread)
private
FText: string;
protected
procedure Execute; override;
//this will hold the result of the communication
property Text: string read FText;
end;
procedure TCommThread.Execute;
const
//this is the message to be sent. I removed the A because the server will close
//the connection on the first A sent. I'm adding a final A to close the channel.
Str: AnsiString = 'HELLO, THIS IS _ THRE_DED CLIENT!A';
var
AByte: Byte;
I: Integer;
Client: TIdTCPClient;
Txt: TStringList;
begin
try
Client := TIdTCPClient.Create(nil);
try
Client.Host := 'localhost';
Client.Port := 1025;
Client.Connect;
Txt := TStringList.Create;
try
Txt.Add(Client.IOHandler.ReadLn); //we know there must be a welcome message!
Txt.Add('');// a new line to write in!
AByte := 0;
I := 0;
while (Client.Connected) and (AByte <> 65) do
begin
Inc(I);
AByte := Ord(Str[I]);
Client.IOHandler.Write(AByte);
AByte := Client.IOHandler.ReadByte;
Txt[Txt.Count - 1] := Txt[Txt.Count - 1] + Chr(AByte);
end;
Txt.Add(Client.IOHandler.ReadLn); //we know there must be a goodbye message!
FText := Txt.Text;
finally
Txt.Free;
end;
Client.Disconnect;
finally
Client.Free;
end;
except
on E:Exception do
FText := 'Error! ' + E.ClassName + '||' + E.Message;
end;
end;
Then, I'm adding this two methods to the form:
//this will collect the result of the thread execution on the Memo1 component.
procedure TForm3.AThreadTerminate(Sender: TObject);
begin
Memo1.Lines.Text := (Sender as TCommThread).Text;
end;
//this will spawn a new thread on a Create and forget basis.
//The OnTerminate event will fire the result collect.
procedure TForm3.Button2Click(Sender: TObject);
var
AThread: TCommThread;
begin
AThread := TCommThread.Create(True);
AThread.FreeOnTerminate := True;
AThread.OnTerminate := AThreadTerminate;
AThread.Start;
end;
TCP doesn't operate with messages. That is stream-based interface. Consequently don't expect that you will get a "message" on the receiver. Instead you read incoming data stream from the socket and parse it according to your high-level protocol.
Here is my code to Read / Write with Delphi 7. Using the Tcp Event Read.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ScktComp;
type
TForm1 = class(TForm)
ClientSocket1: TClientSocket;
Button1: TButton;
ListBox1: TListBox;
Edit1: TEdit;
Edit2: TEdit;
procedure Button1Click(Sender: TObject);
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
UsePort: Integer;
UseHost: String;
begin
UseHost := Edit1.Text;
UsePort := STRTOINT(Edit2.Text);
ClientSocket1.Port := UsePort;
ClientSocket1.Host := UseHost;
ClientSocket1.Active := true;
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
begin
ListBox1.Items.Add(ClientSocket1.Socket.ReceiveText);
end;
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
ErrorCode:=0;
ClientSocket1.Active := False;
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
ClientSocket1.Socket.SendText(Edit1.Text);
end;
end.
If you need the Indy client to handle incoming "messages" (definition of "message" depends on the protocol used), I recommend to take a look at the implementation of TIdTelnet in the protocols\IdTelnet unit.
This component uses a receiving thread, based on a TIdThread, which asynchronously receives messages from the Telnet server, and passes them to a message handler routine. If you have a similar protocol, this could be a good starting point.
Update: to be more specific, the procedure TIdTelnetReadThread.Run; in IdTelnet.pas is where the asynchronous client 'magic' happens, as you can see it uses Synchronize to run the data processing in the main thread - but of course your app could also do the data handling in the receiving thread, or pass it to a worker thread to keep the main thread untouched. The procedure does not use a loop, because looping / pausing / restarting is implemented in IdThread.
Add a TTimer.
Set its Interval to 1.
Write in OnTimer Event:
procedure TForm1.Timer1Timer(Sender: TObject);
var
s: string;
begin
if not IdTCPClient1.Connected then Exit;
if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
s := IdTCPClient1.IOHandler.InputBufferAsString;
Memo1.Lines.Add('Received: ' + s);
end;
Don't set Timer.Interval something else 1.
Because, the received data deletes after some milliseconds.