Cant write to a form's canvas - delphi

I am creating a form on the fly and would like to draw on its canvas however I'm unable to do so. No errors are generated just no output.
Here is the simple test code
procedure TForm11.Button1Click(Sender: TObject);
var
Fm: TForm;
R: TRect;
begin
try
Fm := TForm.Create(nil);
Fm.Position := poScreenCenter;
Fm.Caption := 'Test';
Fm.Width := 600;
Fm.Height := 400;
Fm.Color := clGreen;
Fm.Canvas.Font.Color := clBlack;
Fm.Canvas.Font.Size := 12;
Fm.Canvas.Pen.Color := clBlack;
Fm.Canvas.Pen.Width := 5;
Fm.Canvas.Brush.Color := clRed;;
Fm.Canvas.Brush.Style := bsSolid;
R.Left := 10;
R.Top := 10;
R.Width := 100;
R.Height := 100;
Fm.Canvas.TextOut(200, 10, 'Hello');
Fm.Canvas.Rectangle(R);
Fm.ShowModal;
finally
Fm.Free;
end;
end;

The canvas is redrawn all the time when you click controls, move the form around. In your case you draw the image, but after that, the form is shown and it will (re)-draw itself, overwriting your drawing. In order to keep your drawing, you could draw it in the OnPaint event of the form, which is invoked everytime the form needs to repaint itself.
procedure TForm11.Button1Click(Sender: TObject);
var
Fm: TForm;
begin
Fm := TForm.Create(nil);
try
Fm.Position := poScreenCenter;
Fm.Caption := 'Test';
Fm.Width := 600;
Fm.Height := 400;
Fm.Color := clGreen;
Fm.OnPaint := MyFormPaint;
Fm.ShowModal;
finally
Fm.Free;
end;
end;
procedure TForm11.MyFormPaint(Sender: TObject);
var
Fm: TForm;
R: TRect;
begin
FM := TForm(Sender);
Fm.Canvas.Font.Color := clBlack;
Fm.Canvas.Font.Size := 12;
Fm.Canvas.Pen.Color := clBlack;
Fm.Canvas.Pen.Width := 5;
Fm.Canvas.Brush.Color := clRed;;
Fm.Canvas.Brush.Style := bsSolid;
R.Left := 10;
R.Top := 10;
R.Width := 100;
R.Height := 100;
Fm.Canvas.TextOut(200, 10, 'Hello');
Fm.Canvas.Rectangle(R);
end;
Another solution would be to draw it on the canvas of a Bitmap, assign that bitmap to a TImage's Picture property, and show the TImage on the form. Then, everytime when the form needs to repaint, it lets each control repaint itself too, and the image will redraw the bitmap, so it remains visible.
procedure TForm11.Button1Click(Sender: TObject);
var
Fm: TForm;
Img: TImage;
B: TBitmap;
R: TRect;
begin
Fm := TForm.Create(nil);
try
Fm.Position := poScreenCenter;
Fm.Caption := 'Test';
Fm.Width := 600;
Fm.Height := 400;
// Add an image. Make the form the owner. That way, it is automatically
// discarded when you free the form in the `finally` block.
Img := TImage.Create(Fm);
// Make the form the parent too, and make sure the image covers the form.
Img.Parent := Fm;
Img.Align := alClient;
// Get bitmap of the picture. This will automatically create
// a bitmap for it too, which is managed by the image.
// You just have to give it the right dimensions.
B := Img.Picture.Bitmap;
B.Width := Fm.ClientWidth;
B.Height := Fm.ClientHeight;
// The image is not transparent, so you'll have to draw the green background too.
B.Canvas.Brush.Color := clGreen;
B.Canvas.FillRect(B.Canvas.ClipRect);
// Draw on the canvas of the bitmap.
B.Canvas.Font.Color := clBlack;
B.Canvas.Font.Size := 12;
B.Canvas.Pen.Color := clBlack;
B.Canvas.Pen.Width := 5;
B.Canvas.Brush.Color := clRed;;
B.Canvas.Brush.Style := bsSolid;
R.Left := 10;
R.Top := 10;
R.Width := 100;
R.Height := 100;
B.Canvas.TextOut(200, 10, 'Hello');
B.Canvas.Rectangle(R);
Fm.ShowModal;
finally
Fm.Free;
end;
end;

Related

Simulate Align position in Delphi Custom Panel

