I have the following code, where I can draw several rectangles and make a hole to each.
How can I send the RectList object over a socket (TServerSocket) and recover (receive in a TClientSocket) this object directly to a variable of same type (var RectList: TList<TRect>)?
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure FormPaint(Sender: TObject);
private
{ Private declarations }
Drawing: Boolean;
RectList: TList<TRect>;
Rectangle: TRect;
FormRegion, HoleRegion: HRGN;
function ClientToWindow(const P: TPoint): TPoint;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.ClientToWindow(const P: TPoint): TPoint;
begin
Result := ClientToScreen(P);
Dec(Result.X, Left);
Dec(Result.Y, Top);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
RectList := TList<TRect>.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
RectList.Free;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
Rectangle.Left := X;
Rectangle.Top := Y;
Drawing := True;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
begin
Rectangle.Right := X;
Rectangle.Bottom := Y;
Invalidate;
end;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
I: Integer;
begin
Drawing := false;
Rectangle.Right := X;
Rectangle.Bottom := Y;
Invalidate;
if RectList.Count < StrToInt(ComboBox1.Text) then
begin
Rectangle.NormalizeRect;
if not Rectangle.IsEmpty then
RectList.Add(Rectangle)
else
SetWindowRgn(Handle, 0, True);
end
else
begin
FormRegion := CreateRectRgn(0, 0, Width, Height);
for I := 0 to Pred(RectList.Count) do
begin
HoleRegion := CreateRectRgn(ClientToWindow(RectList.Items[I].TopLeft).X, ClientToWindow(RectList.Items[I].TopLeft).Y, ClientToWindow(RectList.Items[I].BottomRight).X, ClientToWindow(RectList.Items[I].BottomRight).Y);
CombineRgn(FormRegion, FormRegion, HoleRegion, RGN_DIFF);
end;
SetWindowRgn(Handle, FormRegion, True);
RectList.Clear;
end;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
R: TRect;
begin
Canvas.Brush.Style := bsClear;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Color := clRed;
Canvas.Rectangle(Rectangle);
for R in RectList do
Canvas.Rectangle(R);
end;
end.
I made some code to show you how to do it.
In your code, I added a TClientSocketon the form and assigned a few events. Also added a TButton to send the RectList to the other side (server side) thru the TClientSocket.
I designed a new simple server application having a TServerSocket set to listen for client connection and accepting commands from the client. I implemented two commands: rectangle and clear. Obviously clear command is implemented to clear the display on the rectangle list. The rectangle command is used to sent a rectangle (Left, top, right and bottom as coma delimited integers).
Since client and server must understand each other, I designed a very simple communication protocol. Data is exchanged between client and server using ASCII lines. A line is any character collection terminated by a CRLF pair. TCP port 2500 (Almost any other would do) is used.
For example, the command
rectangle 10,20,30,40
will sent a rectangle from client to server (The line above is terminated by CRLF).
If the server receive a valid command, it act on it and then send
OK
The line above is terminated by CRLF. In case of an error, an error message is sent back to the client.
When a client establish a connection, the first thing the server does is to send a welcome banner. That is a line terminated by CRLF.
The client wait to receive the banner before sending any command. Then it send the clear command, wait for OK, then send a rectangle command with first item in RectList and wait for OK, then loop sending all rectangle commands and waiting for OK acknowledge until all RectList has been sent. The the client close the connection.
I'm not completely correct when I say wait for. Actually the socket is event driven. That means everything is done thry events. For example, when a line comes in - sent by the other side - the socket triggers an OnRead event. In the corresponding event handler, you receive the line that is already received.
I used this line oriented protocol because it is really simple, easy to debug and cross platform. Actually, if looks much like the SMTP protocol which is used to send an email! Sending binary data is surely faster but has a lot of difficulties. Binary data format is compiler and platform specific. This result in difficulties. Binary data is diffcult to read for a human and so it is difficult to debug.
Below you'll find your enhanced source code and DFM (This is the client), then the server source code and DFM.
Client source code:
unit SktSocketClientDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
System.Generics.Collections,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
System.Win.ScktComp;
type
TSktSocketClientMainForm = class(TForm)
ComboBox1 : TComboBox;
SocketSendButton : TButton;
ClientSocket1 : TClientSocket;
Memo1 : TMemo;
procedure ClientSocket1Connect(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ClientSocket1Connecting(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ClientSocket1Read(
Sender : TObject;
Socket : TCustomWinSocket);
procedure FormCreate(Sender : TObject);
procedure FormDestroy(Sender : TObject);
procedure FormMouseDown(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
procedure FormMouseMove(
Sender : TObject;
Shift : TShiftState;
X, Y : Integer);
procedure FormMouseUp(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
procedure FormPaint(Sender : TObject);
procedure SocketSendButtonClick(Sender : TObject);
private
Drawing : Boolean;
RectList : TList<TRect>;
Rectangle : TRect;
FormRegion, HoleRegion : HRGN;
FBanner : string;
FSendIndex : Integer;
function ClientToWindow(const P : TPoint) : TPoint;
end;
var
SktSocketClientMainForm : TSktSocketClientMainForm;
implementation
{$R *.dfm}
function TSktSocketClientMainForm.ClientToWindow(const P : TPoint) : TPoint;
begin
Result := ClientToScreen(P);
Dec(Result.X, Left);
Dec(Result.Y, Top);
end;
procedure TSktSocketClientMainForm.FormCreate(Sender : TObject);
begin
RectList := TList<TRect>.Create;
end;
procedure TSktSocketClientMainForm.FormDestroy(Sender : TObject);
begin
RectList.Free;
end;
procedure TSktSocketClientMainForm.FormMouseDown(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
begin
Rectangle.Left := X;
Rectangle.Top := Y;
Drawing := True;
end;
procedure TSktSocketClientMainForm.FormMouseMove(
Sender : TObject;
Shift : TShiftState;
X, Y : Integer);
begin
if Drawing then begin
Rectangle.Right := X;
Rectangle.Bottom := Y;
Invalidate;
end;
end;
procedure TSktSocketClientMainForm.FormMouseUp(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
var
I : Integer;
begin
Drawing := false;
Rectangle.Right := X;
Rectangle.Bottom := Y;
Invalidate;
if RectList.Count < StrToInt(ComboBox1.Text) then begin
Rectangle.NormalizeRect;
if not Rectangle.IsEmpty then
RectList.Add(Rectangle)
else
SetWindowRgn(Handle, 0, True);
end
else begin
FormRegion := CreateRectRgn(0, 0, Width, Height);
for I := 0 to Pred(RectList.Count) do
begin
HoleRegion :=
CreateRectRgn(ClientToWindow(RectList.Items[I].TopLeft).X,
ClientToWindow(RectList.Items[I].TopLeft).Y,
ClientToWindow(RectList.Items[I].BottomRight).X,
ClientToWindow(RectList.Items[I].BottomRight).Y);
CombineRgn(FormRegion, FormRegion, HoleRegion, RGN_DIFF);
end;
SetWindowRgn(Handle, FormRegion, True);
RectList.Clear;
end;
end;
procedure TSktSocketClientMainForm.FormPaint(Sender : TObject);
var
R : TRect;
begin
Canvas.Brush.Style := bsClear;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Color := clRed;
Canvas.Rectangle(Rectangle);
for R in RectList do
Canvas.Rectangle(R);
end;
procedure TSktSocketClientMainForm.SocketSendButtonClick(Sender : TObject);
begin
FBanner := '';
FSendIndex := 0;
ClientSocket1.Port := 2500; // Must be the same as server side
ClientSocket1.Address := '127.0.0.1';
ClientSocket1.Active := True;
end;
procedure TSktSocketClientMainForm.ClientSocket1Connect(
Sender : TObject;
Socket :
TCustomWinSocket);
begin
Memo1.Lines.Add('Connected');
end;
procedure TSktSocketClientMainForm.ClientSocket1Connecting(
Sender : TObject;
Socket : TCustomWinSocket);
begin
Memo1.Lines.Add('Connecting...');
end;
procedure TSktSocketClientMainForm.ClientSocket1Read(
Sender : TObject;
Socket : TCustomWinSocket);
var
Line : string;
CmdLine : string;
R : TRect;
begin
Line := Trim(string(Socket.ReceiveText));
Memo1.Lines.Add('Rcvd: "' + Line + '"');
if FBanner = '' then begin
FBanner := Line;
Socket.SendText('Clear' + #13#10);
Exit;
end;
if Line <> 'OK' then begin
Memo1.Lines.Add('Expected "OK", received "' + Line + '"');
Socket.Close;
Exit;
end;
if FSendIndex >= RectList.Count then begin
// We have sent everything in RectList
Memo1.Lines.Add('Send completed OK');
Socket.Close;
Exit;
end;
// Send next item in RectList
R := RectList[FSendIndex];
CmdLine := Format('Rectangle %d,%d,%d,%d' + #13#10,
[R.Left, R.Top, R.Right, R.Bottom]);
Inc(FSendIndex);
Socket.SendText(AnsiString(CmdLine));
end;
end.
Client DFM:
object SktSocketClientMainForm: TSktSocketClientMainForm
Left = 0
Top = 0
Caption = 'SktSocketClientMainForm'
ClientHeight = 299
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
OnMouseDown = FormMouseDown
OnMouseMove = FormMouseMove
OnMouseUp = FormMouseUp
OnPaint = FormPaint
DesignSize = (
635
299)
PixelsPerInch = 96
TextHeight = 13
object ComboBox1: TComboBox
Left = 24
Top = 12
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 4
TabOrder = 0
Text = '5'
Items.Strings = (
'1'
'2'
'3'
'4'
'5'
'6'
'7'
'8'
'9')
end
object SocketSendButton: TButton
Left = 188
Top = 8
Width = 75
Height = 25
Caption = 'Send'
TabOrder = 1
OnClick = SocketSendButtonClick
end
object Memo1: TMemo
Left = 8
Top = 192
Width = 621
Height = 101
Anchors = [akLeft, akTop, akRight, akBottom]
Lines.Strings = (
'Memo1')
TabOrder = 2
end
object ClientSocket1: TClientSocket
Active = False
ClientType = ctNonBlocking
Port = 0
OnConnecting = ClientSocket1Connecting
OnConnect = ClientSocket1Connect
OnRead = ClientSocket1Read
Left = 44
Top = 148
end
end
Server source code:
unit SktSocketServerDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
System.Generics.Collections,
Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Win.ScktComp,
Vcl.ExtCtrls;
type
TCmdProc = procedure (Socket : TCustomWinSocket;
const Params : String) of object;
TCmdItem = record
Cmd : String;
Proc : TCmdProc;
constructor Create(const ACmd : String; AProc : TCmdProc);
end;
TServerMainForm = class(TForm)
ServerSocket1 : TServerSocket;
Memo1 : TMemo;
ServerStartButton : TButton;
PaintBox1 : TPaintBox;
ServerStopButton : TButton;
procedure PaintBox1Paint(Sender : TObject);
procedure ServerSocket1ClientConnect(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ServerSocket1ClientDisconnect(
Sender : TObject;
Socket :
TCustomWinSocket);
procedure ServerSocket1ClientRead(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ServerSocket1Listen(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ServerStartButtonClick(Sender : TObject);
procedure ServerStopButtonClick(Sender : TObject);
private
RectList : TList<TRect>;
CmdList : TList<TCmdItem>;
procedure ProcessCmd(
Socket : TCustomWinSocket;
const CmdLine : string);
procedure CmdNoop(
Socket : TCustomWinSocket;
const Params : string);
procedure CmdClear(
Socket : TCustomWinSocket;
const Params : string);
procedure CmdRectangle(
Socket : TCustomWinSocket;
const Params : string);
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
end;
var
ServerMainForm: TServerMainForm;
implementation
{$R *.dfm}
function SkipOverWhiteSpaces(const CmdLine : String; Index : Integer) : Integer;
var
I : Integer;
begin
I := Index;
while (I <= Length(CmdLine)) and
CharInSet(CmdLine[I], [' ', #13, #10, #9]) do
Inc(I);
Result := I;
end;
function SkipToNextWhiteSpace(const CmdLine : String; Index : Integer) : Integer;
var
I : Integer;
begin
I := Index;
while (I <= Length(CmdLine)) and
(not CharInSet(CmdLine[I], [' ', #13, #10, #9])) do
Inc(I);
Result := I;
end;
function SkipToNextDelimiter(
const CmdLine : String;
Index : Integer;
Delimiters : array of const) : Integer;
var
I : Integer;
nArg : Integer;
V : TVarRec;
begin
I := Index;
while I <= Length(CmdLine) do begin
nArg := 0;
while nArg <= High(Delimiters) do begin
V := Delimiters[nArg];
case (V.VType and varTypeMask) of
vtWideChar:
begin
if CmdLine[I] = V.VWideChar then begin
Result := I;
Exit;
end;
end;
end;
Inc(nArg);
end;
Inc(I);
end;
Result := I;
end;
function GetInteger(
const CmdLine : String;
Index : Integer;
out Value : Integer) : Integer;
var
I : Integer;
begin
Value := 0;
I := SkipOverWhiteSpaces(CmdLine, Index);
while (I <= Length(CmdLine)) and
CharInSet(CmdLine[I], ['0'..'9']) do begin
Value := Value * 10 + Ord(CmdLine[I]) - Ord('0');
Inc(I);
end;
Result := I;
end;
procedure TServerMainForm.CmdClear(Socket: TCustomWinSocket; const Params: String);
begin
RectList.Clear;
PaintBox1.Invalidate;
Socket.SendText('OK' + #13#10);
end;
procedure TServerMainForm.CmdNoop(Socket: TCustomWinSocket; const Params: String);
begin
Socket.SendText('OK' + #13#10);
end;
procedure TServerMainForm.CmdRectangle(Socket: TCustomWinSocket; const Params: String);
var
Param : array [0..3] of Integer;
I, J, K : Integer;
begin
// Clear all parameters
for K := Low(Param) to High(Param) do
Param[K] := 0;
// Parse all parameters
J := 1;
K := Low(Param);
while K <= High(Param) do begin
I := GetInteger(Params, J, Param[K]);
J := SkipOverWhiteSpaces(Params, I);
if J > Length(Params) then
break;
if K = High(Param) then // Check if we got all
break;
if Params[J] <> ',' then // Check for coma delimiter
break;
Inc(J); // Skip over coma
Inc(K);
end;
if K <> High(Param) then begin
Socket.SendText('Rectangle requires 4 parameters.'#13#10);
Exit;
end;
RectList.Add(TRect.Create(Param[0], Param[1], Param[2], Param[3]));
PaintBox1.Invalidate;
Socket.SendText('OK'#13#10);
end;
constructor TServerMainForm.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
RectList := TList<TRect>.Create;
RectList.Add(TRect.Create(10, 10, 50, 50));
RectList.Add(TRect.Create(20, 30, 80, 100));
CmdList := TList<TCmdItem>.Create;
CmdList.Add(TCmdItem.Create('', CmdNoop));
CmdList.Add(TCmdItem.Create('Clear', CmdClear));
CmdList.Add(TCmdItem.Create('Rectangle', CmdRectangle));
end;
destructor TServerMainForm.Destroy;
begin
FreeAndNil(CmdList);
FreeAndNil(RectList);
inherited Destroy;
end;
procedure TServerMainForm.PaintBox1Paint(Sender: TObject);
var
R: TRect;
ACanvas : TCanvas;
begin
ACanvas := (Sender as TPaintBox).Canvas;
ACanvas.Brush.Style := bsClear;
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Color := clRed;
for R in RectList do
ACanvas.Rectangle(R);
end;
procedure TServerMainForm.ServerSocket1ClientConnect(
Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo1.Lines.Add('Client connected');
Socket.SendText('Welcome to myServer' + #13#10);
end;
procedure TServerMainForm.ServerSocket1ClientRead(Sender: TObject; Socket:
TCustomWinSocket);
var
CmdLine : String;
begin
CmdLine := String(Socket.ReceiveText);
Memo1.Lines.Add('Rcvd: "' + CmdLine + '"');
ProcessCmd(Socket, CmdLine);
end;
procedure TServerMainForm.ProcessCmd(
Socket : TCustomWinSocket;
const CmdLine : String);
var
Cmd : String;
Params : String;
I, J : Integer;
begin
I := SkipOverWhiteSpaces(CmdLine, 1);
J := SkipToNextWhiteSpace(CmdLine, I);
// Split command and parameters
Cmd := UpperCase(Copy(CmdLine, I, J - I));
Params := Copy(CmdLine, J, MAXINT);
Memo1.Lines.Add(Format('Cmd="%s" Params="%s"', [Cmd, Params]));
for I := 0 to CmdList.Count - 1 do begin
if CmdList[I].Cmd = Cmd then begin
CmdList[I].Proc(Socket, Params);
Exit;
end;
end;
Socket.SendText('Unknown command' + #13#10);
end;
procedure TServerMainForm.ServerSocket1ClientDisconnect(Sender: TObject; Socket:
TCustomWinSocket);
begin
Memo1.Lines.Add('Client disconnected');
end;
procedure TServerMainForm.ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket);
begin
Memo1.Lines.Add('Waiting for client connection');
end;
procedure TServerMainForm.ServerStartButtonClick(Sender: TObject);
begin
ServerSocket1.Port := 2500; // Almost any (free) port is OK
ServerSocket1.Open; // Start listening for clients
end;
procedure TServerMainForm.ServerStopButtonClick(Sender: TObject);
begin
ServerSocket1.Close;
Memo1.Lines.Add('Server stopped');
end;
{ TCmdItem }
constructor TCmdItem.Create(const ACmd: String; AProc: TCmdProc);
begin
Cmd := UpperCase(ACmd);
Proc := AProc;
end;
end.
Server DFM:
object ServerMainForm: TServerMainForm
Left = 0
Top = 0
Caption = 'ServerMainForm'
ClientHeight = 498
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
DesignSize = (
635
498)
PixelsPerInch = 96
TextHeight = 13
object PaintBox1: TPaintBox
Left = 8
Top = 48
Width = 617
Height = 273
Anchors = [akLeft, akTop, akRight, akBottom]
OnPaint = PaintBox1Paint
end
object Memo1: TMemo
Left = 8
Top = 329
Width = 617
Height = 161
Anchors = [akLeft, akTop, akRight, akBottom]
Lines.Strings = (
'Memo1')
TabOrder = 0
end
object ServerStartButton: TButton
Left = 12
Top = 8
Width = 75
Height = 25
Caption = 'Server Start'
TabOrder = 1
OnClick = ServerStartButtonClick
end
object ServerStopButton: TButton
Left = 93
Top = 8
Width = 75
Height = 25
Caption = 'Server Stop'
TabOrder = 2
OnClick = ServerStopButtonClick
end
object ServerSocket1: TServerSocket
Active = False
Port = 0
ServerType = stNonBlocking
OnListen = ServerSocket1Listen
OnClientConnect = ServerSocket1ClientConnect
OnClientDisconnect = ServerSocket1ClientDisconnect
OnClientRead = ServerSocket1ClientRead
Left = 64
Top = 196
end
end
Related
Is there any reasonably simple and robust way to smoothly animate the programmatic resize of a Delphi VCL form on Windows?
For instance, when the user clicks the "Show details" button the form's height is increased with a details panel shown in the new client area.
Resizing the form by setting its Height (or ClientHeight) property will resize it immediately. I want the form to grow smoothly in height from its original value to the new value over a half-second duration.
How to smoothly animate the resize of a Delphi VCL form?
Yes, this is actually pretty easy.
Probably the simplest way is to base the solution on a TTimer which fires some 30 times per second or so, each time updating the form's size.
We just have to settle for a mapping T from time to size (width or height), so that T(0) is the original size, T(1) is the final, target size, and T(t) is the intermediate size at time t, normalized to [0, 1].
Here the simplest approach would be to let the size grow or shrink linearly with time. However, this looks bad. Instead, we should use some sigmoid function to make the speed slow at the beginning and the end and maximal at t = 0.5. My favourite sigmoid function is the inverse tangent function, but we could equally well use the hyperbolic tangent function or the error function.
Now, if FFrames[i] is the size of the ith frame, then
var F := 1 / ArcTan(Gamma);
for var i := 0 to High(FFrames) do
begin
var t := i / High(FFrames); // [0, 1]
t := 2*t - 1; // [-1, 1]
t := F*ArcTan(Gamma*t); // sigmoid transformation
t := (t + 1) / 2; // [0, 1]
FFrames[i] := Round((1 - t) * AFrom + t * ATo);
end;
computes the trajectory according to this scheme. Notice that FFrames[i] is a convex combination of the initial and final sizes.
The following component uses this code to implement animated resizing:
unit WindowAnimator;
interface
uses
SysUtils, Windows, Types, Classes, Vcl.Forms, Vcl.ExtCtrls;
type
TWindowAnimator = class(TComponent)
strict private
type
TAxis = (axWidth, axHeight);
const
DEFAULT_GAMMA = 10;
DEFAULT_DURATION = 1000 {ms};
FrameCount = 256;
var
FTimer: TTimer;
FGamma: Integer;
FDuration: Integer {ms};
FFrames: array[0..FrameCount - 1] of Integer;
FAxis: TAxis;
FTarget: Integer;
FAnimStart,
FAnimEnd: TDateTime;
FForm: TCustomForm;
FBeforeProc, FAfterProc: TProc;
procedure TimerProc(Sender: TObject);
procedure Plot(AFrom, ATo: Integer);
procedure Stop;
procedure Animate(ABeforeProc: TProc = nil; AAfterProc: TProc = nil);
procedure DoBegin;
procedure DoFinish;
public
constructor Create(AOwner: TComponent); override;
procedure AnimateWidth(ANewWidth: Integer; ABeforeProc: TProc = nil; AAfterProc: TProc = nil);
procedure AnimateHeight(ANewHeight: Integer; ABeforeProc: TProc = nil; AAfterProc: TProc = nil);
published
property Gamma: Integer read FGamma write FGamma default DEFAULT_GAMMA;
property Duration {ms}: Integer read FDuration write FDuration default DEFAULT_DURATION;
end;
procedure Register;
implementation
uses
Math, DateUtils;
procedure Register;
begin
RegisterComponents('Rejbrand 2020', [TWindowAnimator]);
end;
{ TWindowAnimator }
procedure TWindowAnimator.Animate(ABeforeProc, AAfterProc: TProc);
begin
if FForm = nil then
Exit;
FBeforeProc := ABeforeProc;
FAfterProc := AAfterProc;
DoBegin;
FAnimStart := Now;
FAnimEnd := IncMilliSecond(FAnimStart, FDuration);
FTimer.Enabled := True;
end;
procedure TWindowAnimator.AnimateHeight(ANewHeight: Integer;
ABeforeProc, AAfterProc: TProc);
begin
if FForm = nil then
Exit;
Stop;
FAxis := axHeight;
Plot(FForm.Height, ANewHeight);
Animate(ABeforeProc, AAfterProc);
end;
procedure TWindowAnimator.AnimateWidth(ANewWidth: Integer;
ABeforeProc, AAfterProc: TProc);
begin
if FForm = nil then
Exit;
Stop;
FAxis := axWidth;
Plot(FForm.Width, ANewWidth);
Animate(ABeforeProc, AAfterProc);
end;
constructor TWindowAnimator.Create(AOwner: TComponent);
begin
inherited;
if AOwner is TCustomForm then
FForm := TCustomForm(AOwner);
FGamma := DEFAULT_GAMMA;
FDuration := DEFAULT_DURATION;
FTimer := TTimer.Create(Self);
FTimer.Interval := 30;
FTimer.OnTimer := TimerProc;
FTimer.Enabled := False;
end;
procedure TWindowAnimator.DoBegin;
begin
if Assigned(FBeforeProc) then
FBeforeProc();
end;
procedure TWindowAnimator.DoFinish;
begin
if Assigned(FAfterProc) then
FAfterProc();
end;
procedure TWindowAnimator.Plot(AFrom, ATo: Integer);
begin
FTarget := ATo;
var F := 1 / ArcTan(Gamma);
for var i := 0 to High(FFrames) do
begin
var t := i / High(FFrames); // [0, 1]
t := 2*t - 1; // [-1, 1]
t := F*ArcTan(Gamma*t); // sigmoid transformation
t := (t + 1) / 2; // [0, 1]
FFrames[i] := Round((1 - t) * AFrom + t * ATo);
end;
end;
procedure TWindowAnimator.Stop;
begin
FTimer.Enabled := False;
end;
procedure TWindowAnimator.TimerProc(Sender: TObject);
begin
var LNow := Now;
if (FForm = nil) or (FAnimEnd = 0.0) then
begin
FTimer.Enabled := False;
Exit;
end;
if LNow > FAnimEnd then // play it safe
begin
FTimer.Enabled := False;
case FAxis of
axWidth:
FForm.Width := FTarget;
axHeight:
FForm.Height := FTarget;
end;
DoFinish;
Exit;
end;
var t := MilliSecondsBetween(LNow, FAnimStart) / MilliSecondsBetween(FAnimStart, FAnimEnd);
var i := EnsureRange(Round(t * High(FFrames)), 0, High(FFrames));
case FAxis of
axWidth:
FForm.Width := FFrames[i];
axHeight:
FForm.Height := FFrames[i];
end;
end;
end.
To use this component, simply drop it on a form and use its public methods:
procedure AnimateWidth(ANewWidth: Integer; ABeforeProc: TProc = nil;
AAfterProc: TProc = nil);
procedure AnimateHeight(ANewHeight: Integer; ABeforeProc: TProc = nil;
AAfterProc: TProc = nil);
The optional TProc references let you run some code before and/or after the animation; typically, you want to populate any newly obtained client area after an increase in size and hide some content before a reduction in size.
Here's the component in action, showing and hiding a "Details" text:
Here's a more complicated example with a three-stage input procedure:
The total duration of the animation, as well as the sharpness of the sigmoid function, can be adjusted using the component's published properties.
procedure TForm1.SmoothResizeFormTo(const ToSize: integer);
var
CurrentHeight: integer;
Step: integer;
begin
while Height <> ToSize do
begin
CurrentHeight := Form1.Height;
// this is the trick which both accelerates initially then
// decelerates as the form reaches its target size
Step := (ToSize - CurrentHeight) div 3;
// this allows for both collapse and expand by using Absolute
// calculated value
if (Step = 0) and (Abs(ToSize - CurrentHeight) > 0) then
begin
Step := ToSize - CurrentHeight;
Sleep(50); // adjust for smoothness
end;
if Step <> 0 then
begin
Height := Height + Step;
sleep(50); // adjust for smoothness
end;
end;
end;
procedure TForm1.btnCollapseClick(Sender: TObject);
begin
SmoothResizeFormTo(100);
end;
procedure TForm1.btnExpandClick(Sender: TObject);
begin
SmoothResizeFormTo(800);
end;
Try this without any timers ;)
My Problem is that I Wanted to create new Forms and Buttons with procedure Button1Click(Sender: TObject);. I had the problem that i did not know how to open the new created Forms with procedure TForm2.myClick(Sender: TObject);. I wanted that different forms are created by different buttons.
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
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
procedure myClick(Sender: TObject);
end;
var
Form2: TForm2;
var
i:Integer = 1;
var
a:Integer = 1;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
with TButton.Create(self) do begin
Left := 80;
Top := 50 + i * 60;
Width := 300;
Height := 50;
Name := 'Liste' + IntToStr(i);
Self.Caption := ClassName+' '+ IntToStr(i);
OnClick := MyClick;
Parent :=Form2;
end;
i:= i + 1;
begin
with TForm.Create(Self) do begin
Left := 0;
Top := 0;
Width := 1000;
Height := 700;
Name := 'Erinnerung' + IntToStr(a);
Caption := 'Erinnerung' + IntToStr(a);
Parent := self;
OnClick:= MyClick;
Visible:=false;
end;
a:= a + 1;
end;
end;
procedure TForm2.myClick(Sender: TObject);
begin
end;
end.
There are a few number of unspecified items in your problem. Anyway, I made a simple example of what can be done. You'll get started and modify this code to suit your needs.
This code has a form (TForm1) with a single button. Each time the user click on the button, a new button and a new form are created. The created button is show on TForm1 in a column like you have done. The form is created but not visible like you have done. A list keep reference to the buttons and forms created.
The created button and form have their Tag property set to the index in the list. The button's OnClick event handler use this index to retrieve the corresponding form from the list and make it visible.
The form is created with a nil Parent so that it is a flying window. If you use Self instead of nil, the created form will be inside TForm1, much like an MDI application.
Here is the code:
unit DynFormDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
System.Generics.Collections;
type
TFormItem = record
AForm : TForm;
AButton : TButton;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
FList : TList<TFormItem>;
procedure MyClick(Sender : TObject);
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
Item : TFormItem;
AButton : TButton;
AForm : TForm;
begin
AButton := TButton.Create(Self);
AButton.Tag := FList.Count;
AButton.Left := 80;
AButton.Top := 50 + AButton.Tag * 60;
AButton.Width := 300;
AButton.Height := 50;
AButton.Name := 'Liste' + IntToStr(AButton.Tag);
AButton.Caption := ClassName + ' ' + IntToStr(AButton.Tag);
AButton.OnClick := MyClick;
AButton.Parent := Self;
AForm := TForm.Create(Self);
AForm.Tag := AButton.Tag;
AForm.Left := AButton.Tag * 20;
AForm.Top := AButton.Tag * 20;
AForm.Width := 200;
AForm.Height := 100;
AForm.Name := 'Erinnerung' + IntToStr(AButton.Tag);
AForm.Caption := 'Erinnerung' + IntToStr(AButton.Tag);
AForm.Parent := nil;
AForm.Visible := FALSE;
Item.AForm := AForm;
Item.AButton := AButton;
FList.Add(Item);
end;
constructor TForm1.Create(AOwner: TComponent);
begin
inherited;
FList := TList<TFormItem>.Create;
end;
destructor TForm1.Destroy;
begin
FreeAndNil(FList);
inherited;
end;
procedure TForm1.MyClick(Sender: TObject);
var
Item : TFormItem;
begin
Item := FList[(Sender as TButton).Tag];
if Assigned(Item.AForm) then
Item.AForm.Show;
end;
end.
Actually i have this code below that is working fine to send webcam images over socket (ClientSocket > ServerSocket), before send, i'm saving the image to file . Then i want know if exists some solution to insert the capture inside of MemoryStream without need save to file before.
Any suggestion will welcome.
Thanks in advance.
Camera.pas
unit Camera;
interface
uses
Windows, Messages, SysUtils, Graphics, Controls, Dialogs, ExtCtrls,
Jpeg;
type
TCamera = class(TObject)
private
Parent: TPanel;
VideoHwnd: HWND;
procedure Resize(Sender: TObject);
public
constructor Create(Owner: TPanel);
destructor Destroy; override;
function TakePicture(FileName: string): boolean;
procedure SetSize;
procedure SetSource;
end;
implementation
const
WM_CAP_START = WM_USER;
WM_CAP_STOP = WM_CAP_START + 68;
WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
WM_CAP_SAVEDIB = WM_CAP_START + 25;
WM_CAP_DLG_VIDEOFORMAT = WM_CAP_START + 41;
WM_CAP_DLG_VIDEOSOURCE = WM_CAP_START + 42;
WM_CAP_SET_PREVIEW = WM_CAP_START + 50;
WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52;
WM_CAP_SET_SCALE = WM_CAP_START + 53;
WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
WM_CAP_SEQUENCE = WM_CAP_START + 62;
WM_CAP_FILE_SET_CAPTURE_FILEA = WM_CAP_START + 20;
function capCreateCaptureWindowA(lpszWindowName: PCHAR; dwStyle: longint; x: integer; y: integer; nWidth: integer; nHeight: integer; ParentWin: HWND; nId: integer): HWND; stdcall; external 'AVICAP32.DLL';
constructor TCamera.Create(Owner: TPanel);
begin
try
VideoHwnd := capCreateCaptureWindowA('Camera', WS_CHILD or WS_VISIBLE, 0, 0, Owner.Width, Owner.Height, Owner.Handle, 0);
if (SendMessage(VideoHwnd, WM_CAP_DRIVER_CONNECT, 0, 0) <> 0) then
begin
SendMessage(VideoHwnd, WM_CAP_SET_PREVIEW, -1, 0);
SendMessage(VideoHwnd, WM_CAP_SET_PREVIEWRATE, 100, 0);
SendMessage(VideoHwnd, WM_CAP_SET_SCALE, -1, 0);
Parent := Owner;
Owner.OnResize := Resize;
end;
except
exit;
end;
end;
destructor TCamera.Destroy;
begin
if (VideoHwnd <> 0) then
begin
SendMessage(VideoHwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0);
SetParent(VideoHwnd, 0);
SendMessage(VideoHwnd, WM_CLOSE, 0, 0);
end;
inherited;
end;
procedure TCamera.Resize(Sender: TObject);
begin
inherited;
if (VideoHwnd <> 0) then
begin
SetWindowPos(VideoHwnd, HWND_BOTTOM, 0, 0, Parent.Width, Parent.Height, SWP_NOMOVE or SWP_NOACTIVATE);
end;
end;
procedure TCamera.SetSize;
begin
SendMessage(VideoHwnd, WM_CAP_DLG_VIDEOFORMAT, 0, 0);
end;
procedure TCamera.SetSource;
begin
SendMessage(VideoHwnd, WM_CAP_DLG_VIDEOSOURCE, 0, 0);
end;
function StringToPAnsiChar(stringVar: string): PAnsiChar;
var
AnsString: AnsiString;
begin
Result := '';
try
if stringVar <> '' then
begin
AnsString := AnsiString(stringVar);
Result := PAnsiChar(PAnsiString(AnsString));
end;
except
end;
end;
function TCamera.TakePicture(FileName: string): boolean;
var
p: TPicture;
j: TJpegImage;
Q, k: integer;
begin
if (SendMessage(VideoHwnd, WM_CAP_GRAB_FRAME, 0, 0) <> 0) and (SendMessage(VideoHwnd, WM_CAP_SAVEDIB, wparam(0), lparam(StringToPAnsiChar(FileName))) <> 0) then
begin
SendMessage(VideoHwnd, WM_CAP_SET_PREVIEW, -1, 0);
p := TPicture.Create;
p.Bitmap.LoadFromFile(FileName);
j := TJpegImage.Create;
j.Assign(p.Bitmap);
val('100', Q, k);
j.CompressionQuality := Q;
j.SaveToFile(FileName);
p.Free;
j.Free;
result := true;
end
else
result := false;
end;
end.
Form1.pas
uses
Camera;
// ...
procedure TForm1.ClientSocketCamConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
Camera := TCamera.Create(Form1.Panel1);
end;
procedure TForm1.ClientSocketCamDisconnected(Sender: TObject; Socket: TCustomWinSocket);
begin
Camera.Destroy;
end;
procedure TForm1.ClientSocketCamError(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
ErrorCode := 0;
end;
procedure TForm1.ClientSocketCamRead(Sender: TObject; Socket: TCustomWinSocket);
var
s, FileName: string;
Stream: TMemoryStream;
begin
s := Socket.ReceiveText;
FileName := ExtractFilePath(Application.ExeName) + 'webcam.jpg';
if s = 'camoff' then
begin
Camera.Destroy;
Socket.SendText('endcam');
end;
if s = 'cam' then
begin
try
Camera.TakePicture(FileName);
Sleep(200);
Stream := TMemoryStream.Create;
if FileExists(FileName)
then
begin
Stream.LoadFromFile(FileName);
Stream.Position := 0;
Socket.SendText(inttostr(Stream.Size) + #0);
Socket.SendStream(Stream);
end;
finally
Stream.Free;
Exit;
end;
end;
end;
In another question near this, i get the answer to get modal forms to keep inside a workarea inside the mainform.
The way i can accomplish that (thanks to David again) is catching WMSizing, WMMoving, WMGetMaxMinInfo, and for my porpuose WMShowwindow messages.
I am not closed to messages handling and i think it is likely the way i manage messages the cause that i am not getting the result i needed.
All the forms in my application are modal. But you can open a lot in the same execution thread. (Mainform, form1, form2, form3... formN).
All form(1..N) move inside a workarea in my mainform. Maximize, restore, size, move... all between limits of that workarea.
But i cannot manage how to minimize the whole application from then click on active modal form minimize button, and from click on taskbar button.
The application will be used in XP and W7... i am developing in DelphiXE.
The project can be downloaded from here (Project files - Mainform, panel, button, SecondaryForm, unit, nothing more), just to see that i try all the suggestions i found before asking here.
This is the source code of the original unit that keeps the modal forms inside the workarea.
unit uFormularios;
interface
uses Classes, SysUtils, Windows, Messages, Forms, DBGrids, StdCtrls, Menus, Graphics, ComCtrls, Math;
type
TForm_en_ventana = class(TForm)
private
inicializada: boolean;
bCentrada : boolean;
bMaximizada : boolean;
ancho_original: integer;
alto_original : integer;
procedure WMShowWindow(var Message: TWMShowWindow); message WM_SHOWWINDOW;
procedure WMSizing(var msg: TMessage); message WM_SIZING;
procedure WMMoving(Var msg: TMessage); message WM_MOVING;
procedure WMGetMinMaxInfo(Var msg: TWMGetMinMaxInfo); message WM_GETMINMAXINFO;
public
constructor Create(AOwner: TComponent); override;
property centrada: boolean read bCentrada write bCentrada;
property maximizada: boolean read bMaximizada write bMaximizada;
end;
procedure MaximizarFormulario(var F; MaximaAltura: integer = 0; MaximoAncho: integer = 0; Centrado: boolean = TRUE);
procedure InicializarVentanaTrabajo(const izq, der, arr, aba: integer);
var
ESPACIO_DE_TRABAJO, VENTANA_DE_TRABAJO: TRect;
implementation
constructor TForm_en_ventana.Create(AOwner: TComponent);
begin
inherited;
centrada := TRUE;
maximizada := false;
inicializada := false;
end;
Procedure TForm_en_ventana.WMGetMinMaxInfo(Var msg: TWMGetMinMaxInfo);
begin
inherited;
with msg.MinMaxInfo^.ptMaxPosition do
begin
x := VENTANA_DE_TRABAJO.Left;
y := VENTANA_DE_TRABAJO.Top;
end;
with msg.MinMaxInfo^.ptMaxSize do
begin
x := VENTANA_DE_TRABAJO.Right - VENTANA_DE_TRABAJO.Left;
y := VENTANA_DE_TRABAJO.Bottom - VENTANA_DE_TRABAJO.Top;
end;
with msg.MinMaxInfo^.ptMaxTrackSize do
begin
x := VENTANA_DE_TRABAJO.Right - VENTANA_DE_TRABAJO.Left;
y := VENTANA_DE_TRABAJO.Bottom - VENTANA_DE_TRABAJO.Top;
end;
with msg.MinMaxInfo^.ptMinTrackSize do
begin
x := ancho_original;
y := alto_original;
end;
end;
procedure TForm_en_ventana.WMSizing(var msg: TMessage);
var
R: PRect;
begin
R := PRect(msg.LParam);
R.Left := Max(R.Left, VENTANA_DE_TRABAJO.Left);
R.Right := Min(R.Right, VENTANA_DE_TRABAJO.Right);
R.Top := Max(R.Top, VENTANA_DE_TRABAJO.Top);
R.Bottom := Min(R.Bottom, VENTANA_DE_TRABAJO.Bottom);
Caption := 'Ancho: ' + inttostr(ancho_original) + ' - Alto: ' + inttostr(alto_original);
end;
procedure TForm_en_ventana.WMMoving(var msg: TMessage);
var
R : PRect;
dx, dy: integer;
begin
R := PRect(msg.LParam);
dx := 0;
dy := 0;
if R.Left < VENTANA_DE_TRABAJO.Left then
dx := VENTANA_DE_TRABAJO.Left - R.Left;
if R.Right > VENTANA_DE_TRABAJO.Right then
dx := VENTANA_DE_TRABAJO.Right - R.Right;
if R.Top < VENTANA_DE_TRABAJO.Top then
dy := VENTANA_DE_TRABAJO.Top - R.Top;
if R.Bottom > VENTANA_DE_TRABAJO.Bottom then
dy := VENTANA_DE_TRABAJO.Bottom - R.Bottom;
OffsetRect(R^, dx, dy);
end;
procedure TForm_en_ventana.WMShowWindow(var Message: TWMShowWindow);
begin
if inicializada then
Exit;
inicializada := TRUE;
ancho_original := Width;
alto_original := Height;
Constraints.MinHeight := Height;
Constraints.MinWidth := Width;
if centrada then
begin
Left := (((VENTANA_DE_TRABAJO.Right - VENTANA_DE_TRABAJO.Left) - Width) div 2) + VENTANA_DE_TRABAJO.Left;
Top := (((VENTANA_DE_TRABAJO.Bottom - VENTANA_DE_TRABAJO.Top) - Height) div 2) + VENTANA_DE_TRABAJO.Top;
end;
if maximizada then
SendMessage(Handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
end;
procedure InicializarVentanaTrabajo(const izq, der, arr, aba: integer);
begin
VENTANA_DE_TRABAJO.Left := izq;
VENTANA_DE_TRABAJO.Right := der;
VENTANA_DE_TRABAJO.Top := arr;
VENTANA_DE_TRABAJO.Bottom := aba;
end;
procedure MaximizarFormulario(var F; MaximaAltura: integer = 0; MaximoAncho: integer = 0; Centrado: boolean = TRUE);
begin
LockWindowUpdate(TForm(F).Handle);
TForm(F).Left := ESPACIO_DE_TRABAJO.Left;
if MaximoAncho = 0 then
TForm(F).Width := ESPACIO_DE_TRABAJO.Right
else
begin
if ESPACIO_DE_TRABAJO.Right < MaximoAncho then
TForm(F).Width := ESPACIO_DE_TRABAJO.Right
else
TForm(F).Width := MaximoAncho;
end;
TForm(F).Top := ESPACIO_DE_TRABAJO.Top;
if MaximaAltura = 0 then
TForm(F).Height := ESPACIO_DE_TRABAJO.Bottom
else
begin
if ESPACIO_DE_TRABAJO.Bottom < MaximaAltura then
TForm(F).Height := ESPACIO_DE_TRABAJO.Bottom
else
TForm(F).Height := MaximaAltura;
end;
if ((MaximoAncho <> 0) or (MaximaAltura <> 0)) and (Centrado) then
begin
TForm(F).Left := (ESPACIO_DE_TRABAJO.Right - TForm(F).Width) div 2;
TForm(F).Top := (ESPACIO_DE_TRABAJO.Bottom - TForm(F).Height) div 2;
end;
LockWindowUpdate(0);
end;
initialization
SystemParametersInfo(SPI_GETWORKAREA, 0, #ESPACIO_DE_TRABAJO, 0);
VENTANA_DE_TRABAJO := ESPACIO_DE_TRABAJO;
end.
Thanks to anybody who can help me!
I needed this too, and I tried the other answer but it's not working. Fortunately I managed to make it work, like this:
procedure TFoodsForm.WMSysCommand(var Msg: TWMSysCommand);
begin
if (fsModal in FormState) and (Msg.CmdType and $FFF0 = SC_MINIMIZE)
then Application.MainForm.WindowState:= wsMinimized
else inherited;
end;
Simply catch the Minimize and Restore messages in the Modal Form and do this ...
procedure TTheModalForm.WMSysCommand(var Msg: TWMSysCommand);
begin
if (fsModal in FormState) or not Application.MainForm.Visible then
begin
case Msg.CmdType of
SC_MINIMIZE:
begin
ShowWindow(Application.Handle, SW_SHOWMINNOACTIVE);
end;
SC_RESTORE:
begin
ShowWindow(Application.Handle, SW_SHOWNORMAL);
inherited;
end;
else
inherited;
end;
end
else
inherited;
end;
Thank you, JFGravel! This worked great for me, but I could never get the SC_RESTORE to get caught here, but restore works fine without, so here's my short version:
if (Message.CmdType and $FFF0) = SC_MINIMIZE then
ShowWindow(Application.Handle, SW_SHOWMINNOACTIVE)
else
inherited;
These days you see a lot of software displaying message windows in the right bottom corner of the active screen for a few seconds or until a close button is clicked (f.i. Norton does this after it has checked a download).
I would like to do this using Delphi 7 (and if possible Delphi 2010, since I am slowly migrating my code to the latest version).
I found some posts here on SO regarding forms not receiving focus, but that's only one part of the problem. I'm thinking also on how to determine the exact position of this message window (knowing that f.i. a user may have put his taskbar to the right of the screen.
Thx in advance.
UPDATE 26 Jan, 10: Starting from the code of drorhan I created the following form (in Delphi 7) which works whether the taskbar is displayed at the bottom, the right, the left or the top of the schreen.
fPopupMessage.dpr:
object frmPopupMessage: TfrmPopupMessage
Left = 537
Top = 233
AlphaBlend = True
AlphaBlendValue = 200
BorderStyle = bsToolWindow
Caption = 'frmPopupMessage'
ClientHeight = 48
ClientWidth = 342
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
OnCreate = FormCreate
DesignSize = (
342
48)
PixelsPerInch = 96
TextHeight = 13
object img: TImage
Left = 0
Top = 0
Width = 64
Height = 48
Align = alLeft
Center = True
Transparent = True
end
object lblMessage: TLabel
Left = 72
Top = 8
Width = 265
Height = 34
Alignment = taCenter
Anchors = [akLeft, akTop, akRight, akBottom]
AutoSize = False
Caption = '...'
Font.Charset = DEFAULT_CHARSET
Font.Color = clNavy
Font.Height = -11
Font.Name = 'Verdana'
Font.Style = [fsBold]
ParentFont = False
Transparent = True
WordWrap = True
end
object tmr: TTimer
Enabled = False
Interval = 3000
OnTimer = tmrTimer
Left = 16
Top = 16
end
end
and
fPopupMessage.pas
unit fPopupMessage;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, ImgList;
type
TfrmPopupMessage = class(TForm)
tmr: TTimer;
img: TImage;
lblMessage: TLabel;
procedure FormCreate(Sender: TObject);
procedure tmrTimer(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
bBeingDisplayed : boolean;
function GetPopupMessage: string;
procedure SetPopupMessage(const Value: string);
function GetPopupCaption: string;
procedure SetPopupCaption(const Value: string);
function TaskBarHeight: integer;
function TaskBarWidth: integer;
procedure ToHiddenPosition;
procedure ToVisiblePosition;
public
{ Public declarations }
procedure StartAnimationToHide;
procedure StartAnimationToShow;
property PopupCaption: string read GetPopupCaption write SetPopupCaption;
property PopupMessage: string read GetPopupMessage write SetPopupMessage;
end;
var
frmPopupMessage: TfrmPopupMessage;
procedure DisplayPopup( sMessage:string; sCaption:string = '');
implementation
{$R *.dfm}
const
DFT_TIME_SLEEP = 5; // the speed you want to show/hide.Increase/descrease this to make it faster or slower
DFT_TIME_VISIBLE = 3000; // number of mili-seconds the form is visible before starting to disappear
GAP = 2; // pixels between form and right and bottom edge of the screen
procedure DisplayPopup( sMessage:string; sCaption:string = '');
begin
// we could create the form here if necessary ...
if not Assigned(frmPopupMessage) then Exit;
frmPopupMessage.PopupCaption := sCaption;
frmPopupMessage.PopupMessage := sMessage;
if not frmPopupMessage.bBeingDisplayed
then begin
ShowWindow( frmPopupMessage.Handle, SW_SHOWNOACTIVATE);
frmPopupMessage.Visible := True;
end;
frmPopupMessage.StartAnimationToShow;
end;
procedure TfrmPopupMessage.FormCreate(Sender: TObject);
begin
img.Picture.Assign(Application.Icon);
Caption := '';
lblMessage.Caption := '';
bBeingDisplayed := False;
ToHiddenPosition();
end;
procedure TfrmPopupMessage.FormClose(Sender: TObject; var Action: TCloseAction);
begin
tmr.Enabled := False;
Action := caHide;
bBeingDisplayed := False;
end;
function TfrmPopupMessage.TaskBarHeight: integer; // this is just to get the taskbar height to put
// my form in the correct position
var
hTB: HWND;
TBRect: TRect;
begin
hTB := FindWindow('Shell_TrayWnd', '');
if hTB = 0 then
Result := 0
else
begin
GetWindowRect(hTB, TBRect);
if TBRect.Top = 0 // tray bar is positioned to the left or to the right
then
Result := 1
else
Result := TBRect.Bottom - TBRect.Top;
end;
end;
function TfrmPopupMessage.TaskBarWidth: integer; // this is just to get the taskbar height to put
// my form in the correct position
var
hTB: HWND;
TBRect: TRect;
begin
hTB := FindWindow('Shell_TrayWnd', '');
if hTB = 0 then
Result := 0
else
begin
GetWindowRect(hTB, TBRect);
if TBRect.Left = 0 // tray bar is positioned to the left or to the right
then
Result := 1
else
Result := TBRect.Right - TBRect.Left
end;
end;
procedure TfrmPopupMessage.ToHiddenPosition;
begin
Self.Left := Screen.Width - TaskbarWidth - Self.Width - GAP;
Self.Top := Screen.Height - TaskBarHeight;
end;
procedure TfrmPopupMessage.ToVisiblePosition;
begin
Self.Left := Screen.Width - TaskBarWidth - Self.Width - GAP;
Self.Top := Screen.Height - Self.Height - TaskBarHeight - GAP;
end;
procedure TfrmPopupMessage.StartAnimationToShow;
var
i: integer;
begin
if bBeingDisplayed
then
ToVisiblePosition()
else begin
ToHiddenPosition();
for i := 1 to Self.Height+GAP do
begin
Self.Top := Self.Top-1;
Application.ProcessMessages;
Sleep(DFT_TIME_SLEEP);
end;
end;
tmr.Interval := DFT_TIME_VISIBLE;
tmr.Enabled := True;
bBeingDisplayed := True;
end;
procedure TfrmPopupMessage.StartAnimationToHide;
var
i: integer;
begin
if not bBeingDisplayed then Exit;
for i := 1 to Self.Height+GAP do
begin
Self.Top := Self.Top+1;
Application.ProcessMessages;
Sleep(DFT_TIME_SLEEP);
end;
bBeingDisplayed := False;
Visible := False;
end;
procedure TfrmPopupMessage.tmrTimer(Sender: TObject);
begin
tmr.Enabled := False;
StartAnimationToHide();
end;
function TfrmPopupMessage.GetPopupMessage: string;
begin
Result := lblMessage.Caption;
end;
procedure TfrmPopupMessage.SetPopupMessage(const Value: string);
begin
lblMessage.Caption := Value;
end;
function TfrmPopupMessage.GetPopupCaption: string;
begin
Result := frmPopupMessage.Caption;
end;
procedure TfrmPopupMessage.SetPopupCaption(const Value: string);
begin
frmPopupMessage.Caption := Value;
end;
end.
To be used as in my test form with two buttons:
procedure TfrmMain.button1Click(Sender: TObject);
begin
DisplayPopup('Message displayed at ' + FormatDateTime('ddd mmm yy zzz', Now),'My Program');
beep;
end;
procedure TfrmMain.button2Click(Sender: TObject);
begin
DisplayPopup('Another message displayed at ' + FormatDateTime('hh:nn zzz', Now),'My Program');
end;
The message form will display the application icon, but I will probably add a TImageList and add a property to pass an image index so I can display different icons. I will also use the TcxLabel from the Dev.Express components as this will provide verticle positionting, but the above unit can be used as is.
I tested this with Delphi 7 and Windows XP. If anyone uses this unit with another version of Delphi and/or Windows Vista or Windows 7, please tell me if this unit will work there too.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
function TaskBarHeight: integer; // this is just to get the taskbar height to put
// my form in the correct position
var
hTB: HWND;
TBRect: TRect;
begin
hTB := FindWindow('Shell_TrayWnd', '');
if hTB = 0 then
Result := 0
else
begin
GetWindowRect(hTB, TBRect);
Result := TBRect.Bottom - TBRect.Top;
end;
end;
begin
Self.Left := Screen.Width - Self.Width;
Self.Top := Screen.Height-Self.Height-TaskBarHeight;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
TimeSleep: integer;
begin
TimeSleep := 5; // the speed you want to show/hide.Increase/descrease this to make it faster or slower
for i := 1 to Self.Height do
begin
Self.Top := Self.Top+1;
Sleep(TimeSleep);
end;
// now let's show it again(use this as code as the show code)
for i := 1 to Self.Height do
begin
Self.Top := Self.Top-1;
Sleep(TimeSleep);
end;
end;
end.
via http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_25043483.html
Try using the TJvDesktopAlert component wich is included in the JVCL, you can find an example in jvcl\examples\JvDesktopAlert\JvDesktopAlertDemo.dpr
(source: agnisoft.com)
What you are searching for are Balloon Tips in a System Tray. For general WinAPI here's a nice tutorial for it, that you shouldn't have problems translating to Delphi.
You can find some ready to use code for balloon tips in Delphi here.
A nice implementation is available here.
You can check where is Taskbar:
uses ShellAPI;
//...
Var AppBar: TAppbarData;
//...
begin
FillChar(AppBar, sizeof(AppBar), 0);
AppBar.cbSize := Sizeof(AppBar);
if ShAppBarMessage(ABM_GETTASKBARPOS, AppBar) <> 0 then
begin
//AppBar.rc is TRect
end;
end;
And then show your form...
You could use Growl for Windows - I don't think there is a Delphi library for it yet, but you can control it via UDP messages, so any network library should do.
TMsnPopUpNotify
http://www.torry.net/vcl/forms/appearence/tmsnpopup.zip
Check out Snarl, similar to Growl for Windows, but I have found to be better.
There is a Pas file to easily interface, and the way it works is very simple, with just sending windows messages.
http://fullphat.net/
It also allows the end user some amount of control of which messages to see, duration before fading, etc.