I have some troubles with rendering my canvas. Actually I'm trying to write game like space invaders, without using any OpenGL or DirectX. So at the background I have moving sky, and jet moving over it. But the jet is blinding, and sky moves not uniformly. Here's my code
sky := TBitmap.Create;
sky.LoadFromFile('sky.bmp');
jet := TBitmap.Create;
jet.LoadFromFile('jet.bmp');
jet.Transparent := True;
while True do
begin
for k := 0 to sky.Height do
begin
for i := -1 to (pbMain.Height div sky.Height) do
begin
for j := 0 to (pbMain.Width div sky.Width) do
begin
pbMain.Canvas.Draw(nx, ny, jet);
pbMain.Canvas.Draw(j*sky.Width, k + i*sky.Height, sky);
end;
Application.ProcessMessages;
end;
Sleep(1);
end;
end;
Thank you.
You can't write a standard Windows app like that. You have to do your painting in response to WM_PAINT messages. In Delphi terms this equates to overriding the Paint method of a TWinControl descendent, or perhaps using a TPaintBox and providing an OnPaint event handler. I'm going to assume that you use a TPaintBox.
If you need to avoid flicker it is common practice to draw to an off-screen bitmap and then show this when you are asked to paint.
Your application should probably use a timer control to provide a regular pulse. Then, on each pulse, update your off-screen bitmap. Then call Invalidate on your paint box to force a paint cycle.
The code might look like this:
procedure TMainForm.RefreshTimerTimer(Sender: TObject);
begin
RedrawOffscreenBitmap;
PaintBox.Invalidate;
end;
procedure TMainForm.RedrawOffscreenBitmap;
begin
//paint to FOffscreenBitmap
end;
procedure TMainForm.PaintBoxBox(Sender: TObject);
begin
PaintBox.Canvas.Draw(0, 0, FOffscreenBitmap);
end;
Related
My Android application is a little big, and when I launch it to my phone I need to wait 3-5 seconds.
So I decided to use TTask.
In my FormCreate event I use a TTask to load my Database to a personnal class.
When loading is finnish I load item on a TListBoxItem.
I got a problem, all background of my TImage are black, same for text and other objects.
The black rectangle with write "+2 Voir tout" is with a Opacity of 0.5, and then here it's all black.
TTask.Run(procedure
begin
Gestionnaire := TGestionnaire.Create(Cnx);
TThread.Queue(nil,
procedure
begin
ChargerHomePage;
TabControl.ActiveTab := tabHome;
aniindicator1.Enabled := false;
end);
end);
I'm attempting to write an analog VU meter. I use a bitmap of a VU meter and draw the needle on to the bitmap. I am using a trackbar and it's onChange event to test the meter:
procedure TForm1.TrackBar1Change(Sender: TObject);
var
angle : integer;
x,y : integer;
Peaked : boolean;
begin
Angle := 120 - Round(sTrackBar1.Position / sTrackBar1.Max * 100 )+20;
Peaked := Angle < PeakVol;
if Peaked then
Buffer.Picture := VUImagePeaked.Picture
else
buffer.picture := VUImage.Picture;
buffer.Picture.Bitmap.Canvas.Pen.Color := clSilver;
buffer.Picture.Bitmap.Canvas.Pen.Width:=2;
buffer.Canvas.MoveTo(pivot.x,Pivot.y);
x := 150 + Round(Cos(DegToRad(Angle)) * NeedleLen);
y := PaintBox1.Height - Round(Sin(DegToRad(Angle)) *NeedleLen);
buffer.Canvas.LineTo(x,y);
PaintBox1.Canvas.Draw(0,0,buffer.Picture.Bitmap)
end;
Seems to work but what I can't get is to display the bitmap of the meter when the program starts. I have even resorted to copying the above code to both the Form.Create and Form.Activate event handlers but no joy. I created a button and added the following code to trigger the onChange event handler for the trackbar. This works and displays the meter.
procedure TForm1.Button1Click(Sender: TObject);
begin
TrackBar1.Position := 1;
end;
When I copied this to the Form.Activate handler it doesn't. Can anyone please tell me what I'm doing wrong? I'm using Delphi Berlin starter edition. Thanks
A TPaintBox must be painted using its OnPaint event. This event is triggered every time Windows needs you to redraw the control. You can't simply draw to the control's Canvas at any time, because it will just be drawn over the next time the control is repainted.
In your particular case, you don't need to move your entire block of code to the OnPaint event handler. Instead, all you need is:
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
PaintBox1.Canvas.Draw(0,0,buffer.Picture.Bitmap)
end;
We have a win control object which moves its clients to some other coordiantes. The problem is, when there are too many children - for example 500 controls - the code is really slow.
It must be because of each control being repainted each time I set Left and Top property. So, I want to tell the WinControl object stop being repainted, and after moving all objects to their new positions, it may be painted again (Something like BeginUpdate for memo and list objects). How can I do this?
Here's the code of moving the objects; it's quite simple:
for I := 0 to Length(Objects) - 1 do begin
with Objects[I].Client do begin
Left := Left + DX;
Top := Top + DY;
end;
end;
As Cosmin Prund explains, the cause for the long duration is not an effect of repainting but of VCL's realignment requisites at control movement. (If it really should take as long as it does, then you might even need to request immediate repaints).
To temporarily prevent realignment and all checks and work for anchors, align settings and Z-order, use DisableAlign and EnableAlign. And halve the count of calls to SetBounds by called it directly:
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
Control: TControl;
begin
for I := 0 to 499 do
begin
Control := TButton.Create(Self);
Control.SetBounds((I mod 10) * 40, (I div 10) * 20, 40, 20);
Control.Parent := Panel1;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
C: TControl;
begin
// Disable Panel1 paint
SendMessage(Panel1.Handle, WM_SETREDRAW, Integer(False), 0);
Panel1.DisableAlign;
try
for I := 0 to Panel1.ControlCount - 1 do
begin
C := Panel1.Controls[I];
C.SetBounds(C.Left + 10, C.Top + 5, C.Width, C.Height);
end;
finally
Panel1.EnableAlign;
// Enable Panel1 paint
SendMessage(Panel1.Handle, WM_SETREDRAW, Integer(True), 0);
// Update client area
RedrawWindow(Panel1.Handle, nil, 0, RDW_INVALIDATE or RDW_UPDATENOW or RDW_ALLCHILDREN);
end;
end;
Your assumption that the slowness comes from re-painting controls is probably true, but not the whole story. The default Delphi code that handles moving controls would delay painting until the next WM_PAINT message is received, and that would happen when the message queue is pumped, after you complete moving all the controls. Unfortunately there are a lot of things involved in this, that default behavior can be altered in many places, including Delphi and Windows itself. I've used the following code to test what happens when you move a control at runtime:
var i: Integer;
begin
for i:=1 to 100 do
begin
Panel1.Left := Panel1.Left + 1;
Sleep(10); // Simulate slow code.
end;
end;
The behaviour depends on the control! A TControl (example: TLabel) is going to behave according to Delphi's rules, but a TWinControl depends on too many factors. A simple TPanel is not repainted until after the loop, in the case of TButton on my machine only the background is re-painted, while a TCheckBox is fully repainted. On David's machine the TButton is also fully repainted, proving this depends on many factors. In the case of TButton the most likely factor is the Windows version: I tested on Windows 8, David tested on Windows 7.
AlignControl Avalanche
Anyhow, there's an other really important factor to be taken into account. When you move a control at runtime, all the rules for alignment and anchoring for all the controls need to be taken into account. This likely causes an avalanche of AlignControls / AlignControl / UpdateAnchorRules calls. Since all those calls end up requiring recursive invocations of the same, the number of calls will be exponential (hence your observation that moving lots of objects on a TWinControl is slow).
The simplest solution is, as David suggests, placing everything on a Panel and moving the panel as one. If that's not possible, and all your controls are actually TWinControl (ie: they have a Window Handle), you could use:
BeginDeferWindowPos, DeferWindowPos, EndDeferWindowPos
I would put all the controls in a panel, and then move the panel rather than the controls. That way you perform the shift in a one single operation.
If you would rather move the controls within their container then you can use TWinControl.ScrollBy.
For what it is worth, it is more efficient to use SetBounds than to modify Left and Top in separate lines of code.
SetBounds(Left+DX, Top+DY, Width, Height);
To speed up you should set the Visible property of you WinControl to False during child movement to avoid repainting.
Together with SetBounds you will get the best from moving the child controls.
procedure TForm1.MoveControls( AWinControl : TWinControl; ADX, ADY : Integer );
var
LIdx : Integer;
begin
AWinControl.Visible := False;
try
for LIdx := 0 to Pred( AWinControl.ControlCount ) do
with AWinControl.Controls[LIdx] do
begin
SetBounds( Left + ADX, Top + ADY, Width, Height );
end;
finally
AWinControl.Visible := True;
end;
end;
BTW As David suggested, moving the parent is much faster than each child.
I have component (descendat of TPanel) where I implemented Transparency and BrushStyle (using TImage) properties.
All it's ok when I have one component of this type on the form. Bun when I pun on the form more components of this type only first visible component is painted. When form is moved and first component is under other window or outside desktop next component is painted.
unit TransparentPanel;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, stdctrls;
type
TTransparentPanel = class(TPanel)
private
FTransparent: Boolean;
FBrushStyle: TBrushStyle;
FImage: TImage;
procedure SetTransparent(const Value: Boolean);
procedure SetBrushStyle(const Value: TBrushStyle);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Transparent: Boolean read FTransparent write SetTransparent default
True;
property BrushStyle: TBrushStyle read FBrushStyle write SetBrushStyle default
bsBDiagonal;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('TransparentPanel', [TTransparentPanel]);
end;
constructor TTransparentPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FTransparent := True;
FBrushStyle := bsBDiagonal;
FImage := TImage.Create(Self);
FImage.Align := alClient;
FImage.Parent := Self;
FImage.Transparent := FTransparent;
end;
procedure TTransparentPanel.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if ((not (csDesigning in ComponentState)) and FTransparent) then
Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT;
end;
destructor TTransparentPanel.Destroy;
begin
if Assigned(FImage) then
FreeAndNil(FImage);
inherited Destroy;
end;
procedure TTransparentPanel.Paint;
var
XBitMap,
BitmapBrush: TBitmap;
XOldDC: HDC;
XRect: TRect;
ParentCanvas: TCanvas;
begin
{This panel will be transparent only in Run Time}
if (csDesigning in ComponentState) or (not FTransparent) or (FBrushStyle in [bsClear, bsSolid]) then
inherited Paint
else
begin
XRect := ClientRect;
XOldDC := Canvas.Handle;
XBitMap := TBitmap.Create;
BitmapBrush := TBitmap.Create;
try
XBitMap.Height := Height;
XBitMap.Width := Width;
Canvas.Handle := XBitMap.Canvas.Handle;
inherited Paint;
RedrawWindow(Parent.Handle, #XRect, 0,
RDW_ERASE or RDW_INVALIDATE or
RDW_NOCHILDREN or RDW_UPDATENOW);
BitmapBrush.Width := FImage.Width;
BitmapBrush.Height := FImage.Height;
BitmapBrush.Canvas.Brush.Color := clBlack;
BitmapBrush.Canvas.Brush.Style := FBrushStyle;
SetBkColor(BitmapBrush.Canvas.Handle, clWhite);
BitmapBrush.Canvas.FillRect(BitmapBrush.Canvas.ClipRect);
FImage.Canvas.Draw(0, 0, BitmapBrush);
finally
Canvas.Handle := XOldDC;
Canvas.BrushCopy(XRect, XBitMap, XRect, Color);
XBitMap.Free;
BitmapBrush.Free;
end;
end;
end;
procedure TTransparentPanel.SetBrushStyle(const Value: TBrushStyle);
begin
if (FBrushStyle <> Value) then
begin
FBrushStyle := Value;
Invalidate;
end
end;
procedure TTransparentPanel.SetTransparent(const Value: Boolean);
begin
if (FTransparent <> Value) then
begin
FTransparent := Value;
FImage.Transparent := Value;
Invalidate;
end;
end;
end.
What is wrong?
OK, a few tips:
Only one component is drawn, because during painting the client area of the control is invalidated again, so you create an infinite stream of WM_PAINT messages, and the second component never gets drawn. Until the first one is made invisible, as you describe. You can see this from the CPU load, having one of your components on a form uses 100% of one core on my system (Delphi 2007, component created at runtime).
You should try to remove the bitmap you draw into, and make use of the DoubleBuffered property instead.
What is FImage actually used for?
If you modify the create parameters depending on the value of the Transparent property, then you need to recreate the window handle when the property changes.
Maybe you can get rid of the component completely, and use a TPaintBox instead? It is transparent as long as you don't paint the background yourself. But I can't tell from your code what you actually want to achieve, so it's hard to say.
I think you want a control that can contain other controls — like TPanel can do — and a control that can display the contents of the window underneath it — like TImage can do when its Transparent property is set. It appears you are under the mistaken impression that if you put one control on top of another, you'll get the behavior of both combined. That's what's wrong.
First thing you should do is get rid of the TImage control. That's just making things more complicated than they need to be. When you need to draw a brush pattern on the panel, draw it directly onto the panel.
Next, realize that the ws_ex_Transparent window style controls whether siblings of the window are painted first. That says nothing about whether the parent of the window gets repainted. If the parent of your panel has the ws_ClipChildren style set, then it will not paint itself underneath where your panel supposedly is. It looks like it would help you if the parent of your panel control had the ws_ex_Composited style set, but as a component writer, you don't get control over your controls' parents.
TImage is able to appear transparent because it is not a windowed control. It has no window handle, so the OS rules about painting and clipping don't apply to it. From Windows' point of view, TImage doesn't exist at all. What we in the Delphi world perceive as the TImage painting itself is really the parent window deferring to a separate subroutine to paint a certain region of the parent window. Because of that, the TImage painting code can simply not paint over some of the parent's area.
If I were doing this, I'd ask myself whether the control with the brush pattern really needed to be a container control. Could I instead just use an ordinary TImage with a repeating brush pattern drawn on it? Other controls can still go on top of it, but they won't be considered children of the pattern control.
Try to look at the Graphics32 library : it's very good at drawing things and works great with Bitmaps and Transparency
If you want the panel to be transparent, all you need to do is override Paint and do nothing (or paint a transparent image, for example), and also catch the WM_ERASEBKGND message and do nothing here as well. This ensures the panel doesn't paint itself at all.
Make sure also to exclude the csOpaque flag from ControlStyle, so the parent knows it should paint itself underneath the panel.
The stuff you have in Paint is absolutely horrible, by the way (I mean the RedrawWindow thing). Get rid of it. And WS_EX_TRANSPARENT is meant for toplevel windows only, not for controls.
I'm trying to create with Delphi a component inherited from TLabel, with some custom graphics added to it on TLabel.Paint. I want the graphics to be on left side of text, so I overrode GetClientRect:
function TMyComponent.GetClientRect: TRect;
begin
result := inherited GetClientRect;
result.Left := 20;
end;
This solution has major problem I'd like to solve: It's not possible to click on the "graphics area" of the control, only label area. If the caption is empty string, it's not possible to select the component in designer by clicking it at all. Any ideas?
First excuse-me for my bad English.
I think it is not a good idea change the ClientRect of the component. This property is used for many internal methods and procedures so you can accidentally change the functionality/operation of that component.
I think that you can change the point to write the text (20 pixels in the DoDrawText procedure -for example-) and the component can respond on events in the graphic area.
procedure TGrlabel.DoDrawText(var Rect: TRect; Flags: Integer);
begin
Rect.Left := 20;
inherited;
end;
procedure TGrlabel.Paint;
begin
inherited;
Canvas.Brush.Color := clRed;
Canvas.Pen.Color := clRed;
Canvas.pen.Width := 3;
Canvas.MoveTo(5,5);
Canvas.LineTo(15,8);
end;
What methods/functionality are you getting from TLabel that you need this component to do?
Would you perhaps be better making a descendent of (say, TImage) and draw your text as part of it's paint method?
If it's really got to be a TLabel descendant (with all that this entails) then I think you'll be stuck with this design-time issue, as doesn't TLabel have this problem anyway when the caption is empty?
I'll be interested in the other answers you get! :-)