delphi, send image with indy10 from client to server - delphi

How I can send from a Timage (clientside) to another Timage(serverside)?
I'm using delphi XE3 with idtcpclient1, idtcpserver1 (indy10 component).
I already tried to do something but I had some trouble.
Server side:
FileStream := TFileStream.Create('ciao.jpg', fmCreate);
AContext.Connection.IOHandler.LargeStream := True;
AContext.Connection.IOHandler.ReadStream(FileStream); FileStream.Free;
image1.Picture.LoadFromFile(sname);
Client side:
idTCPClient1.IOHandler.LargeStream := True;
FileStream := TFileStream.Create('hello.jpg', fmOpenRead);
IdTCPClient1.IOHandler.Write(FileStream,0,true);
filestream.Free;

Example implementation for the transfer of different graphic formats.
Main issue is that you will have to create an appropriate GraphicClass.
If an image is loaded from a file the class is determinate from the file extension.
In this implemetation we add the information to the stream.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdContext, Vcl.ExtCtrls, IdTCPConnection, IdTCPClient, IdBaseComponent,
IdComponent, IdCustomTCPServer, IdTCPServer, Vcl.StdCtrls, Vcl.Imaging.jpeg;
type
TForm1 = class(TForm)
IdTCPServer1: TIdTCPServer;
IdTCPClient1: TIdTCPClient;
Source: TImage;
Dest: TImage;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure IdTCPServer1Execute(AContext: TIdContext);
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
uses PNGImage;
{$R *.dfm}
//Enable transfer of different graphicformats
procedure Picture2Stream(DestStream: TMemoryStream; Picture: TPicture);
var
ms2: TMemoryStream;
TheClassName: AnsiString;
len: Byte;
begin
TheClassName := Picture.Graphic.ClassName;
len := Length(TheClassName);
DestStream.WriteBuffer(len, 1);
if len > 0 then // save GraphicClass name
DestStream.WriteBuffer(TheClassName[1], len);
ms2 := TMemoryStream.Create;
try // save graphic
Picture.Graphic.SaveToStream(ms2);
ms2.Position := 0;
if ms2.Size > 0 then
DestStream.CopyFrom(ms2, ms2.Size);
finally
ms2.Free;
end;
end;
Procedure LoadPictureFromStream(Picture: TPicture; SourceStream: TMemoryStream);
var
ms2: TMemoryStream;
len: Byte;
TheClassName: AnsiString;
Graphic: TGraphic;
GraphicClass: TGraphicClass;
begin
SourceStream.Position := 0;
SourceStream.ReadBuffer(len, 1);
SetLength(TheClassName, len);
if len > 0 then // read GraphicClass name
SourceStream.ReadBuffer(TheClassName[1], len);
GraphicClass := TGraphicClass(FindClass(TheClassName)); //(*)
if (GraphicClass <> nil) and (len > 0) then
begin
Graphic := GraphicClass.Create; // create appropriate graphic class
try
ms2 := TMemoryStream.Create;
try
ms2.CopyFrom(SourceStream, SourceStream.Size - len - 1);
ms2.Position := 0;
Graphic.LoadFromStream(ms2);
finally
ms2.Free;
end;
Picture.Assign(Graphic);
finally
Graphic.Free;
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ms: TMemoryStream;
begin
ms := TMemoryStream.Create;
try
Picture2Stream(ms, Source.Picture);
ms.Position := 0;
IdTCPClient1.Host := '127.0.0.1';
IdTCPClient1.Port := 12345;
IdTCPClient1.Connect;
IdTCPClient1.IOHandler.LargeStream := true;
IdTCPClient1.IOHandler.Write(ms, ms.Size, true);
finally
ms.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
IdTCPServer1.DefaultPort := 12345;
IdTCPServer1.Active := true;
ReportMemoryLeaksOnShutDown := true;
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
ms: TMemoryStream;
begin
ms := TMemoryStream.Create;
try
AContext.Connection.IOHandler.LargeStream := true;
AContext.Connection.IOHandler.ReadStream(ms);
TThread.Synchronize(nil,
Procedure
begin
LoadPictureFromStream(Dest.Picture, ms);
end);
finally
ms.Free;
end;
end;
initialization
// RegisterClasses to enable FindClass (*)
RegisterClasses([TIcon, TMetafile, TBitmap, TJPEGImage, TPngImage]);
end.