I'm building a custom panel in Delphi XE5 and I'm having a hard time simulating a new "Gravity" property where I can combine two coordinates (like Right + Bottom) and the effect is similar to "Align" however, it does not resize the object, direction. The main problem I encountered is to simulate this behavior. My initial intention was to create a panel in memory with the same "Parent" in my custom panel and then align to the position defined in "Gravity" overwriting the "SetBounds" method. It's working, but a bit precarious, especially in "Design Time". Could someone suggest me how to more effectively simulate this alignment using VCL?
function TZPanel.GetPosition: TCustomPanel;
var
sid: TZSide;
anch: TAnchors;
panTest: TPanel;
function getGravity(al: TAlign): TRect;
var
panGravity: TPanel;
I: Integer;
begin
try
//Self.Visible := False;
panGravity:= TPanel.Create(Self);
panGravity.BevelInner := panTest.BevelInner;
panGravity.BevelOuter := panTest.BevelOuter;
panGravity.BevelWidth := panTest.BevelWidth;
panGravity.BorderWidth := panTest.BorderWidth;
panGravity.ParentBackground := True;
panGravity.SetBounds(panTest.Left, panTest.Top, panTest.Width, panTest.Height);
panGravity.Parent:= Self.Parent;
panGravity.Align := al;
Result:= panGravity.BoundsRect;
finally
panGravity.Destroy;
Self.Visible := True;
end;
end;
begin
panTest := TPanel.Create(Self);
panTest.Align := Align;
panTest.Anchors := Anchors;
panTest.BevelInner := BevelInner;
panTest.BevelOuter := BevelOuter;
panTest.BevelWidth := BevelWidth;
panTest.BorderWidth := BorderWidth;
panTest.SetBounds(Left, Top, Width, Height);
if (FGravity = []) then
begin
//
end
else
begin
panTest.Align := alCustom;
anch := [];
for sid in FGravity do
begin
case sid of
sTop:
begin
panTest.Top := getGravity(alTop).Top;
anch := anch + [akTop];
end;
sRight:
begin
panTest.Left := getGravity(alRight).Left;
anch := anch + [akRight];
end;
sBottom:
begin
panTest.Top := getGravity(alBottom).Top;
anch := anch + [akBottom];
end;
sLeft:
begin
panTest.Left := getGravity(alLeft).Left;
anch := anch + [akLeft];
end;
end;
end;
panTest.Anchors := anch;
end;
Result := panTest;
end;

Draw TPanel and TSplitter at runtime results in wrong component order

