I want to create a slide effect: one bitmap is painted from right to left on a form's canvas. For this I use BitBlt.
I call this function in a Timer (20ms):
var ViewPort: TRect;
ViewPort.Left := 0;
ViewPort.Top := 0;
ViewPort.Width := 1400;
ViewPort.Height := 900;
x: integer := spnStep.Value; //SpinBox.Value = 10
procedure TfrmTester.Slide(BMP: TBitmap; ViewPort: TRect);
begin
Inc(x, spnStep.Value);
if x >= ViewPort.Width then
begin
x:= ViewPort.Width;
Timer.Enabled:= FALSE;
end;
BitBlt(frmTester.Canvas.Handle,
ViewPort.Width-x, 0, // X, Y
x, ViewPort.Height, // cX, cY
BMP.Canvas.Handle, 0, 0, SRCCOPY);
end;
However, the image does not progress smoothly. It has some kind of flicker, but not the kind of flicker that we know in the VCL. It is difficult to describe it. It is like the image moves two pixels forward and then one pixel backward.
How to make the image move smoothly?
Could the actually be caused by the refresh rate of the monitor?
Update: I don't know why, but it is caused by the timer.
If I call Slide() in a 'for' loop then the animation is smooth.
I know that the timer has an accuracy of ~15ms, but I still don't get it why it makes the image to shimmer.
If I add a sleed(1) inside the loop the shimmer effect appears again, and it is even worse. It really looks like the image is drawn twice.
First, you should only paint on the form in the form's OnPaint handler. I don't know if you do that or not, but you should do so.
Second, you cannot really rely on the temporal distance between successive WM_TIMER messages being very precise or even constant. So it is better to check the actual time each time you paint. For instance, you may use the formula Position = Original Position + Velocity × Time known from school physics.
Also, to avoid flickering, you should probably handle WM_ERASEBKGND.
Putting these together,
unit AnimatedRabbit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ExtCtrls;
type
TMainForm = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
FRabbit: TBitmap;
FStartTime: TDateTime;
const
Speed = -100;
function GetRabbitLeft: Double;
protected
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
public
end;
var
MainForm: TMainForm;
implementation
uses
DateUtils;
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
FRabbit := TBitmap.Create;
FRabbit.LoadFromFile('K:\rabbit.bmp');
FStartTime := Now;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
FreeAndNil(FRabbit);
end;
procedure TMainForm.FormPaint(Sender: TObject);
var
x: Integer;
begin
x := Round(GetRabbitLeft);
BitBlt(
Canvas.Handle,
x,
0,
FRabbit.Width,
FRabbit.Height,
FRabbit.Canvas.Handle,
0,
0,
SRCCOPY
);
Canvas.Brush.Color := Color;
if x > 0 then
Canvas.FillRect(Rect(0, 0, x - 1, ClientHeight));
if x + FRabbit.Width < ClientWidth then
Canvas.FillRect(Rect(x + FRabbit.Width, 0, ClientWidth, ClientHeight));
end;
function TMainForm.GetRabbitLeft: Double;
begin
Result := ClientWidth + Speed * SecondSpan(Now, FStartTime);
end;
procedure TMainForm.Timer1Timer(Sender: TObject);
begin
Invalidate;
if GetRabbitLeft + FRabbit.Width < 0 then
Timer1.Enabled := False;
end;
procedure TMainForm.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := 1;
end;
end.
I think this is as good as you can make it using GDI (a graphics API from the 1980s). I bet it will look better in Direct2D (or OpenGL, if you prefer that).
Update
After further investigation, I suspect that the usual timer isn't good enough. The problem is two-fold: (1) The best FPS obtainable by a normal timer is too low. (2) The fact that the duration between two consecutive WM_TIMER messages isn't constant causes visual issues.
If I instead use a high-resolution multimedia timer, ignoring the fact that they are deprecated, I get a nicer result:
unit AnimatedRabbit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ExtCtrls;
const
WM_DOINVALIDATE = WM_USER + 1;
type
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FRabbit: TBitmap;
FStartTime: TDateTime;
FMMEvent: Cardinal;
const
Speed = -100;
function GetRabbitLeft: Double;
protected
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure WMDoInvalidate(var Message: TMessage); message WM_DOINVALIDATE;
public
end;
var
MainForm: TMainForm;
implementation
uses
DateUtils, MMSystem, Math;
{$R *.dfm}
procedure RepaintFunc(wTimerID: UINT; msg: UINT; dwUser: NativeUINT;
dw1, dw2: NativeUINT); stdcall;
begin
PostMessage(MainForm.Handle, WM_DOINVALIDATE, 0, 0);
end;
procedure TMainForm.FormCreate(Sender: TObject);
const
TargetResolution = 1;
var
tc: TTimeCaps;
res: Cardinal;
begin
FRabbit := TBitmap.Create;
FRabbit.LoadFromFile('K:\rabbit.bmp');
FStartTime := Now;
if timeGetDevCaps(#tc, SizeOf(tc)) <> TIMERR_NOERROR then
Exit;
res := EnsureRange(TargetResolution, tc.wPeriodMin, tc.wPeriodMax);
if timeBeginPeriod(res) <> TIMERR_NOERROR then
Exit;
FMMEvent := timeSetEvent(10, res, RepaintFunc, 0, TIME_PERIODIC);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
timeKillEvent(FMMEvent);
FreeAndNil(FRabbit);
end;
procedure TMainForm.FormPaint(Sender: TObject);
var
x: Integer;
begin
x := Round(GetRabbitLeft);
BitBlt(
Canvas.Handle,
x,
0,
FRabbit.Width,
FRabbit.Height,
FRabbit.Canvas.Handle,
0,
0,
SRCCOPY
);
Canvas.Brush.Color := Color;
if x > 0 then
Canvas.FillRect(Rect(0, 0, x - 1, ClientHeight));
if x + FRabbit.Width < ClientWidth then
Canvas.FillRect(Rect(x + FRabbit.Width, 0, ClientWidth, ClientHeight));
end;
function TMainForm.GetRabbitLeft: Double;
begin
Result := ClientWidth + Speed * SecondSpan(Now, FStartTime);
end;
procedure TMainForm.WMDoInvalidate(var Message: TMessage);
begin
Invalidate;
end;
procedure TMainForm.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := 1;
end;
end.
Update 2
And here is the non-deprecated version:
unit AnimatedRabbit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ExtCtrls;
const
WM_DOINVALIDATE = WM_USER + 1;
type
TMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FRabbit: TBitmap;
FStartTime: TDateTime;
FTimer: THandle;
const
Speed = -100;
function GetRabbitLeft: Double;
protected
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure WMDoInvalidate(var Message: TMessage); message WM_DOINVALIDATE;
public
end;
var
MainForm: TMainForm;
implementation
uses
DateUtils, Math;
{$R *.dfm}
procedure RepaintFunc(Context: Pointer; Success: Boolean); stdcall;
begin
PostMessage(MainForm.Handle, WM_DOINVALIDATE, 0, 0);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FRabbit := TBitmap.Create;
FRabbit.LoadFromFile('K:\rabbit.bmp');
FStartTime := Now;
if not CreateTimerQueueTimer(FTimer, 0, RepaintFunc, nil, 0, 10, 0) then
RaiseLastOSError;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
DeleteTimerQueueTimer(0, FTimer, INVALID_HANDLE_VALUE);
FreeAndNil(FRabbit);
end;
procedure TMainForm.FormPaint(Sender: TObject);
var
x: Integer;
begin
x := Round(GetRabbitLeft);
BitBlt(
Canvas.Handle,
x,
0,
FRabbit.Width,
FRabbit.Height,
FRabbit.Canvas.Handle,
0,
0,
SRCCOPY
);
Canvas.Brush.Color := Color;
if x > 0 then
Canvas.FillRect(Rect(0, 0, x - 1, ClientHeight));
if x + FRabbit.Width < ClientWidth then
Canvas.FillRect(Rect(x + FRabbit.Width, 0, ClientWidth, ClientHeight));
end;
function TMainForm.GetRabbitLeft: Double;
begin
Result := ClientWidth + Speed * SecondSpan(Now, FStartTime);
end;
procedure TMainForm.WMDoInvalidate(var Message: TMessage);
begin
Invalidate;
end;
procedure TMainForm.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := 1;
end;
end.
Also, I previously said that the precise result depends on CPU, GPU, OS, and monitor. But it also depends on the eye and brain. The thing that makes this animation require such a high-quality timer is the fact that the motion is a simple translation with constant velocity, and the eye + brain can easily spot any imperfection. If we had animated a bouncing ball or SHM, an old-school timer would have been enough.
You should not be drawing on the Form's Canvas from outside of its OnPaint event at all. All of the drawing should be in the OnPaint event only. Have your timer save the desired information into variables that the Form can access, and then Invalidate() the Form, and let its OnPaint event draw the image using the latest saved information.
Alternatively, simply display your BMP inside a TImage control, and then have the timer set that control's Left/Top/Width/Height properties as needed. Let the TImage handle the drawing of the image for you.
You can use AnimateWindow
Here's the DFM. Just add client aligned TPicture inside the TPanel
object Form30: TForm30
Left = 0
Top = 0
Caption = 'Form30'
ClientHeight = 337
ClientWidth = 389
Color = clBtnFace
DoubleBuffered = True
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 389
Height = 289
Align = alTop
BevelOuter = bvNone
Color = clRed
FullRepaint = False
ParentBackground = False
ShowCaption = False
TabOrder = 0
Visible = False
end
object Button1: TButton
Left = 136
Top = 304
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 1
OnClick = Button1Click
end
end
And the Button1.OnClick handler:
procedure TForm30.Button1Click(Sender: TObject);
begin
AnimateWindow(Panel1.Handle, 1000, AW_SLIDE or AW_HOR_POSITIVE or AW_ACTIVATE);
end;
Related
So, I don't even know how to write the proper title.
What I want to do is to animate the position of lets say a progressbar.
One could discuss how to do this with timers and loops and so on.
However, I want to be able to do something like this:
ProgressBar1.Position:=Animate(ToValue);
or
Animate(ProgressBar1.Position, ToValue);
Is this possible?
creating a component inherited from an integer didnt work.
I tried number 2 using pointers and made this procedure
procedure TForm1.Animate(ToValue: integer; var Dest: Integer);
begin
Dest:=ToValue;
end;
and it did change the position value internally of the progress bar,
but the progress bar did not change visually.
If anybody has an idea of how to do this it would be great.
Thank you!
If you have a relative new version of Delphi,
this is an animation wrapper around a TTimer using anonymous methods.
type
Animate = class
private
class var fTimer : TTimer;
class var fStartValue : Integer;
class var fEndValue : Integer;
class var fProc : TProc<Integer>;
class Constructor Create;
class Destructor Destroy;
class procedure OnTimer(Sender : TObject);
public
class procedure Run( aProc : TProc<Integer>;
fromValue, ToValue, AnimationDelay : Integer);
end;
class constructor Animate.Create;
begin
fTimer := TTimer.Create(nil);
fTimer.Enabled := false;
fTimer.OnTimer := Animate.OnTimer;
end;
class destructor Animate.Destroy;
begin
fTimer.Free;
end;
class procedure Animate.OnTimer(Sender: TObject);
begin
if Assigned(fProc) then
begin
if (fStartValue <= fEndValue) then
begin
fProc(fStartValue);
Inc(fStartValue);
end
else
fTimer.Enabled := false;
end;
end;
class procedure Animate.Run( aProc: TProc<Integer>;
fromValue, ToValue, AnimationDelay: Integer);
begin
fTimer.Interval := AnimationDelay;
fStartValue := fromValue;
fEndValue := ToValue;
fProc := aProc;
fTimer.Enabled := (fStartValue <= fEndValue);
end;
The Animate class is self initializing and self destructing on application start/stop.
Only one animation process can be active.
Use it this way :
Animate.Run(
procedure( aValue : Integer)
begin
ProgressBar1.Position := aValue;
ProgressBar1.Update;
end,
1,100,5
);
As discussed in comments, the above code use class variables and class functions. Drawback is only one animation can be active.
Here is a more complete animation class, where you can instantiate as many animations you like. Expanded functionallity with possibility to stop/proceed, adding an event when ready, and some more properties.
unit AnimatePlatform;
interface
uses
System.Classes,System.SysUtils,Vcl.ExtCtrls;
type
TAnimate = class
private
fTimer : TTimer;
fLoopIx : Integer;
fEndIx : Integer;
fProc : TProc<Integer>;
fOnReady : TProc<TObject>;
procedure OnTimer(Sender : TObject);
function GetRunning : boolean;
procedure SetReady;
public
Constructor Create;
Destructor Destroy; override;
procedure Run( aProc : TProc<Integer>;
FromValue,ToValue,AnimationDelay : Integer); overload;
procedure Run( aProc : TProc<Integer>;
FromValue,ToValue,AnimationDelay : Integer;
AReadyEvent : TNotifyEvent); overload;
procedure Run( aProc : TProc<Integer>;
FromValue,ToValue,AnimationDelay : Integer;
AReadyEvent: TProc<TObject>); overload;
procedure Stop;
procedure Proceed;
property ActualLoopIx : Integer read fLoopIx write fLoopIx;
property Running : boolean read GetRunning;
property OnReady : TProc<TObject> read fOnReady write fOnReady;
end;
implementation
constructor TAnimate.Create;
begin
Inherited;
fTimer := TTimer.Create(nil);
fTimer.Enabled := false;
fTimer.OnTimer := Self.OnTimer;
fOnReady := nil;
end;
destructor TAnimate.Destroy;
begin
fTimer.Free;
Inherited;
end;
function TAnimate.GetRunning: boolean;
begin
Result := fTimer.Enabled;
end;
procedure TAnimate.OnTimer(Sender: TObject);
begin
if Assigned(fProc) then
begin
if (fLoopIx <= fEndIx) then
begin
fProc(fLoopIx);
Inc(fLoopIx);
end;
if (fLoopIx > fEndIx) then
SetReady;
end
else SetReady;
end;
procedure TAnimate.Proceed;
begin
fTimer.Enabled := true;
end;
procedure TAnimate.Run(aProc: TProc<Integer>; FromValue, ToValue,
AnimationDelay: Integer; AReadyEvent: TNotifyEvent);
begin
Run(aProc,FromValue,ToValue,AnimationDelay);
fOnReady := AReadyEvent;
end;
procedure TAnimate.Run(aProc: TProc<Integer>; FromValue, ToValue,
AnimationDelay: Integer; AReadyEvent: TProc<TObject>);
begin
Run(aProc,FromValue,ToValue,AnimationDelay);
fOnReady := AReadyEvent;
end;
procedure TAnimate.Run(aProc: TProc<Integer>; fromValue, ToValue,
AnimationDelay: Integer);
begin
fTimer.Interval := AnimationDelay;
fLoopIx := fromValue;
fEndIx := ToValue;
fProc := aProc;
fTimer.Enabled := true;
end;
procedure TAnimate.SetReady;
begin
Stop;
if Assigned(fOnReady) then
fOnReady(Self);
end;
procedure TAnimate.Stop;
begin
fTimer.Enabled := false;
end;
end.
Update:
Instead of a TTimer based animator, here is a version using an anonymous thread:
uses
SyncObjs;
procedure AnimatedThread( aProc: TProc<Integer>;
FromValue, ToValue, AnimationDelay: Integer;
AReadyEvent: TNotifyEvent);
begin
TThread.CreateAnonymousThread(
procedure
var
i: Integer;
w : TSimpleEvent;
begin
w := TSimpleEvent.Create(Nil,False,False,'');
try
for i := FromValue to ToValue do begin
TThread.Synchronize(nil,
procedure
begin
aProc(i);
end
);
w.WaitFor(AnimationDelay);
end;
finally
w.Free;
end;
if Assigned(AReadyEvent) then
TThread.Synchronize(nil,
procedure
begin
AReadyEvent(Nil);
end
);
end
).Start;
end;
// Example call
AnimateThread(
procedure(aValue: Integer)
begin
ProgressBar1.Position := aValue;
ProgressBar1.Update;
end,
1,100,5,nil
);
You can do this easily with RTTI.
You cannot avoid writing a loop, but you can write it once and call your Animate method for any object/property you want to set. Of course, writing such a function is still tricky because you have to take into account flickering, time the UI is blocking, etc.
A very simple example would be something in the lines of:
implementation
uses RTTI;
procedure TForm1.Animate(AObj: TObject; APropertyName: string; AValue: Integer);
var
Context: TRTTIContext;
OType: TRTTIType;
Prop: TRTTIProperty;
StartValue: Integer;
begin
Context := TRTTIContext.Create;
OType := context.GetType(AObj.ClassType);
Prop := OType.GetProperty(APropertyName);
StartValue := Prop.GetValue(AObj).AsInteger;
for AValue := StartValue to AValue do
begin
Prop.SetValue(AObj, AValue);
if AObj is TWinControl then
begin
TWinControl(AObj).Update;
Sleep(3);
end;
end;
end;
//call it like this:
procedure TForm1.Button1Click(Sender: TObject);
begin
Animate(ProgressBar1, 'Position', 30);
Animate(Self, 'Height', 300);
end;
As David says, you will need to use Timers. Here's some code the demonstates the principle. I would advise that you take the idea and roll them into your own TProgressbar descendant.
Be aware that under Vista and Windows 7 TProgressBar has some built in animations when incrementing the position. This can produce odd effects when using your own animation.
You don't mention which version of Delphi you are using. This example was created using XE2. If you are using an earlier version you may need to fix the dotted unit names in the uses clause e.g. Winapi.Windows should be Windows.
Code:
unit Unit11;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, Vcl.ExtCtrls,
Vcl.Samples.Spin;
type
TForm11 = class(TForm)
ProgressBar1: TProgressBar;
Timer1: TTimer;
Button1: TButton;
Button2: TButton;
spnIncrement: TSpinEdit;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
FDestPos: Integer;
FProgInc: Integer;
procedure AnimateTo(const DestPos, Increment: Integer);
public
{ Public declarations }
end;
var
Form11: TForm11;
implementation
{$R *.dfm}
procedure TForm11.Button1Click(Sender: TObject);
begin
AnimateTo(10, spnIncrement.Value);
end;
procedure TForm11.Button2Click(Sender: TObject);
begin
AnimateTo(90, spnIncrement.Value);
end;
procedure TForm11.Timer1Timer(Sender: TObject);
begin
if ((FProgInc > 0) and (ProgressBar1.Position + FProgInc >= FDestPos)) or
((FProgInc < 0) and (ProgressBar1.Position + FProgInc <= FDestPos)) then
begin
ProgressBar1.Position := FDestPos;
Timer1.Enabled := FALSE;
end
else
begin
ProgressBar1.Position := ProgressBar1.Position + FProgInc;
end;
end;
procedure TForm11.AnimateTo(const DestPos, Increment: Integer);
begin
FDestPos := DestPos;
FProgInc := Increment;
if FDestPos < ProgressBar1.Position then
FProgInc := -FProgInc;
Timer1.Enabled := FProgInc <> 0;
end;
end.
DFM:
object Form11: TForm11
Left = 0
Top = 0
BorderStyle = bsDialog
Caption = 'Animated Progressbar'
ClientHeight = 77
ClientWidth = 466
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
Position = poScreenCenter
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 309
Top = 42
Width = 53
Height = 13
Caption = 'Increment:'
end
object ProgressBar1: TProgressBar
Left = 24
Top = 16
Width = 417
Height = 17
TabOrder = 0
end
object Button1: TButton
Left = 24
Top = 39
Width = 75
Height = 25
Caption = '10%'
TabOrder = 1
OnClick = Button1Click
end
object Button2: TButton
Left = 105
Top = 39
Width = 75
Height = 25
Caption = '90%'
TabOrder = 2
OnClick = Button2Click
end
object spnIncrement: TSpinEdit
Left = 368
Top = 39
Width = 73
Height = 22
MaxValue = 100
MinValue = 1
TabOrder = 3
Value = 0
end
object Timer1: TTimer
Enabled = False
Interval = 20
OnTimer = Timer1Timer
Left = 240
Top = 40
end
end
You can't assign anything other than an integer to a progress bar's position. So, if you want to make the position move smoothly from one value to another you need to set the position to each individual value.
There are no handy shortcuts. There's nothing available out of the box like jQuery's animate() method. You mention timers and loops. Those are the methods you need to use.
I am using LineDDA to draw animated selection:
procedure TFormMain.DrawMarchingAnts;
begin
AMarchingAntsCounter := AMarchingAntsCounterStart;
// Use LineDDA to draw each of the 4 edges of the rectangle
LineDDA(AMarchingAntsPointA.X, AMarchingAntsPointA.Y, AMarchingAntsPointB.X, AMarchingAntsPointA.Y,
#MarchingAnts, LongInt(
AMarchingAntsCanvas));
LineDDA(AMarchingAntsPointB.X, AMarchingAntsPointA.Y, AMarchingAntsPointB.X, AMarchingAntsPointB.Y,
#MarchingAnts, LongInt(
AMarchingAntsCanvas));
LineDDA(AMarchingAntsPointB.X, AMarchingAntsPointB.Y, AMarchingAntsPointA.X, AMarchingAntsPointB.Y,
#MarchingAnts, LongInt(
AMarchingAntsCanvas));
LineDDA(AMarchingAntsPointA.X, AMarchingAntsPointB.Y, AMarchingAntsPointA.X, AMarchingAntsPointA.Y,
#MarchingAnts, LongInt(
AMarchingAntsCanvas));
if AMarchingAntsPointB.X > AMarchingAntsPointA.X then
ARubberbandVisible := True
else
ARubberbandVisible := False;
end;
Is there a function to add animated ellipses to the corners of the rect for grip points?
You want an animated "marching ants" circle? Then create a custom pen style. For example, as follows:
unit Unit1;
interface
uses
Windows, Classes, Graphics, Controls, Forms, ExtCtrls, Math;
type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormPaint(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FMarkBrush: LOGBRUSH;
FMarkPen: HPEN;
FPenStyle: array[0..1] of Integer;
FStartAngle: Single;
end;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FPenStyle[0] := 4;
FPenStyle[1] := 4;
FMarkBrush.lbStyle := BS_SOLID;
FMarkBrush.lbColor := ColorToRGB(clBlue);
FMarkPen := ExtCreatePen(PS_GEOMETRIC or PS_USERSTYLE, 1, FMarkBrush, 2,
#FPenStyle);
Canvas.Pen.Handle := FMarkPen;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
X: Integer;
Y: Integer;
begin
Canvas.FillRect(Rect(0, 0, 50, 50));
X := Round(25 * (1 + Cos(FStartAngle)));
Y := Round(25 * (1 + Sin(FStartAngle)));
Canvas.Arc(0, 0, 50, 50, X, Y, X, Y);
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
FStartAngle := FStartAngle + DegToRad(5);
Invalidate;
end;
end.
I have a TFrame with a TImage as background.
This frame serves as ancestor for other frames that I put on a limited space in the main TForm.
So it is just a user interface base for the other frames.
I need to put many controls inside these frames, because they will handle large database forms.
As the main form has limited space, I need to put a TScrollBox in all the TFrame space except for the title bar. But this covers the backgroud image.
How do I make this ScrollBar to be background transparent?
Or is it better to make a new component with that functionality, and how to do it?
I saw some examples in other sites, but they are buggy at the run-time
Thank You!
Edit2:
I found the TElScrollBox from ElPack from LMD Inovative.
This is background transparent and allow us to put an image as background.
But the same problem occurs: When we scroll it at run-time, it moves the ancestor's background in it's area of effect.
Edit1:
I've tried to make a descendant but the scrollbar only shows when we pass hover the mouse where it should be, and the form's background move inside the scrollbox when we scroll it.
And also, the controls inside of it get some paint errors...
interface
uses
Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms,
Dialogs, Menus, StdCtrls, ExtCtrls;
type
TTransScrollBox = class(TScrollBox)
private
{ Private declarations }
protected
procedure CreateParams(var params: TCreateParams); override;
procedure WMEraseBkGnd(var msg: TWMEraseBkGnd); message WM_ERASEBKGND;
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Eduardo', [TTransScrollBox]);
end;
procedure TTransScrollBox.CreateParams(var params: TCreateParams);
begin
inherited CreateParams(params);
params.ExStyle := params.ExStyle or WS_EX_TRANSPARENT;
end;
procedure TTransScrollBox.WMEraseBkGnd(var Msg: TWMEraseBkGnd);
begin
SetBkMode (Msg.DC, TRANSPARENT);
Msg.Result := 1;
end;
f you don't want the image to scroll you will have to roll your own scroller, which is not too difficult (It still raining here in England so I'm bored!)
To test, Create the frame put the image on and alighn to client.
Put a scrollbar on the frame set to vertical and align right.
enlarge the frame at design time.
Put controls on anywhere and then shrink it so some are not visible (below the bottom).
On the main form in form show (for testing), or when you create a new frame call Frame.BeforeShow to do the setup.
[LATER] EDIT It's raining & Still Bored So I finished it for ya!
unit ScrollingBaseFrameU;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, jpeg, ExtCtrls, StdCtrls, contnrs, Generics.Collections, Grids,
DBGrids;
const
MaxLargeSteps = 5; // maximum large scroll clicks to scroll to max
IgnoreTag = 99; // Controls with this tag value are igored for scrolling
TitleHeight = 23; // Height of title bar as we are dealing with the image mouse co-ordinates
RightMargin = 25; // space after right-most control
BottomMargin = 25; // space after bottom-most control
StrControl = 'ControlName'; // prefix for controls with no name
type
TControlPos = class(Tobject) // Little object to save initial control positions
public
Name: string;
X,
Y: Integer;
end;
TScrollingBaseFrame = class(TFrame)
BackGroundImage: TImage;
HorzScrollBar: TScrollBar;
VertScrollBar: TScrollBar;
pnlTitle: TPanel;
procedure VertScrollBarChange(Sender: TObject);
procedure BackGroundImageMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure FrameResize(Sender: TObject);
procedure HorzScrollBarChange(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
ShowHScroller,
ShowVScroller : Boolean; // scroller needed at all?
PosList: TList<TControlPos>; // list of initial positions
procedure BeforeShow; virtual; // override in descendants for specific behaviour
procedure BeforeClose; virtual; // override in descendants for specific behaviour
function IndexOfPos(AName:string): Integer;
end;
implementation
{$R *.dfm}
procedure TScrollingBaseFrame.BeforeClose;
// Clean up
var
p: TControlPos;
begin
for p in PosList do
p.free;
PosList.Free;
end;
procedure TScrollingBaseFrame.BeforeShow;
//Setup scroller and save initial control positions
var
i,XMax,YMax,Idx: Integer;
AControl: TControl;
begin
pnlTitle.Height := TitleHeight;
PosList := TList<TControlpos>.Create;
XMax := 0;
YMax := 0;
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
if Acontrol.Tag <> IgnoreTag then
begin
Idx := PosList.Add(TcontrolPos.Create);
if AControl.Name = '' then // deal with empty names
AControl.Name := StrControl + IntToStr(i);
PosList[Idx].Name := AControl.Name;
PosList[Idx].X := AControl.Left;
PosList[Idx].Y := AControl.Top;
if YMax < AControl.Top + AControl.Height then
YMax := AControl.Top + AControl.Height;
if XMax < AControl.Left + AControl.Width then
XMax := AControl.Left + AControl.Width;
end; // Ignored
end; // is control
end; // count
VertScrollBar.Max := (YMax + BottomMargin) - Height; // bit of a bottom margin :)
VertScrollBar.LargeChange := VertScrollBar.Max div MaxLargeSteps;
ShowVScroller := VertScrollBar.Max > BottomMargin;
VertScrollBar.Visible := ShowVScroller;
HorzScrollBar.Max := (XMax + RightMargin) - Width; // bit of a Right margin :)
HorzScrollBar.LargeChange := HorzScrollBar.Max div MaxLargeSteps;
ShowHScroller := HorzScrollBar.Max > RightMargin;
HorzScrollBar.Visible := ShowHScroller;
end;
procedure TScrollingBaseFrame.FrameResize(Sender: TObject);
begin
BackGroundImage.Width := Width;
BackGroundImage.Height := Height;
end;
procedure TScrollingBaseFrame.HorzScrollBarChange(Sender: TObject);
// Move the controls left and right relative to thier initail positions
var
i,j: Integer;
AControl: TControl;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
j := IndexOfPos(AControl.Name);
if j >= 0 then // could be ignored or the background image
Acontrol.Left := PosList[j].X - HorzScrollBar.Position;
end;
end;
end;
procedure TScrollingBaseFrame.BackGroundImageMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
// Show/Hide the scrollbars using mouse position
var
ScrollBarWidth: Integer;
begin
ScrollBarWidth := GetSystemmetrics(SM_CXVSCROLL); // assume the same for horizontal
VertScrollBar.Visible := (X > Width - ScrollBarWidth) and ShowVScroller;
HorzScrollBar.Visible := (Y > (Height - TitleHeight) - ScrollBarWidth) and ShowHScroller;
end;
function TScrollingBaseFrame.IndexOfPos(AName:string): Integer;
// Find a control position in the list by name
var
Idx: Integer;
begin
Result := -1;
Idx := 0;
while (Result < 0) and (Idx < PosList.Count) do
begin
if PosList[idx].Name = AName then
Result := idx;
inc(idx);
end;
end;
procedure TScrollingBaseFrame.VertScrollBarChange(Sender: TObject);
// Move the controls Up and down relative to thier initail positions
var
i,j: Integer;
AControl: TControl;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
j := IndexOfPos(AControl.Name);
if j >= 0 then // could be ignored
Acontrol.Top := PosList[j].Y - VertScrollBar.Position;
end;
end;
end;
end.
and the DFM for completeness:
object ScrollingBaseFrame: TScrollingBaseFrame
Left = 0
Top = 0
Width = 830
Height = 634
DoubleBuffered = True
ParentDoubleBuffered = False
TabOrder = 0
OnResize = FrameResize
object BackGroundImage: TImage
Tag = 99
Left = 0
Top = 23
Width = 813
Height = 594
Align = alClient
Picture.Data = { **Removed as it was so big!**}
Transparent = True
OnMouseMove = BackGroundImageMouseMove
ExplicitTop = 0
ExplicitWidth = 1600
ExplicitHeight = 1200
end
object HorzScrollBar: TScrollBar
Tag = 99
Left = 0
Top = 617
Width = 830
Height = 17
Align = alBottom
PageSize = 0
TabOrder = 0
OnChange = HorzScrollBarChange
ExplicitLeft = 231
ExplicitTop = 293
ExplicitWidth = 121
end
object VertScrollBar: TScrollBar
Tag = 99
Left = 813
Top = 23
Width = 17
Height = 594
Align = alRight
Kind = sbVertical
PageSize = 0
TabOrder = 1
OnChange = VertScrollBarChange
ExplicitTop = 29
end
object pnlTitle: TPanel
Tag = 99
Left = 0
Top = 0
Width = 830
Height = 23
Align = alTop
Caption = 'pnlTitle'
TabOrder = 2
ExplicitLeft = 184
ExplicitTop = 3
ExplicitWidth = 185
end
end
[2ND EDIT] Well, Not wanting my spare time to go to waste, the below should work with Delphi 6 onwards.
unit ScrollingBaseFrameU;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, jpeg, ExtCtrls, StdCtrls, contnrs, Grids,
DBGrids;
const
MaxLargeSteps = 5; // maximum large scroll clicks to scroll to max
IgnoreTag = 99; // Controls with this tag value are igored for scrolling
TitleHeight = 23; // Height of title bar as we are dealing with the image mouse co-ordinates
RightMargin = 25; // space after right-most control
BottomMargin = 25; // space after bottom-most control
StrControl = 'ControlName'; // prefix for controls with no name
type
TControlPos = class(Tobject) // Little object to save initial control positions
public
Name: string;
X,
Y: Integer;
end;
TControlPosList = class(TObject)
private
function GetCount: Integer;
function GetItems(Index: Integer): TControlPos;
procedure SetItems(Index: Integer; const Value: TControlPos);
public
TheList: TObjectList;
Constructor Create; virtual;
Destructor Destroy; override;
function Add(APos: TControlPos): Integer;
function IndexOfPos(AName: string): Integer;
property Count: Integer read GetCount;
property Items[Index: Integer]: TControlPos read GetItems write SetItems; default;
end;
TScrollingBaseFrame = class(TFrame)
BackGroundImage: TImage;
HorzScrollBar: TScrollBar;
VertScrollBar: TScrollBar;
pnlTitle: TPanel;
procedure VertScrollBarChange(Sender: TObject);
procedure BackGroundImageMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure FrameResize(Sender: TObject);
procedure HorzScrollBarChange(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
ShowHScroller,
ShowVScroller : Boolean; // scroller needed at all?
PosList: TControlPosList; // list of initial positions
procedure BeforeShow; virtual; // override in descendants for specific behaviour
procedure BeforeClose; virtual; // override in descendants for specific behaviour
end;
implementation
{$R *.dfm}
procedure TScrollingBaseFrame.BeforeClose;
// Clean up
begin
PosList.Free;
end;
procedure TScrollingBaseFrame.BeforeShow;
//Setup scroller and save initial control positions
var
i,XMax,YMax,Idx: Integer;
AControl: TControl;
begin
pnlTitle.Height := TitleHeight;
PosList := TControlPosList.Create;
XMax := 0;
YMax := 0;
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
if Acontrol.Tag <> IgnoreTag then
begin
Idx := PosList.Add(TcontrolPos.Create);
if AControl.Name = '' then // deal with empty names
AControl.Name := StrControl + IntToStr(i);
PosList[Idx].Name := AControl.Name;
PosList[Idx].X := AControl.Left;
PosList[Idx].Y := AControl.Top;
if YMax < AControl.Top + AControl.Height then
YMax := AControl.Top + AControl.Height;
if XMax < AControl.Left + AControl.Width then
XMax := AControl.Left + AControl.Width;
end; // Ignored
end; // is control
end; // count
VertScrollBar.Max := (YMax + BottomMargin) - Height; // bit of a bottom margin :)
VertScrollBar.LargeChange := VertScrollBar.Max div MaxLargeSteps;
ShowVScroller := VertScrollBar.Max > BottomMargin;
VertScrollBar.Visible := ShowVScroller;
HorzScrollBar.Max := (XMax + RightMargin) - Width; // bit of a Right margin :)
HorzScrollBar.LargeChange := HorzScrollBar.Max div MaxLargeSteps;
ShowHScroller := HorzScrollBar.Max > RightMargin;
HorzScrollBar.Visible := ShowHScroller;
end;
procedure TScrollingBaseFrame.FrameResize(Sender: TObject);
begin
BackGroundImage.Width := Width;
BackGroundImage.Height := Height;
end;
procedure TScrollingBaseFrame.HorzScrollBarChange(Sender: TObject);
// Move the controls left and right relative to thier initail positions
var
i,j: Integer;
AControl: TControl;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
j := PosList.IndexOfPos(AControl.Name);
if j >= 0 then // could be ignored
Acontrol.Left := PosList[j].X - HorzScrollBar.Position;
end;
end;
end;
procedure TScrollingBaseFrame.BackGroundImageMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
// Show/Hide the scrollbars using mouse position
var
ScrollBarWidth: Integer;
begin
ScrollBarWidth := GetSystemmetrics(SM_CXVSCROLL); // assume the same for horizontal
VertScrollBar.Visible := (X > Width - ScrollBarWidth) and ShowVScroller;
HorzScrollBar.Visible := (Y > (Height - TitleHeight) - ScrollBarWidth) and ShowHScroller;
end;
procedure TScrollingBaseFrame.VertScrollBarChange(Sender: TObject);
// Move the controls Up and down relative to thier initail positions
var
i,j: Integer;
AControl: TControl;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TControl then
begin
AControl := TControl(Components[i]);
j := PosList.IndexOfPos(AControl.Name);
if j >= 0 then // could be ignored
Acontrol.Top := PosList[j].Y - VertScrollBar.Position;
end;
end;
end;
{ TcontrolPosList }
function TControlPosList.Add(APos: TControlPos): Integer;
begin
Result := TheList.Add(APos);
end;
constructor TControlPosList.Create;
begin
TheList := TObjectList.Create;
TheList.OwnsObjects := True;
end;
destructor TControlPosList.Destroy;
begin
TheList.Free;
inherited;
end;
function TControlPosList.GetCount: Integer;
begin
Result := TheList.Count;
end;
function TControlPosList.GetItems(Index: Integer): TControlPos;
begin
Result := TControlPos(TheList[Index]);
end;
function TControlPosList.IndexOfPos(AName: string): Integer;
// Find a control position in the list by name
var
Idx: Integer;
begin
Result := -1;
Idx := 0;
while (Result < 0) and (Idx < TheList.Count) do
begin
if Items[idx].Name = AName then
Result := idx;
inc(idx);
end;
end;
procedure TControlPosList.SetItems(Index: Integer; const Value: TControlPos);
begin
TheList[Index] := Value;
end;
end.
Reverse the order on the Base frame :)
Put the ScrollBox on, then put the image on the Scrollbox (align Client) and make it transparent. Then Place controls all over it and it allows scrolling...
I'm sure you will have tried this, so what gives you a problem...
I have an object consisting of a TFrame, on it a TPanel and on that a TImage. A bitmap is assigned to the TImage containing a piano roll. This frame-object is put on a TImage, containing an image that contains a grid. See the image for an example.
Question: Is it possible to make the frame partially transparent, so that the background image containing the grid (on the main form) is vaguely visible? Ideally the amount of transparency can be set by the user. The bitmap is 32 bit deep but experimenting with the alpha channel did not help. The panel is not strictly necessary. It is used to quickly have a border around the object. I could draw that on the image.
Update 1 A small code example is added. The main unit draws a background with vertical lines. The second unit contains a TFrame and a TImage upon it that draws a horizontal line. What I would like to see is that the vertical lines partially shine thru the TFrame Image.
Update 2 What I did not specify in my original question: the TFrame is part of a much bigger application and behaves independently. It would help if the transparency issue could be handled by the TFrame itself.
///////////////// Main unit, on mouse click draw lines and plot TFrame
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls,
Unit2;
type
TForm1 = class(TForm)
Image1: TImage;
procedure Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var background: TBitmap;
f: TFrame2;
i, c: Int32;
begin
background := TBitmap.Create;
background.Height := Image1.Height;
background.Width := Image1.Width;
background.Canvas.Pen.Color := clBlack;
for i := 0 to 10 do
begin
c := i * background.Width div 10;
background.Canvas.MoveTo (c, 0);
background.Canvas.LineTo (c, background.Height);
end;
Image1.Picture.Assign (background);
Application.ProcessMessages;
f := TFrame2.Create (Self);
f.Parent := Self;
f.Top := 10;
f.Left := 10;
f.plot;
end;
end.
///////////////////Unit containing the TFrame
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TFrame2 = class(TFrame)
Image1: TImage;
procedure plot;
end;
implementation
{$R *.dfm}
procedure TFrame2.plot;
var bitmap: TBitmap;
begin
bitmap := TBitmap.Create;
bitmap.Height := Image1.Height;
bitmap.Width := Image1.Width;
bitmap.PixelFormat := pf32Bit;
bitmap.Canvas.MoveTo (0, bitmap.Height div 2);
bitmap.Canvas.LineTo (bitmap.Width, bitmap.Height div 2);
Image1.Picture.Assign (bitmap);
end;
end.
Update 3 I had hoped for that there would be some message or API call that would result in a solution that the control could make itself partially transparent, like the WMEraseBkGnd message does for complete transparency. In their solutions both Sertac and NGLN both point at simulating transparency with the AlphaBlend function. This function merges two bitmaps and thus requires a knowledge of the background image. Now my TFrame has an extra property: BackGround: TImage that is assigned by the parent control. That gives the desired result (it's sooo professional to see it working :-)
RRUZ points to the Graphics32 library. What I've seen it produces fantastic results, for me the learning curve is too steep.
Thank you all for your help!
Here's another solution that copies the background image to the top image and AlphaBlends the bitmap over it while preserving opacity of black dots:
unit1:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Unit2, ExtCtrls, ComCtrls, StdCtrls;
type
TForm1 = class(TForm)
Clip_View1: TClip_View;
TrackBar1: TTrackBar;
Label1: TLabel;
procedure TrackBar1Change(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
TrackBar1.Min := 0;
TrackBar1.Max := 255;
TrackBar1.Position := 255;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
Label1.Caption := IntToStr(TrackBar1.Position);
Clip_View1.Transparency := TrackBar1.Position;
end;
end.
unit2:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;
type
TClip_View = class(TFrame)
Image1: TImage;
Panel1: TPanel;
Image2: TImage;
protected
procedure SetTransparency(Value: Byte);
private
FTopBmp: TBitmap;
FTransparency: Byte;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Transparency: Byte read FTransparency write SetTransparency;
end;
implementation
{$R *.dfm}
{ TClip_View }
constructor TClip_View.Create(AOwner: TComponent);
begin
inherited;
Image1.Left := 0;
Image1.Top := 0;
Image1.Picture.LoadFromFile(ExtractFilePath(Application.ExeName) + '..\..\back.bmp');
Image1.Picture.Bitmap.PixelFormat := pf32bit;
Image1.Width := Image1.Picture.Bitmap.Width;
Image1.Height := Image1.Picture.Bitmap.Height;
FTopBmp := TBitmap.Create;
FTopBmp.LoadFromFile(ExtractFilePath(Application.ExeName) + '..\..\top.bmp');
FTopBmp.PixelFormat := pf32bit;
Image2.SetBounds(1, 1, FTopBmp.Width, FTopBmp.Height);
Panel1.SetBounds(20, 20, Image2.Width + 2, Image2.Height + 2);
Image2.Picture.Bitmap.SetSize(Image2.Width, Image2.Height);
Image2.Picture.Bitmap.Canvas.Draw(0, 0, FTopBmp);
end;
destructor TClip_View.Destroy;
begin
FTopBmp.Free;
inherited;
end;
procedure TClip_View.SetTransparency(Value: Byte);
var
Bmp: TBitmap;
R: TRect;
X, Y: Integer;
Pixel: PRGBQuad;
BlendFunction: TBlendFunction;
begin
if Value <> FTransparency then begin
FTransparency := Value;
R := Image2.BoundsRect;
OffsetRect(R, Panel1.Left, + Panel1.Top);
Image2.Picture.Bitmap.Canvas.CopyRect(Image2.ClientRect,
Image1.Picture.Bitmap.Canvas, R);
Bmp := TBitmap.Create;
Bmp.SetSize(FTopBmp.Width, FTopBmp.Height);
Bmp.PixelFormat := pf32bit;
Bmp.Assign(FTopBmp);
try
for Y := 0 to Bmp.Height - 1 do begin
Pixel := Bmp.ScanLine[Y];
for X := 0 to Bmp.Width - 1 do begin
if (Pixel.rgbBlue <> 0) and (Pixel.rgbGreen <> 0) and
(Pixel.rgbRed <> 0) then begin
Pixel.rgbBlue := MulDiv(Pixel.rgbBlue, Value, $FF);
Pixel.rgbGreen := MulDiv(Pixel.rgbGreen, Value, $FF);
Pixel.rgbRed := MulDiv(Pixel.rgbRed, Value, $FF);
Pixel.rgbReserved := Value;
end else // don't touch black pixels
Pixel.rgbReserved := $FF;
Inc(Pixel);
end;
end;
BlendFunction.BlendOp := AC_SRC_OVER;
BlendFunction.BlendFlags := 0;
BlendFunction.SourceConstantAlpha := 255;
BlendFunction.AlphaFormat := AC_SRC_ALPHA;
AlphaBlend(Image2.Picture.Bitmap.Canvas.Handle,
0, 0, Image2.Picture.Bitmap.Width, Image2.Picture.Bitmap.Height,
Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height,
BlendFunction);
finally
Bmp.Free;
end;
end;
end;
end.
At launch time:
Apply transparency:
Hide the frame and use Frame.PaintTo. For example, as follows:
unit Unit1;
interface
uses
Windows, Classes, Graphics, Controls, Forms, Unit2, JPEG, ExtCtrls;
type
TForm1 = class(TForm)
Image1: TImage; //Align = alClient, Visible = False
Frame21: TFrame2; //Visible = False
procedure FormPaint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
private
FBlendFunc: TBlendFunction;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.FormPaint(Sender: TObject);
var
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
try
Bmp.Width := Frame21.Width;
Bmp.Height := Frame21.Height;
Frame21.PaintTo(Bmp.Canvas, 0, 0);
Canvas.StretchDraw(ClientRect, Image1.Picture.Graphic);
with Frame21 do
Windows.AlphaBlend(Canvas.Handle, Left, Top, Left + Width, Top + Height,
Bmp.Canvas.Handle, 0, 0, Width, Height, FBlendFunc);
finally
Bmp.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FBlendFunc.BlendOp := AC_SRC_OVER;
FBlendFunc.BlendFlags := 0;
FBlendFunc.SourceConstantAlpha := 255 div 2;
FBlendFunc.AlphaFormat := 0;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
Invalidate;
end;
end.
The frame unit:
unit Unit2;
interface
uses
Windows, Classes, Controls, Forms, JPEG, ExtCtrls;
type
TFrame2 = class(TFrame)
Image1: TImage; //Align = alClient
Panel1: TPanel; //Align = alClient, BevelWidth = 5
end;
implementation
{$R *.dfm}
end.
Result:
Rewrite the above for your specific situation, ideally painting on a TPaintBox getting rid of the image component on the main form. But when the only significant element of the frame is the image, then I would stop using that too, and begin painting everything myself.
I would use a TPaintBox instead. In its OnPaint event, draw your grid first, then alpha-blend your roll image on top. No need to use any TImage, TPanel, or TFrame components at all.
I am working with delphi. I have TImage, to which I assign a bitmap.
imgmain.Picture.Bitmap := bmpMain;
imgmain.Picture.Bitmap.PixelFormat := pf24bit;
imgmain is object of TImage and bmpMain is object of TBitmap
I want to zoom my image. I have one trackbar on my form and as I click on trackbar the image should get zoom. What should I do?
Thank You.
Edit :
I found some solution at here It works but it cut my image.
The code you refer to sets up a transformation from one coordinate space to another, I didn't notice anything that would cut/crop your image there. However, instead of having an inversely proportional zoom factor I'd rather have, easy to understand, linear scaling. Also, I see no reason switching map modes depending on the scaling factor, I would modify the SetCanvasZoomFactor like this;
procedure SetCanvasZoomPercent(Canvas: TCanvas; AZoomPercent: Integer);
begin
SetMapMode(Canvas.Handle, MM_ISOTROPIC);
SetWindowExtEx(Canvas.Handle, 100, 100, nil);
SetViewportExtEx(Canvas.Handle, AZoomPercent, AZoomPercent, nil);
end;
A simplified (no error checking) working example with a bitmap loaded to a TImage, scaled via a TrackBar could be like the below. Note that the above function is inlined in the TrackBar's OnChange event.
type
TForm1 = class(TForm)
imgmain: TImage;
TrackBar1: TTrackBar;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure TrackBar1Change(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
bmpmain: TBitmap;
[..]
[...]
procedure TForm1.FormCreate(Sender: TObject);
begin
bmpmain := TBitmap.Create;
bmpmain.LoadFromFile(ExtractFilePath('samplebitmap.bmp');
bmpmain.PixelFormat := pf32bit; // No significance, just seems faster here than pf24bit
TrackBar1.Min := 10;
TrackBar1.Max := 200;
TrackBar1.Frequency := 10;
TrackBar1.PageSize := 10;
TrackBar1.Position := 100; // Fires OnChange
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
bmpmain.Free;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
var
Zoom, x, y: Integer;
begin
Zoom := TrackBar1.Position;
if not (Visible or (Zoom = 100)) or (Zoom = 0) then
Exit;
SetMapMode(imgmain.Canvas.Handle, MM_ISOTROPIC);
SetWindowExtEx(imgmain.Canvas.Handle, 100, 100, nil);
SetViewportExtEx(imgmain.Canvas.Handle, Zoom, Zoom, nil);
x := imgmain.Width * 50 div Zoom - bmpmain.Width div 2;
y := imgmain.Height * 50 div Zoom - bmpmain.Height div 2;
imgmain.Canvas.Draw(x, y, bmpmain);
if (x > 0) or (y > 0) then begin
imgmain.Canvas.Brush.Color := clWhite;
ExcludeClipRect(imgmain.Canvas.Handle, x, y, x + bmpmain.Width, y + bmpmain.Height);
imgmain.Canvas.FillRect(imgmain.Canvas.ClipRect);
end;
Label1.Caption := 'Zoom: ' + IntToStr(TrackBar1.Position) + '%';
end;
edit: same code with a TImage in a ScrollBox;
type
TForm1 = class(TForm)
TrackBar1: TTrackBar;
Label1: TLabel;
ScrollBox1: TScrollBox;
imgmain: TImage;
procedure FormCreate(Sender: TObject);
procedure TrackBar1Change(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
bmpmain: TBitmap;
[...]
[...]
const
FULLSCALE = 100;
procedure TForm1.FormCreate(Sender: TObject);
begin
imgmain.Left := 0;
imgmain.Top := 0;
bmpmain := TBitmap.Create;
bmpmain.LoadFromFile(ExtractFilePath(Application.ExeName) + '610x.bmp');
bmpmain.PixelFormat := pf32bit;
TrackBar1.Min := FULLSCALE div 10; // %10
TrackBar1.Max := FULLSCALE * 2; // %200
TrackBar1.PageSize := (TrackBar1.Max - TrackBar1.Min) div 19;
TrackBar1.Frequency := TrackBar1.PageSize;
TrackBar1.Position := FULLSCALE;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
bmpmain.Free;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
var
Zoom: Integer;
begin
Zoom := TrackBar1.Position;
if not (Visible or (Zoom = FULLSCALE)) or (Zoom = 0) then
Exit;
SetMapMode(imgmain.Canvas.Handle, MM_ISOTROPIC);
SetWindowExtEx(imgmain.Canvas.Handle, FULLSCALE, FULLSCALE, nil);
SetViewportExtEx(imgmain.Canvas.Handle, Zoom, Zoom, nil);
imgmain.Width := Round(bmpmain.Width * Zoom / FULLSCALE);
imgmain.Height := Round(bmpmain.Height * Zoom / FULLSCALE);
if Assigned(imgmain.Picture.Graphic) then begin
imgmain.Picture.Graphic.Width := imgmain.Width;
imgmain.Picture.Graphic.Height := imgmain.Height;
end;
imgmain.Canvas.Draw(0, 0, bmpmain);
Label1.Caption := 'Zoom: ' +
IntToStr(Round(TrackBar1.Position / FULLSCALE * 100)) + '%';
end;