Your question is unclear, but it seems that you're trying to transfer the content of one 'TImage' (on the client) to a TImage on the server. It's unclear whether you mean an image file or an actual TImage, though. I'm going to go with "the picture being displayed in a TImage on the client" being sent to the server.
You can use TMemoryStream instead of TFileStream. If you really mean to send the image displayed in a TImage.Picture, you can do something like this (untested):
// Server side
var
Jpg: TJpegImage;
begin
Strm := TMemoryStream.Create;
try
Strm.Position := 0;
AContext.Connection.IOHandler.LargeStream := True;
AContext.Connection.IOHandler.ReadStream(Strm);
Strm.Position := 0;
Jpg := TJpegImage.Create;
try
Jpg.LoadFromStream(Strm);
Image1.Picture.Assign(Jpg);
finally
Jpg.Free;
end;
finally
Strm.Free;
end;
end;
// Client side
IdTCPClient1.IOHandler.LargeStream := True;
Strm := TMemoryStream.Create;
try
Image1.Picture.Graphic.SaveToStream(Strm);
Strm.Position := 0;
IdTCPClient1.IOHandler.Write(Strm, 0, True);
finally
Strm.Free;
end;
If that's not what you want, edit your question so we can understand what you're trying to do. (Don't tell us in comments, but actually edit your question to make it more clear.)

Related

Delphi: JPG to Bitmap: Incompatible types: 'TPersistent' and 'TFileName'

I'm writing a program that:
1.- Ask the user to select a file, Any kind of image (JPG, PNG, etc)
2.- Let's user pixellate the image and shows the new pixellated image.
Since my test image is a JPG, I'm getting error: Incompatible types: 'TPersistent' and 'TFileName'
Before trying to convert the JPG to Bitmap, I was getting:
Bitmap image is not valid
Code:
unit demo_2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Imaging.Jpeg;
var
OpenDialog: TOpenDialog;
OpenFolder: TFileOpenDialog;
OriginalImage: TBitmap;
PixellatedImage: TBitmap;
type
TForm1 = class(TForm)
btn1_select_img: TButton;
btn2_select_output_path: TButton;
lbl_selected_file: TLabel;
lbl_output_path: TLabel;
img1: TImage;
pnl_img: TPanel;
pnl_btns: TPanel;
procedure btn1_select_imgClick(Sender: TObject);
procedure btn2_select_output_pathClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btn1_select_imgClick(Sender: TObject);
begin
OpenDialog := TOpenDialog.Create(nil);
OpenDialog.Filter := 'All Files|*.*';
OpenDialog.Options := [ofPathMustExist, ofFileMustExist];
try
if OpenDialog.Execute then
begin
// Print the selected file's path to the console
//WriteLn(OpenDialog.FileName);
lbl_selected_file.Caption := OpenDialog.FileName;
img1.Picture.LoadFromFile(OpenDialog.FileName);
end;
finally
//OpenDialog.Free;
end;
end;
procedure TForm1.btn2_select_output_pathClick(Sender: TObject);
begin
//OpenFolder := TFileOpenDialog.Create(nil); for folder selection
//OpenFolder.Options := [fdoPickFolders]; for folder selection
try
//if OpenFolder.Execute then
begin
PixellatedImage := TBitmap.Create;
//PixellatedImage.LoadFromFile(OpenDialog.FileName);
PixellatedImage.Assign(OpenDialog.FileName);
// Pixellate the image by setting the Width and Height to a small value
PixellatedImage.Width := 10;
PixellatedImage.Height := 10;
img1.Picture.Bitmap := PixellatedImage;
//lbl_output_path.Caption := OpenFolder.FileName; for folder selection
end;
finally
//OpenFolder.Free;
//OpenDialog.Free;
PixellatedImage.Free;
end;
end;
end.
Since my test image is a JPG, I'm getting error: Incompatible types: 'TPersistent' and 'TFileName'
Actually, that's wrong in the sense that the error has nothing to do with the image being JPG. So while both "my test image is a JPG" and "I'm getting error ..." are correct, the implication ("since") is not.
The TPersistent.Assign method used in
PixellatedImage.Assign(OpenDialog.FileName);
requires a TPersistent object. In this case, when you are dealing with graphics, you typically need a TGraphic instance. Hence, you cannot pass a string, even if that string happens to be a file name of an image file.
So if you want to use the Assign method on a graphics object, you need to pass it another graphics object -- one you may have loaded from file using its own LoadFromFile:
procedure TForm1.FormCreate(Sender: TObject);
begin
var JpegImage := TJPEGImage.Create;
try
var OpenDlg := TFileOpenDialog.Create(Self);
try
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'JPEG images';
FileMask := '*.jpg';
end;
if OpenDlg.Execute then
JpegImage.LoadFromFile(OpenDlg.FileName)
else
Exit;
finally
OpenDlg.Free;
end;
var BmpImage := TBitmap.Create;
try
BmpImage.Assign(JpegImage);
// For example: BmpImage.SaveToFile('K:\bitmap.bmp');
finally
BmpImage.Free;
end;
finally
JpegImage.Free;
end;
end;
Also, please note that you must use the idiom
LFrog := TFrog.Create;
try
// use LFrog
finally
LFrog.Free;
end
and never
try
LFrog := TFrog.Create;
// use LFrog
finally // WRONG!
LFrog.Free;
end
assuming LFrog is a local variable. If LFrog isn't a local variable, it probably should be made local! Otherwise, it is important to do FreeAndNil on it and not only Free.
Update. The Q was changed so it no longer is about JPG -> BMP, but "any" image file to BMP. Then perhaps the best way is to use the Windows Imaging Component:
procedure TForm1.FormCreate(Sender: TObject);
begin
var WicImage := TWICImage.Create;
try
var OpenDlg := TFileOpenDialog.Create(Self);
try
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'All image files';
FileMask := '*.jpg;*.tiff;*.tif;*.png;*.gif;*.bmp';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'JPEG images';
FileMask := '*.jpg';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'TIFF images';
FileMask := '*.tiff;*.tif';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'PNG images';
FileMask := '*.png';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'GIF images';
FileMask := '*.gif';
end;
with OpenDlg.FileTypes.Add do
begin
DisplayName := 'Bitmap images';
FileMask := '*.bmp';
end;
// etc.
if OpenDlg.Execute then
WicImage.LoadFromFile(OpenDlg.FileName);
finally
OpenDlg.Free;
end;
var BmpImage := TBitmap.Create;
try
BmpImage.Assign(WicImage);
// For example: BmpImage.SaveToFile('K:\bitmap.bmp');
finally
BmpImage.Free;
end;
finally
WicImage.Free;
end;
end;
Finally, I note that you write
// Pixellate the image by setting the Width and Height to a small value
PixellatedImage.Width := 10;
PixellatedImage.Height := 10;
Although not relevant to your main question about TGraphic.Assign, I should note that setting a TBitmap's Width and Height very much doesn't pixelate the image in the usual sense of the word. (Algorithmically, pixelation should be done like in this Pixelate procedure.)

How load a base64 encrypted url in a TWebBrowser or TImage?

I have this url encrypted in base64 that is a animated QRCode.
How i can load it in a TWebBrowser (or TImage)? Thanks in advance.
Edit:
Here was my attempt, but without success:
uses
IdHTTP, IdSSLOpenSSL, GIFImg, ClipBrd;
type
TForm1 = class(TForm)
Button1: TButton;
Image1: TImage;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function GetStrFromClipbrd: string;
begin
if Clipboard.HasFormat(CF_TEXT) then
Result := Clipboard.AsText
else
begin
ShowMessage('There is no text in the Clipboard!');
Result := '';
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MS: TMemoryStream;
IdHTTP1: TIdHTTP;
GIF: TGIFImage;
begin
MS := TMemoryStream.Create;
try
IdHTTP1 := TIdHTTP.Create;
try
GIF := TGIFImage.Create;
try
IdHTTP1.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP1);
IdHTTP1.HandleRedirects := True;
IdHTTP1.Get(GetStrFromClipbrd, MS);
MS.Seek(0, soFromBeginning);
GIF.LoadFromStream(MS);
Image1.Picture.Assign(GIF);
(Image1.Picture.Graphic as TGIFImage).Animate := True;
//(Image1.Picture.Graphic as TGIFImage).AnimationSpeed := 500;
finally
FreeAndNil(GIF);
end;
finally
FreeAndNil(IdHTTP1);
end;
finally
FreeAndNil(MS);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Form1.DoubleBuffered := True;
end;
end.
data: is not a URL type you can request with TIdHTTP (or any other HTTP library), nor do you need to since all of the data is encoded directly in the URL itself. So simply extract the base64 portion and decode it using any base64 decoder of your choosing.
Since your code is already using Indy anyway, you could use its TIdDecoderMIME class in the IdCoderMIME unit to decode the base64 data to a binary stream, such as with the TIdDecoderMIME.DecodeStream() class procedure. Then you can load that stream into an appropriate TGraphic descendant (TGIFImage, TBitmap, etc), and then finally you can load that graphic into your TImage.
For example:
uses
IdGlobal, IdGlobalProtocols, IdCoderMIME, IdHTTP, IdSSLOpenSSL,
Graphics, GIFImg, JPEG, ClipBrd;
function GetStrFromClipbrd: string;
const
CTextFormat = {$IFDEF UNICODE}CF_UNICODETEXT{$ELSE}CF_TEXT{$ENDIF};
begin
if Clipboard.HasFormat(CTextFormat) then
Result := Clipboard.AsText
else
Result := '';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Graphic: TGraphic;
MS: TMemoryStream;
IdHTTP1: TIdHTTP;
URL, ContentType: string;
begin
URL := GetStrFromClipbrd;
if URL = '' then
begin
ShowMessage('There is no text in the Clipboard!');
Exit;
end;
Graphic := nil;
try
MS := TMemoryStream.Create;
try
if TextStartsWith(URL, 'data:') then
begin
Fetch(URL, ':');
ContentType := Fetch(URL, ',');
if not TextEndsWith(ContentType, ';base64') then
begin
ShowMessage('Data is not encoded in base64!');
Exit;
end;
SetLength(ContentType, Length(ContentType)-7);
TIdDecoderMIME.DecodeStream(URL, MS);
if ContentType = '' then
ContentType := 'text/plain;charset=US-ASCII';
end else
begin
IdHTTP1 := TIdHTTP.Create;
try
IdHTTP1.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP1);
IdHTTP1.HandleRedirects := True;
IdHTTP1.Get(URL, MS);
ContentType := IdHTTP1.Response.ContentType;
finally
IdHTTP1.Free;
end;
end;
MS.Position := 0;
case PosInStrArray(ExtractHeaderItem(ContentType),
['image/gif', 'image/jpeg', 'image/bmp'{, ...}],
False) of
0: Graphic := TGIFImage.Create;
1: Graphic := TJPEGImage.Create;
2: Graphic := TBitmap.Create;
// ...
else
ShowMessage('Unsupported image type!');
Exit;
end;
{ the 'data:' URL you provided is malformed, is says the image type
is 'image/bmp' even though it is actually a GIF and thus should
say 'image/gif'. To avoid problems with the above code determining
the wrong TGraphic class to use in that case, you can instead look
at the first few bytes of the decoded data to determinate its actual
image type, eg...
const
Signature_GIF87a: array[0..5] of Byte = ($47,$49,$46,$38,$37,$61);
Signature_GIF89a: array[0..5] of Byte = ($47,$49,$46,$38,$39,$61);
Signature_JPEG: array[0..2] of Byte = ($FF,$D8,$FF);
Signature_BMP: array[0..1] of Byte = ($42,$4D);
...
if (MS.Size >= 6) and
(CompareMem(MS.Memory, #Signature_GIF87a, 6) or
CompareMem(MS.Memory, #Signature_GIF89a, 6)) then
begin
Graphic := TGIFImage.Create;
end
else if (MS.Size >= 3) and
CompareMem(MS.Memory, #Signature_JPEG, 3) then
begin
Graphic := TJPEGImage.Create;
end
else if (MS.Size >= 2) and
CompareMem(MS.Memory, #Signature_BMP, 2) then
begin
Graphic := TBitmap.Create;
end
...
else
ShowMessage('Unsupported image type!');
Exit;
end;
}
Graphic.LoadFromStream(MS);
finally
MS.Free;
end;
Image1.Picture.Assign(Graphic);
finally
Graphic.Free;
end;
if Image.Picture.Graphic is TGIFImage then
begin
TGIFImage(Image.Picture.Graphic).Animate := True;
//TGIFImage(Image.Picture.Graphic).AnimationSpeed := 500;
end;
end;

How to use DefineProperties in a custom Class Object for dynamic Arrays - Delphi

I'm trying to create my own class object and use it to store various data types for my application, this all works fine when using Published Properties, I can stream these to disk and back with no problems. But I need to stream some dynamic Arrays of integer types as well.
type
TArrayOfInteger = array of integer;
TSetting = class(TComponent)
private
fIntVal: integer;
fIntArr: TArrayOfInteger;
procedure ReadIntArr(Reader: TReader);
procedure WriteIntArr(Writer: TWriter);
protected
procedure DefineProperties(Filer: TFiler); override;
published
property intval: integer read fIntVal write fIntVal;
property intArr: TArrayOfInteger read fIntArr write fIntArr;
end;
{ TSetting }
procedure TSetting.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('intArr', ReadIntArr, WriteIntArr, true);
end;
procedure TSetting.ReadIntArr(Reader: TReader);
var
i: integer;
lvVal:Integer;
begin
i:=low(fintArr);
Reader.ReadListBegin;
{j := Reader.ReadInteger();
setlength(fIntArr, j);
for i := 0 to j - 1 do
begin
fIntArr[i] := Reader.ReadInteger();
end;}
while not Reader.EndOfList do begin
fIntArr[i]:=Reader.ReadInteger;
Inc(i);
end;
Reader.ReadListEnd;
end;
procedure TSetting.WriteIntArr(Writer: TWriter);
var
i: integer;
begin
Writer.WriteListBegin;
//Writer.WriteInteger(integer(Length(fIntArr)));
for i := Low(fIntArr) to High(fIntArr) do
begin
Writer.WriteInteger(fIntArr[i]);
end;
Writer.WriteListEnd;
end;
function ClassToStr(pvClass:TComponent):ansiString;
var
inStream, outStream: TMemoryStream;
begin
inStream := TMemoryStream.Create;
outStream := TMemoryStream.Create;
try
inStream.WriteComponentRes(pvClass.ClassName, pvClass);
//inStream.WriteComponent(pvClass);
inStream.Position := 0;
ObjectResourceToText(inStream, outStream);
// ObjectBinaryToText(inStream,outStream);
outStream.Position := 0;
SetLength(Result,outStream.Size+1);
FillChar(result[1],outStream.Size+1,0);
outStream.ReadBuffer(result[1],outStream.Size);
finally
FreeAndNil(inStream);
FreeAndNil(outStream);
end;
end;
function StrToClass(pvStr:AnsiString;pvComponent:TComponent):tcomponent;
var
inStream, outStream: TMemoryStream;
begin
inStream := TMemoryStream.Create;
outStream := TMemoryStream.Create;
try
if (pvStr<>'') then
inStream.WriteBuffer(pvStr[1],length(pvStr));
inStream.Position:=0;
ObjectTextToResource(inStream, outStream);
// ObjectTextToBinary(inStream,outStream);
outStream.Position:=0;
result:=outStream.ReadComponentRes(pvComponent); //*****Exception Fired*****
//result:=outStream.ReadComponent(pvComponent);
finally
FreeAndNil(inStream);
FreeAndNil(outStream);
end;
end;
=============
//test
procedure TForm1.btn5Click(Sender: TObject);
var
lvObj,lv1: TSetting;
lvStr:String;
lvArr:TArrayOfInteger;
begin
lvObj := TSetting.Create(nil);
try
lvObj.intval := 12345;
setlength(lvArr, 3);
lvArr[0] := 222;
lvArr[1] := 333;
lvArr[2] := 444;
lvObj.intArr:=lvArr;
lvStr:=ClassToStr(lvObj);
RegisterClass(TSetting);
lvObj.intval:=1;
lv1:=TSetting( StrToClass(lvStr,lvObj));
if (lv1.intval>0) then
mmo1.Text:=lvStr;
finally
FreeAndNil(lvObj);
end;
// WriteComponentResFile(ExtractFilePath(ParamStr(0))+ 'd.res',self);
end;
//First chance exception at $77925B68. Exception class EReadError with message 'Property does not exist'. Process Project1.exe (23512)
//First chance exception at $77925B68. Exception class EReadError with message 'Error reading TSetting.: Property does not exist'. Process Project1.exe (23512)
result:=outStream.ReadComponentRes(pvComponent); //*****Exception Fired*****
You are not allocating the array when reading it. You could do that like so:
procedure TSetting.ReadIntArr(Reader: TReader);
begin
fIntArr := nil;
Reader.ReadListBegin;
while not Reader.EndOfList do begin
SetLength(fIntArr, Length(fIntArr) + 1);
fIntArr[high(fIntArr)] := Reader.ReadInteger;
end;
Reader.ReadListEnd;
end;
The other change that you need to make is to move intArr to be a public property. You cannot have it published, and also define a property with the same name in DefineProperties.
I am somewhat dubious of your use of AnsiString. I would have expected UTF-8 encoded bytes in case of non-ASCII characters. Perhaps you should be using a string stream with the appropriate encoding specified.
Personally I am rather sceptical of using form streaming in this way. I would prefer to use a standard format such as JSON.
You are not allocating the array before reading data into it. You were on the right track to have WriteIntArr() save the array length and ReadIntArr() to allocate the array based on that value, so you should re-enable that logic, eg:
type
TArrayOfInteger = array of integer;
TSetting = class(TComponent)
private
fIntVal: integer;
fIntArr: TArrayOfInteger;
procedure ReadIntArr(Reader: TReader);
procedure WriteIntArr(Writer: TWriter);
protected
procedure DefineProperties(Filer: TFiler); override;
public
property intArr: TArrayOfInteger read fIntArr write fIntArr;
published
property intval: integer read fIntVal write fIntVal;
end;
{ TSetting }
procedure TSetting.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('intArr', ReadIntArr, WriteIntArr, true);
end;
procedure TSetting.ReadIntArr(Reader: TReader);
var
i: integer;
begin
i := Reader.ReadInteger;
SetLength(fIntArr, i);
for i := Low(fIntArr) to High(fIntArr) do
fIntArr[i] := Reader.ReadInteger;
end;
procedure TSetting.WriteIntArr(Writer: TWriter);
var
i: integer;
begin
Writer.WriteInteger(Length(fIntArr));
for i := Low(fIntArr) to High(fIntArr) do
Writer.WriteInteger(fIntArr[i]);
end;
Alternatively:
type
TArrayOfInteger = array of integer;
TSetting = class(TComponent)
private
fIntVal: integer;
fIntArr: TArrayOfInteger;
procedure ReadIntArr(Stream: TStream);
procedure WriteIntArr(Stream: TStream);
protected
procedure DefineProperties(Filer: TFiler); override;
public
property intArr: TArrayOfInteger read fIntArr write fIntArr;
published
property intval: integer read fIntVal write fIntVal;
end;
{ TSetting }
procedure TSetting.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineBinaryProperty('intArr', ReadIntArr, WriteIntArr, true);
end;
procedure TSetting.ReadIntArr(Stream: TStream);
var
i: integer;
begin
Stream.ReadBuffer(i, SizeOf(Integer));
SetLength(fIntArr, i);
for i := Low(fIntArr) to High(fIntArr) do
Stream.ReadBuffer(fIntArr[i], SizeOf(Integer));
end;
procedure TSetting.WriteIntArr(Stream: TStream);
var
i: integer;
begin
i := Length(fIntArr);
Stream.WriteBuffer(i, SizeOf(Integer));
for i := Low(fIntArr) to High(fIntArr) do
Stream.WriteBuffer(fIntArr[i], SizeOf(Integer));
end;
I modified the source, it give a demon that how to clone a user class and clone a form . It worked.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TArrayOfInteger = array of integer;
TSetting = class(TComponent)
private
fIntVal: integer;
fIntArr: TArrayOfInteger;
procedure ReadIntArr(Reader: TReader);
procedure WriteIntArr(Writer: TWriter);
protected
procedure DefineProperties(Filer: TFiler); override;
public
property intArr: TArrayOfInteger read fIntArr write fIntArr;
published
property intval: integer read fIntVal write fIntVal;
end;
TForm1 = class(TForm)
btnCloneClass: TButton;
mmo1: TMemo;
btnCloneForm: TButton;
procedure btnCloneClassClick(Sender: TObject);
procedure btnCloneFormClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TSetting }
procedure TSetting.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('intArr', ReadIntArr, WriteIntArr, true);
end;
procedure TSetting.ReadIntArr(Reader: TReader);
var
lvIdx: integer;
begin
fIntArr := nil;
Reader.ReadListBegin;
SetLength(fIntArr,Reader.ReadInteger);
lvIdx:=low(fIntArr);
while not Reader.EndOfList do
begin
fIntArr[lvIdx] := Reader.ReadInteger;
inc(lvIdx);
end;
Reader.ReadListEnd;
end;
procedure TSetting.WriteIntArr(Writer: TWriter);
var
i: integer;
begin
Writer.WriteListBegin;
Writer.WriteInteger(integer(Length(fIntArr)));
for i := Low(fIntArr) to High(fIntArr) do
begin
Writer.WriteInteger(fIntArr[i]);
end;
Writer.WriteListEnd;
end;
function ClassToStr(pvClass: TComponent): ansiString;
var
inStream, outStream: TMemoryStream;
begin
inStream := TMemoryStream.Create;
outStream := TMemoryStream.Create;
try
inStream.WriteComponentRes(pvClass.ClassName, pvClass);
// inStream.WriteComponent(pvClass);
inStream.Position := 0;
ObjectResourceToText(inStream, outStream);
// ObjectBinaryToText(inStream,outStream);
outStream.Position := 0;
SetLength(Result, outStream.Size + 1);
FillChar(Result[1], outStream.Size + 1, 0);
outStream.ReadBuffer(Result[1], outStream.Size);
finally
FreeAndNil(inStream);
FreeAndNil(outStream);
end;
end;
function StrToClass(pvStr: ansiString; pvCmpToSetProperties: TComponent=nil): TComponent;
var
inStream, outStream: TMemoryStream;
begin
inStream := TMemoryStream.Create;
outStream := TMemoryStream.Create;
try
if (pvStr <> '') then
inStream.WriteBuffer(pvStr[1], length(pvStr));
inStream.Position := 0;
ObjectTextToResource(inStream, outStream);
// ObjectTextToBinary(inStream,outStream);
outStream.Position := 0;
Result := outStream.ReadComponentRes(pvCmpToSetProperties);
finally
FreeAndNil(inStream);
FreeAndNil(outStream);
end;
end;
procedure TForm1.btnCloneClassClick(Sender: TObject);
var
lvObj, lv1: TSetting;
lvStr: String;
lvArr: TArrayOfInteger;
begin
lvObj := TSetting.Create(nil);
try
lvObj.intval := 12345;
SetLength(lvArr, 3);
lvArr[0] := 222;
lvArr[1] := 333;
lvArr[2] := 444;
lvObj.intArr := lvArr;
lvStr := ClassToStr(lvObj);
RegisterClass(TSetting);
lvObj.intval := 1;
lv1 := TSetting(StrToClass(lvStr, nil));
if (lv1.intval > lvObj.intval) then
mmo1.Text := lvStr;
finally
FreeAndNil(lvObj);
FreeAndNil(lv1);
end;
// WriteComponentResFile(ExtractFilePath(ParamStr(0))+ 'd.res',self);
end;
procedure TForm1.btnCloneFormClick(Sender: TObject);
var lvNewForm:TForm1;
lvRes:string;
begin
lvRes:=ClassToStr(self);
RegisterClass(TForm1);
lvNewForm:=TForm1.CreateNew(application);
StrToClass(lvRes,lvNewForm);
lvNewForm.Left:=self.Left+50;
lvNewForm.Top:=self.Top+50;
end;
end.

Delphi Open two files and re size whatever application they open in so they fit in the screen 50% 50%

If you have two random files e.g. .txt, .csv, .jpg, and you wanted to open two of them and make them take up the screen 50% 50%.
How would you find the window handle that was opened so that you can re size the right one?
I have edited below to be closer to the answer thanks to suggestions from David Heffernan and Rob Kennedy
The code below kind of works if everything goes right but i'm sure there are ways to improve the code.
Using ShellExecuteEx can return a process ID, you can get a window handle off the process ID by using EnumWindows checking against the process id. Then if everything works you can re size the form using MoveWindow
i have an example in the unit uFileStuff below
There are a few issues that i'm not sure can be resolved
Files can be opened in the same application e.g. notepad++.
ShellExecuteEx may not return a process id
EnumWindows may not find the window
Unit uFileStuff
unit uFileStuff;
interface
uses Winapi.Windows, System.SysUtils, Generics.Collections, shellapi, Winapi.Messages, Vcl.Dialogs, Vcl.Forms;
type
PWindowSearch = ^TWindowSearch;
TWindowSearch = record
TargetProcessID: DWord;
ResultList: TList<HWnd>;
end;
TMyFile = class
private
sFileNameAndPath : String;
MyProcessID : DWord;
MyParentProcessID : Dword;
Procedure OpenFile(sFile: String);
procedure UpdateWindowListByProcessID;
public
WindowsLinkedToProcessID : TList<HWnd>;
function GetWindowInformation(Wnd: HWnd) : String;
function GetAllWindowInformation : String;
property ProcessID : Dword read MyProcessID;
property ParentProcessID : Dword read MyParentProcessID;
constructor Create(sFile : String);
destructor Destroy; override;
end;
implementation
constructor TMyFile.Create(sFile: String);
begin
MyProcessID := 0;
MyParentProcessID := 0;
sFileNameAndPath := sFile;
WindowsLinkedToProcessID := TList<HWnd>.Create;
if (sFile <> '') and FileExists(sFile) then
OpenFile(sFileNameAndPath);
end;
destructor TMyFile.Destroy;
begin
WindowsLinkedToProcessID.Free;
Inherited;
end;
function TMyFile.GetAllWindowInformation: String;
var i : Integer;
sMessage : String;
begin
result := '';
for I := 0 to WindowsLinkedToProcessID.Count -1 do begin
sMessage := sMessage + #13#10 + GetWindowInformation(WindowsLinkedToProcessID[i]);
end;
result := result + sMessage;
end;
function TMyFile.GetWindowInformation(Wnd: HWnd): String;
var Buffer: array[0..255] of char;
begin
result := inttostr(Wnd);
SendMessage(Wnd, WM_GETTEXT, 255, LongInt(#Buffer[0]));
if Buffer <> '' then begin
result := result + ', ' + Buffer;
end;
end;
procedure TMyFile.OpenFile(sFile: String);
var i : Integer;
SEInfo: TShellExecuteInfo;
ExitCode: DWORD;
ExecuteFile, ParamString, StartInString, sMessage: string;
begin
ExecuteFile:=sFile;
FillChar(SEInfo, SizeOf(SEInfo), 0);
SEInfo.cbSize := SizeOf(TShellExecuteInfo);
with SEInfo do
begin
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := Application.Handle;
lpFile := PChar(sFile);
nShow := SW_SHOWNORMAL;
end;
if ShellExecuteEx(#SEInfo) then
begin
if SEInfo.hProcess > 0 then begin
Sleep(100);
WaitForInputIdle(SEInfo.hProcess, 10000 );
MyProcessID := GetProcessId( SEInfo.hProcess );
UpdateWindowListByProcessID;
end else begin
ShowMessage('No Process ' + SysErrorMessage(GetLastError) );
end;
end else
ShowMessage('Error starting "'+ sFile +'"' + #13#10 + SysErrorMessage(GetLastError));
end;
procedure TMyFile.UpdateWindowListByProcessID;
function SelectWindowByProcessID(Wnd: HWnd; Param: LParam): Bool; stdcall;
var
pSearchRec: PWindowSearch;
WindowPid: DWord;
begin
pSearchRec := PWindowSearch(Param);
Assert(Assigned(pSearchRec));
GetWindowThreadProcessID(Wnd, WindowPid);
if (WindowPid = pSearchRec.TargetProcessID) and IsWindowVisible(Wnd) then
pSearchRec.ResultList.Add(Wnd);
Result := True;
end;
var
SearchRec: TWindowSearch;
begin
if MyProcessID > 0 then begin
SearchRec.TargetProcessID := MyProcessID;
SearchRec.ResultList := WindowsLinkedToProcessID;
EnumWindows(#SelectWindowByProcessID, LParam(#SearchRec));
end;
end;
end.
Form Creating Files on create - has button to open them
unit fFileOpen;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TfrmFileOpen = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
sApplicationPath : String;
sFile1, sFile2 : String;
public
{ Public declarations }
end;
var
frmFileOpen: TfrmFileOpen;
implementation
uses uFileStuff;
{$R *.dfm}
procedure TfrmFileOpen.btn1Click(Sender: TObject);
var File1 : TMyFile;
File2 : TMyFile;
begin
File1 := TMyFile.Create( sFile1 );
try
if sFile2 <> sFile1 then
File2 := TMyFile.Create( sFile2 )
else
File2 := TMyFile.Create( '' );
try
if (File1.ProcessID > 0) and (File2.ProcessID > 0) then begin
if (File1.ParentProcessID > 0) and (File2.ParentProcessID > 0) and (File1.ParentProcessID = File2.ParentProcessID) then begin
showmessage('Both Files opened in same process');
end else if (File1.WindowsLinkedToProcessID.Count > 0) and (File2.WindowsLinkedToProcessID.Count > 0) then begin
if File1.WindowsLinkedToProcessID.Count > 1 then
ShowMessage('Warning returned more than 1 window Moving the first window' + #13#10 + File1.GetAllWindowInformation);
MoveWindow(File1.WindowsLinkedToProcessID[0], 0, 0, Trunc(Screen.WorkAreaWidth / 2), Screen.WorkAreaHeight, True);
if File2.WindowsLinkedToProcessID.Count > 1 then
ShowMessage('Warning returned more than 1 window Moving the first window' + #13#10 + File2.GetAllWindowInformation);
MoveWindow(File2.WindowsLinkedToProcessID[0], Round(Screen.WorkAreaWidth / 2)+1, 0, Trunc(Screen.WorkAreaWidth / 2), Screen.WorkAreaHeight, True);
end;
end;
finally
File2.Free;
end;
finally
File1.Free;
end;
end;
procedure TfrmFileOpen.FormCreate(Sender: TObject);
var slTemp : TStringList;
img : TBitmap;
begin
ReportMemoryLeaksOnShutdown := true;
sApplicationPath := ExtractFileDir(application.ExeName);
sFile1 := IncludeTrailingPathDelimiter( sApplicationPath ) + 'File1.txt';
sFile2 := IncludeTrailingPathDelimiter( sApplicationPath ) + 'File2.csv';
{
if not FileExists( sFile1 ) then begin
img := TBitmap.Create;
img.SetSize(300,300);
img.SaveToFile( sFile1 );
img.Free;
end; }
if not FileExists(sFile1) then begin
slTemp := TStringList.Create;
slTemp.Add('File1');
slTemp.SaveToFile(sFile1);
slTemp.Free;
end;
if not FileExists(sFile2) then begin
slTemp := TStringList.Create;
slTemp.Add('File2');
slTemp.SaveToFile(sFile2);
slTemp.Free;
end;
end;
end.

Acquiring a Kerberos ticket with a Delphi application

I am at a loss here. For some weeks now I tried to get a Kerberos ticket with my Delphi application.
I consulted:
How can I get a Kerberos ticket with Delphi? (I honestly don't see why the bounty was rewarded, btw)
Every page around https://msdn.microsoft.com/en-us/library/aa374713%28v=vs.85%29.aspx
Most importantly: https://msdn.microsoft.com/en-us/library/ee498143.aspx
The last page links to example code that I converted to Delphi - I think. On a form I dropped a TWSocket out of Overbytes Socket components and named it mySocket. Rest is done in code. Problem is, that I seem to get the Kerberos TGT, but can't get the Ticket itself through the UDP connection. The Server just wont answer. I also feel there is something fundamentally wrong here, with the break of media during the communication with the server. How come I can use API to get the TGT but have to switch to UDP to get the ticket itself?
Maybe a good start to discuss this is to ignore the code first and tell me if the way I'm going is right or not. Here are my steps:
Call InitSecurityInterface to get the SecurityFunctionTable
Call QuerySecurityPackageInfo for the Kerberos Package to obtain max message size
Call AcquireCredentialsHandle for Kerberos package
Call InitializeSecurityContext with above received CredentialsHandle and the KerberosServer. Receiving some message, that may contain a KerbTicket a Kerb TGT or anything inbetween
Depending on the result of InitializeSecurityContext either use received KerbTicket or open UDP connection to KerbServer to send received buffer from step 4
Use answer message as parameter for new call of InitilizeSecurityContext
Repeat from Step 4 until result is SEC_E_OK
Have I understood correctly? If so, please consider reading my implementation to find my mistake. If not, please explain how it's really done.
Here is the code:
unit ukerber;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
IdSSPI, IdAuthenticationSSPI, Vcl.StdCtrls, OverbyteIcsWndControl,
OverbyteIcsWSocket;
const
krbServer: PAnsiChar = 'krbtgt/mydomain.int';
type
TForm2 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Button2: TButton;
mySocket: TWSocket;
procedure Button1Click(Sender: TObject);
procedure mySocketDataAvailable(Sender: TObject; ErrCode: Word);
function InitPackage(var maxMessage: Cardinal): Boolean;
function SendUDPMessage(outBuf: Pointer; outsize: Cardinal): Boolean;
function GenClientContext(pIn: Pointer; InputSize: Cardinal; pOut: Pointer; Outputsize: PCardinal; var Done: Boolean): Boolean;
procedure Cleanup;
private
{ Private-Deklarationen }
secfunc: SecurityFunctionTableA;
maxMessageSize: Cardinal;
hCredential: SecHandle;
hContext: CtxtHandle;
pOutBuf, pInBuf: PByteArray;
MessageReceived: Boolean;
public
{ Public-Deklarationen }
end;
procedure Pause(Zeit: Longint);
var
Form2: TForm2;
implementation
{$R *.dfm}
// the main method from the C-example
// this starts the ticket acquisition
procedure TForm2.Button1Click(Sender: TObject);
var
sec_State: SECURITY_STATUS;
pszTargetName: PSEC_CHAR;
hNewContext: CtxtHandle;
Output, Input: SecBufferDesc;
outSecBuf, inSecBuf: SecBuffer;
fContextAttr: ULONG;
cbOut, cbIn: Cardinal;
Done: Boolean;
timeOut: Integer;
begin
Done := False;
if InitPackage(maxMessageSize) then
begin
try
pOutBuf := nil;
pInBuf := nil;
GetMem(pOutBuf, maxMessageSize);
GetMem(pInBuf, maxMessageSize);
SecInvalidateHandle(hCredential);
SecInvalidateHandle(hContext);
if not GenClientContext(nil, 0, pOutBuf, #cbOut, Done) then
begin
Cleanup;
Exit;
end;
// ------------
// up to here everything seem to work just fine
// ------------
if not SendUDPMessage(pOutBuf, cbout) then
begin
Cleanup;
Exit;
end;
timeOut := 0;
while not Done and (timeOut <= 100) do
begin
repeat
Pause(1000);
Inc(timeOut);
until MessageReceived or (timeOut >= 100);
if MessageReceived then
begin
cbOut := maxMessageSize;
if not GenClientContext(pInBuf, cbIn, pOutBuf, #cbout, Done) then
begin
Cleanup;
Exit;
end;
if not SendUDPMessage(pOutBuf, cbout) then
begin
Cleanup;
Exit;
end;
end;
end;
if Done then // <<<----------Sadly... never done
begin
// Kerberos-ticket ---->>>> pInBuf
end
else // this happens every time
ShowMessage('Authentification failed due to server timeout');
finally
Cleanup;
end;
end;
end;
procedure TForm2.Cleanup;
begin
secfunc.DeleteSecurityContext(#hcontext);
secfunc.FreeCredentialsHandle(#hCredential);
FreeMem(pInBuf);
FreeMem(pOutBuf);
end;
function TForm2.GenClientContext(pIn: Pointer; InputSize: Cardinal;
pOut: Pointer; Outputsize: PCardinal; var Done: Boolean): Boolean;
var
sec_State: SECURITY_STATUS;
LifeTime: TimeStamp;
OutBuffDesc: SecBufferDesc;
OutSecBuff: SecBuffer;
InBuffDesc: SecBufferDesc;
InSecBuff: SecBuffer;
ContextAttributes: ULONG;
NewContext: Boolean;
KerberosServer: PAnsiChar;
function SetSecHandle: PSecHandle;
begin
if NewContext then
Result := nil
else
Result := #hContext;
end;
function SetInBuffer: PSecBufferDesc;
begin
if NewContext then
Result := nil
else
Result := #InBuffDesc;
end;
begin
if not Assigned(pIn) then
begin
NewContext := True;
// No user athentication needed, so we'll skip that part of the example
sec_State := secfunc.AcquireCredentialsHandleA(
nil,
PAnsiChar('Kerberos'),
SECPKG_CRED_OUTBOUND,
nil,
nil,
nil,
nil,
#hCredential,
#LifeTime
);
if not SEC_SUCCESS(sec_State) then
begin
ShowMessage('AqcuireCredentials failed, Error#: ' + IntToStr(sec_State));
Result := False;
Exit;
end;
end;
// Prepare buffers
// Output
OutBuffDesc.ulVersion := SECBUFFER_VERSION;
OutBuffDesc.cBuffers := 1;
OutBuffDesc.pBuffers := #OutSecBuff;
OutSecBuff.cbBuffer := Outputsize^;
OutSecBuff.BufferType := SECBUFFER_TOKEN;
OutSecBuff.pvBuffer := pOut;
//Input
InBuffDesc.ulVersion := SECBUFFER_VERSION;
InBuffDesc.cBuffers := 1;
InBuffDesc.pBuffers := #InSecBuff;
InSecBuff.cbBuffer := InputSize;
InSecBuff.BufferType := SECBUFFER_TOKEN;
InSecBuff.pvBuffer := pIn;
// KerberosServer := krbServer; // Tried both krbtgt and following...no change
KerberosServer := PAnsiChar('RestrictedKrbHost/FM-DC01.mydomain.int');
sec_State := secfunc.InitializeSecurityContextA(
#hCredential,
SetSecHandle,
KerberosServer,
ISC_REQ_DELEGATE + ISC_REQ_MUTUAL_AUTH,
0,
SECURITY_NATIVE_DREP,
SetInBuffer,
0,
#hContext,
#OutBuffDesc,
#contextAttributes,
#Lifetime
);
if not SEC_SUCCESS(sec_State) then
begin
ShowMessage('init context failed, Error #: ' + IntToStr(sec_State));
Result := False;
Exit;
end;
if (sec_State = SEC_I_COMPLETE_NEEDED) or
(sec_State = SEC_I_COMPLETE_AND_CONTINUE) then
begin
sec_State := secfunc.CompleteAuthToken(#hContext, #OutBuffDesc);
if not SEC_SUCCESS(sec_State) then
begin
ShowMessage('complete failed, Error #: ' + IntToStr(sec_State));
Result := False;
Exit;
end;
end;
Outputsize^ := OutSecBuff.cbBuffer;
// First call of this method results in sec_state = SEC_I_CONTINUE_NEEDED
// which should be OK, but then I have to switch to UDP communication
// and that seems to be buggy.
Done := not ((sec_State = SEC_I_CONTINUE_NEEDED) or (sec_State = SEC_I_COMPLETE_AND_CONTINUE));
Result := True;
end;
function TForm2.InitPackage(var maxMessage: Cardinal): Boolean;
var
sec_State: SECURITY_STATUS;
pPkgInfo: PSecPkgInfoA;
InitSecurityInterfaceA: function: PSecurityFunctionTableA; stdcall;
begin
Result := False;
MessageReceived := False;
try
InitSecurityInterfaceA := GetProcAddress(GetModuleHandle('secur32.dll'), 'InitSecurityInterfaceA');
if Assigned(InitSecurityInterfaceA) then
secfunc := InitSecurityInterfaceA^;
sec_State := secfunc.QuerySecurityPackageInfoA(
PAnsiChar('Kerberos'),
#pPkgInfo
);
if sec_state = SEC_E_OK then
begin
maxMessage := pPkgInfo^.cbMaxToken;
Result := True;
end;
finally
secfunc.FreeContextBuffer(pPkgInfo);
end;
end;
procedure TForm2.mySocketDataAvailable(Sender: TObject; ErrCode: Word);
var
inBuf: array of Byte;
BufLen: Integer;
Length: Integer;
sentSize: Cardinal;
begin
MessageReceived := False;
// Data should pour in here. Hopefully the Kerberos-ticket
// First DWORD is message size, rest is the message itself
Length := mySocket.Receive(#sentsize, SizeOf(DWORD));
if Length <= 0 then
begin
Exit;
end;
// The rest
SetLength(inBuf, SizeOf(sentSize));
Length := mySocket.Receive(#inBuf, SizeOf(inBuf));
if Length >= 0 then
begin
pInBuf := #inBuf;
MessageReceived := True;
end;
end;
function TForm2.SendUDPMessage(outBuf: Pointer; outsize: Cardinal): Boolean;
begin
mySocket.Proto := 'udp';
mySocket.Addr := 'FM-DC01.mydomain.int';
mySocket.Port := '88';
mySocket.Connect;
// send size of message first, then message itself
Result := (mySocket.Send(PByte(#outsize), SizeOf(outsize)) > -1);
if Result then
if mySocket.State = wsConnected then
Result := (mySocket.Send(outBuf, outsize) > -1);
end;
// small method to wait for action, should not be part of the problem
procedure Pause(Zeit: Longint);
var
Tick: DWORD;
Event: THandle;
begin
Event := CreateEvent(nil, False, False, nil);
try
Tick := GetTickCount + DWORD(Zeit);
while (Zeit > 0) and
(MsgWaitForMultipleObjects(1, Event, False, Zeit, QS_ALLINPUT) <> WAIT_TIMEOUT) do
begin
Application.ProcessMessages;
Zeit := Tick - GetTickCount;
end;
finally
CloseHandle(Event);
end;
end;
end.

Resources