If I create multiple TPanel and TSplitter components at runtime into a TScrollBox, the order of the components is wrong. If I call drawInput() 3 times, the scrollbox contains 3 panels followed by 3 splitters instead of 1 panel followed by 1 splitter (repeated).
How can I force the correct order?
Here is a screenshot
function drawInput(owner: TWinControl): TWinControl;
var
panel: TPanel;
edit: TEdit;
splitter: TSplitter;
begin
panel := TPanel.Create(owner);
panel.Parent := owner;
panel.Align := alTop;
panel.AlignWithMargins := True;
panel.BorderWidth := 0;
panel.Color := clRed;
panel.BorderStyle := bsNone;
panel.ParentBackground := False;
panel.Ctl3D := False;
edit := TEdit.Create(panel);
edit.Parent := panel;
edit.Align := alTop;
edit.AlignWithMargins := True;
edit.Text := 'foo';
edit.Margins.Left := 5;
edit.Margins.top := 5;
edit.Margins.Bottom := 5;
edit.Margins.Right := 5;
splitter := TSplitter.Create(owner);
splitter.Parent := owner;
splitter.Align := alTop;
splitter.Beveled := True;
splitter.Height := 3;
end;
EDIT:
Here is how I call this function:
procedure TForm2.Button1Click(Sender: TObject);
var
form: TForm;
sb: TScrollBox;
begin
form := TForm.Create(Application);
sb := TScrollBox.Create(form);
sb.Parent := form;
sb.Align := alClient;
sb.Color := clBlack;
drawInput(sb);
drawInput(sb);
drawInput(sb);
drawInput(sb);
form.Width := 300;
form.Height := 700;
form.ShowModal;
end;
Position your panel + splitter then set the alignment
You can position you panel below all other components by aligning it to the client
function drawInput(owner: TWinControl): TWinControl;
var
panel: TPanel;
edit: TEdit;
splitter: TSplitter;
begin
panel := TPanel.Create(owner);
panel.Parent := owner;
///
panel.Align := alclient;
///
panel.Align := alTop;
panel.AlignWithMargins := True;
panel.BorderWidth := 0;
panel.Color := clRed;
panel.BorderStyle := bsNone;
panel.ParentBackground := False;
panel.Ctl3D := False;
splitter := TSplitter.Create(owner);
splitter.Parent := owner;
//
splitter.top := panel.top+panel.height;
//
splitter.Align := alTop;
splitter.Beveled := True;
splitter.Height := 3;
end;
Delphi's alignment logic can be hard at times. But the following works. Note the line splitter.Top := -1;
function drawInput(owner: TWinControl): TWinControl;
var
panel: TPanel;
edit: TEdit;
splitter: TSplitter;
begin
splitter := TSplitter.Create(owner);
splitter.Parent := owner;
splitter.Align := alTop;
splitter.Beveled := True;
splitter.Height := 3;
splitter.Top := -1;
panel := TPanel.Create(owner);
panel.Parent := owner;
panel.Align := alTop;
panel.AlignWithMargins := True;
panel.BorderWidth := 0;
panel.Color := clRed;
panel.BorderStyle := bsNone;
panel.ParentBackground := False;
panel.Ctl3D := False;
edit := TEdit.Create(panel);
edit.Parent := panel;
edit.Align := alTop;
edit.AlignWithMargins := True;
edit.Text := 'foo';
edit.Margins.Left := 5;
edit.Margins.top := 5;
edit.Margins.Bottom := 5;
edit.Margins.Right := 5;
end;
Here's the code that works for me on XE5. I still have to solve my problem but at least I fixed yours :)
procedure drawInput(owner: TWinControl; var t: integer);
var
panel: TPanel;
edit: TEdit;
splitter: TSplitter;
begin
panel := TPanel.Create(owner);
panel.Parent := owner;
panel.Align := alTop;
panel.AlignWithMargins := True;
panel.BorderWidth := 0;
panel.Color := clRed;
panel.BorderStyle := bsNone;
panel.ParentBackground := False;
panel.Ctl3D := False;
panel.Top := t;
t := panel.Top + panel.Height + 1;
edit := TEdit.Create(panel);
edit.Parent := panel;
edit.Align := alTop;
edit.AlignWithMargins := True;
edit.Text := 'foo';
edit.Margins.Left := 5;
edit.Margins.top := 5;
edit.Margins.Bottom := 5;
edit.Margins.Right := 5;
splitter := TSplitter.Create(owner);
splitter.Parent := owner;
splitter.Align := alTop;
splitter.Beveled := True;
splitter.Height := 3;
splitter.Top := t;
t := splitter.Top + splitter.Height + 1;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
form: TForm;
sb: TScrollBox;
t: integer;
begin
form := TForm.Create(Application);
sb := TScrollBox.Create(form);
sb.Parent := form;
sb.Align := alClient;
sb.Color := clBlack;
t := 0;
drawInput(sb, t);
drawInput(sb, t);
drawInput(sb, t);
drawInput(sb, t);
form.Width := 300;
form.Height := 700;
form.ShowModal;
end;
In one of my applications, I have a function that creates a TImage and follows it with a TSplitter with the parent and containing control being a TScrollbox (sbScroller). The function is either called by the end user (tied to a TButton OnClick event) when they select an image or when the program starts it loads a previously loaded set of images each divided by a TSplitter.
It works when run alone by itself (creating one TImage + TSplitter pairing) or when run in a continuous loop to create multiple pairings. The key element in getting it to work seems to the positioning of the TSplitter.Top property as the previous answer says:
procedure AddImage(AFilename: string);
var
Image: TImage;
begin
Image := TImage.Create(sbScroller);
with Image do
begin
Image.Parent := sbScroller;
Left := 0;
Top := 0;
Width := 150;
Height := 150;
Constraints.MinHeight := 128;
Align := alTop;
Anchors := [akLeft, akTop, akRight];
Proportional := True;
Stretch := True;
Visible := True;
end;
if sbScroller.ControlCount > 0 then
with TSplitter.Create(sbScroller) do
begin
Parent := sbScroller;
Top := Image.Top;
Align := alTop;
Color := clGray;
end;
end;

How to draw transparent text on form?

Is there a way to draw a transparent text on form that has some controls? If I use TLabel control, it would always show behind controls on the form.
You cannot use a TLabel control, since it is not a windowed control, and therefore it will be hidden by every windowed child control of the form. You could use a TStaticText, which is indeed a windowed control (a STATIC control), but it will be a bit difficult to make it truly transparent, I'd suppose.
You can use layered windows for this:
Create a new VCL project, and add a bunch of windowed controls to it.
Create a new form in the project, named splash. Set BorderStyle to bsNone, and set the font name, size, and colour to whatever you desire (e.g., Segoe UI, 42, red).
Add a public method
procedure Tsplash.UpdateSplash(const Str: string);
var
R: TRect;
P: TPoint;
S: TPoint;
bm: TBitmap;
bf: TBlendFunction;
EXSTYLE: DWORD;
x, y: integer;
pixel: PRGBQuad;
TextRed,
TextGreen,
TextBlue: byte;
begin
EXSTYLE := GetWindowLong(Handle, GWL_EXSTYLE);
SetWindowLong(Handle, GWL_EXSTYLE, EXSTYLE or WS_EX_LAYERED);
R := ClientRect;
bm := TBitmap.Create;
try
bm.PixelFormat := pf32bit;
bm.SetSize(ClientWidth, ClientHeight);
bm.Canvas.Brush.Color := clBlack;
bm.Canvas.FillRect(ClientRect);
bm.Canvas.Font.Assign(Self.Font);
bm.Canvas.Font.Color := clWhite;
DrawText(bm.Canvas.Handle, PChar(Str), Length(Str), R,
DT_SINGLELINE or DT_VCENTER or DT_CENTER or DT_WORD_ELLIPSIS);
TextRed := GetRValue(Font.Color);
TextGreen := GetGValue(Font.Color);
TextBlue := GetBValue(Font.Color);
for y := 0 to bm.Height - 1 do
begin
pixel := bm.ScanLine[y];
x := 0;
while x < bm.Width do
begin
with pixel^ do
begin
rgbReserved := (rgbRed + rgbGreen + rgbBlue) div 3;
rgbBlue := TextBlue * rgbReserved div 255;
rgbGreen := TextGreen * rgbReserved div 255;
rgbRed := TextRed * rgbReserved div 255;
end;
inc(pixel);
inc(x);
end;
end;
P := Point(0, 0);
S := Point(bm.Width, bm.Height);
bf.BlendOp := AC_SRC_OVER;
bf.BlendFlags := 0;
bf.SourceConstantAlpha := 255;
bf.AlphaFormat := AC_SRC_ALPHA;
UpdateLayeredWindow(Handle, 0, nil, #S, bm.Canvas.Handle, #P, 0, #bf,
ULW_ALPHA)
finally
bm.Free;
end;
end;
To your main form, add private methods
procedure TForm1.CreateSplash;
var
p: TPoint;
begin
splash.Visible := true;
UpdateSplash;
end;
procedure TForm1.UpdateSplash;
var
p: TPoint;
begin
if not (Assigned(splash) and splash.Visible) then Exit;
p := ClientToScreen(Point(0, 0));
splash.SetBounds(p.X, p.Y, ClientWidth, ClientHeight);
splash.UpdateSplash('Sample Text');
end;
and call UpdateSplash every time the form is moved or resized:
procedure TForm1.WMMove(var Message: TWMMove);
begin
UpdateSplash;
end;
procedure TForm4.FormResize(Sender: TObject);
begin
UpdateSplash;
end;
Finally, you can do, just to try it out,
procedure TForm1.FormClick(Sender: TObject);
begin
if splash.Visible then
splash.Hide
else
CreateSplash;
end;
Compiled demo EXE

How can I work out what values to use for TListBox.ScrollWidth?

I'm trying to work out how to set ScrollWidth on a TListBox to control the horizontal scroll bar. Here's my first attempt:
program ListBoxSizing;
uses
Math, Forms, StdCtrls;
var
Form: TForm;
ListBox: TListBox;
procedure BuildForm;
begin
//Form.Font.Size := 9;
Form.ClientWidth := 200;
Form.ClientHeight := 100;
ListBox := TListBox.Create(Form);
ListBox.Parent := Form;
ListBox.SetBounds(0, 0, Form.ClientWidth, Form.ClientHeight);
ListBox.Items.Add('ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ');
end;
procedure SetScrollWidth;
var
i, MaxWidth: Integer;
begin
MaxWidth := -1;
for i := 0 to ListBox.Items.Count-1 do
MaxWidth := Max(MaxWidth, ListBox.Canvas.TextWidth(ListBox.Items[i]));
if MaxWidth<>-1 then
ListBox.ScrollWidth := MaxWidth;
end;
begin
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm, Form);
BuildForm;
SetScrollWidth;
Application.Run;
end.
This is how the result looks with the horizontal scroll bar moved as far right as possible:
Notice how the last part of the final character has been chopped off.
Now, if we uncomment the line that changes the form's font size, it looks like this:
Now, it seems that the change to font size hasn't been accounted for in the subsequent calls to TextWidth.
So, my question is, what code do I need to use to be able to set ScrollWidth accurately, based on the current contents of the list box.
procedure SetScrollWidth;
var
I, MaxWidth: Integer;
begin
MaxWidth := -1;
// assign control's font to canvas
ListBox.Canvas.Font := ListBox.Font;
for I := 0 to ListBox.Items.Count - 1 do
MaxWidth := Max(MaxWidth, ListBox.Canvas.TextWidth(ListBox.Items[I]));
// consider non-client area
if MaxWidth <> -1 then
ListBox.ScrollWidth := MaxWidth + ListBox.Width - ListBox.ClientWidth;
end;
program Project2;
uses
Math, Forms, StdCtrls,Windows,Graphics;
var
Form: TForm;
ListBox: TListBox;
procedure BuildForm;
begin
//Form.Font.Size := 9;
Form.ClientWidth := 200;
Form.ClientHeight := 100;
ListBox := TListBox.Create(Form);
ListBox.Parent := Form;
Listbox.Font.Size := 40;
ListBox.SetBounds(0, 0, Form.ClientWidth, Form.ClientHeight);
ListBox.Items.Add('ABCDEFGXXXXXXXXXXXXOXOXYQASEOOWW');
ListBox.Items.Add('ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ');
ListBox.Items.Add('ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ111111111111111111111111111111111111111O');
end;
function GetRealFontWidth(fnt: TFont; const text:string): Integer;
var
dc: hdc;
tsize : Windows.TSize;
oldObj : Cardinal;
begin
dc := GetDC(0);
oldObj := SelectObject(DC, fnt.Handle);
GetTextExtentPoint32(dc, PChar(text), Length(text), tsize);
SelectObject(DC, oldObj);
ReleaseDC(0, DC);
Result := tsize.cx;
end;
procedure SetScrollWidth;
var
i, MaxWidth: Integer;
begin
MaxWidth := -1;
for i := 0 to ListBox.Items.Count-1 do
MaxWidth := Max(MaxWidth, GetRealFontWidth (ListBox.Font,ListBox.Items[i]));
if MaxWidth<>-1 then
ListBox.ScrollWidth := MaxWidth + 4;
end;
begin
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm, Form);
BuildForm;
SetScrollWidth;
Application.Run;
end.

Problem with creating tpaintbox on tpanel

I have a little problem. I'm trying to create a TPaintBox on a TPanel like this:
procedure TForm1.mkPaint(S: string);
var PB: TPaintBox;
begin
PB := TPaintBox.Create(Self);
with PB do
begin
Parent := Panel1;
Visible := True;
Name := S;
Height := 100;
Width := 100;
Left := 8;
Top := 8;
// ParentColor := False;
Brush.Style := bsSolid;
Brush.Color := $00000000;
end;
Application.ProcessMessages;
end;
Now, if i change the PaintBox's Parent to Form1, i can see the brush.
But, with parent changed to Panel1, nothing happens. Any idea of how can i fix this?
Thanks in advance!
Is the TPanel visible to begin with?
Also, TPaintBox does not have a public Brush property (perhaps you are thinking of TShape?). TWinControl does, but TPaintBox is not a TWinControl descendant. It is a TGraphicControl descendant.
Yeah that was a mistake of mine. I changed the code to:
pb := TPaintBox.Create(self);
with pb do begin
Parent := Form1;
Visible := true;
Top := 1;
Left := 1;
Width := 250;
Height := 100;
ParentColor := false;
Canvas.Brush.Color := clBlack;
Canvas.Font.Size := 12;
Canvas.Font.Color := clWhite;
Canvas.FillRect(ClientRect);
Canvas.TextOut(1, 1, 'test');
end;
but without success.. i mean, if i drop a PaintBox component to the form then the code is taking effect as it should do, but dynamically creating a TPaintBox.... dunno.

Resources