Draw Rectangle on transparent form is very slow - delphi

AlphaBlend and DoubleBuffered set to True. I have Duo Core 2Ghz and drawing takes 100% CPU, even i had once BSOD. My goal is selecting custom area on screen and get coordinates. Enabling/disablin Windows Aero doesnt speed up. Thanks for help.
unit ZaznaczenieObszaru;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;
type
TForm3 = class(TForm)
Image1: TImage;
procedure FormCreate(Sender: TObject);
procedure Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure FormKeyPress(Sender: TObject; var Key: Char);
private
public
{ Public declarations }
end;
var
Form3: TForm3;
rysuj: boolean;
poczatekX, poczatekY, xGlobalne, yGlobalne: Integer;
kolorIzy: TColor;
implementation
{$R *.dfm}
procedure TForm3.FormCreate(Sender: TObject);
begin
Width := Screen.Width;
Height := Screen.Height;
rysuj := False;
kolorIzy := 14413224;
end;
procedure TForm3.FormKeyPress(Sender: TObject; var Key: Char);
begin
if (Key = #13) then Close;
end;
procedure TForm3.Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
Image1.Picture.Bitmap := nil;
Image1.Canvas.Brush.Color := kolorIzy;
poczatekX := X;
poczatekY := Y;
rysuj := true;
end;
procedure TForm3.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if rysuj = true then
begin
xGlobalne := X;
yGlobalne := Y;
Image1.Picture.Bitmap := nil;
Image1.Canvas.Brush.Color := kolorIzy;
Image1.Canvas.Rectangle(poczatekX, poczatekY, xGlobalne, yGlobalne)
end;
end;
procedure TForm3.Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
rysuj := False;
end;
end.

You could use a TPaintBox instead of TImage (It's Canvas is not meant for such usage as #Ken already commented) and draw your rectangle on the TPaintBox.OnPaint event. don't draw on Mouse events, but rather use TPaintBox.Invalidate. Here is an example:
procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
poczatekX := X;
poczatekY := Y;
rysuj := True;
end;
procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if rysuj then
begin
xGlobalne := X;
yGlobalne := Y;
PaintBox1.Invalidate;
end;
end;
procedure TForm1.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
rysuj := False;
PaintBox1.Invalidate;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
if rysuj then
begin
PaintBox1.Canvas.Brush.Color := kolorIzy;
PaintBox1.Canvas.Rectangle(poczatekX, poczatekY, xGlobalne, yGlobalne);
end;
end;
I guess the same could be applied using only the TForm itself without any graphic controls, by drawing to the Form's canvas directly (same events as with the TPaintBox).

Try my NLDXPSelection component which is a non-visual component that provides drawing blue alpha blended selections on any control, form or screen. It has properties OnResize and OnFinish that tell the selection coördinates.
Source can be found here (open source).

Related

Rectangle drawn always is erased when the next is created

In the code below I would like the previously drawn rectangle to not be erased when the next rectangle is drawn. How achieve this?
type
TForm1 = class(TForm)
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 }
FSelecting: Boolean;
FSelection: TRect;
pos1, pos2, pos3, pos4: Integer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
FSelection.Left := X;
FSelection.Top := Y;
FSelecting := true;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if FSelecting then
begin
FSelection.Right := X;
FSelection.Bottom := Y;
Invalidate;
end;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
FSelecting := false;
FSelection.Right := X;
FSelection.Bottom := Y;
Invalidate;
FSelection.NormalizeRect;
if FSelection.IsEmpty then
else
begin
pos1 := FSelection.Left;
pos2 := FSelection.Top;
pos3 := X;
pos4 := Y;
end;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
Canvas.Brush.Style := bsClear;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Color := clRed;
Canvas.Rectangle(FSelection);
end;
When the form's client area is invalidated the entire surface is marked for redrawing. The next time OnPaint is called, what is painted is what is in the event handler. You draw one rectangle and so you see one.
You need to accumulate the information related to the rectangles you need to draw. Then in the paint handler, you can refer to the information and draw them all.
Below example is the slightly modified version of the code in the question. It substitutes a TQueue of rectangles in the place of unused integer variables (pos1, pos2..). A rectangle is queued and any excess rectangle is dequeued when mouse the button is released. Maximum number of recalled rectangles is defined by a constant. The paint handler enumerates the queue to draw the rectangles.
uses
..., generics.collections;
type
TForm1 = class(TForm)
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 FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FSelecting: Boolean;
FSelection: TRect;
FRectangles: TQueue<TRect>;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const
MAXRECTANGLECOUNT = 2;
procedure TForm1.FormCreate(Sender: TObject);
begin
FRectangles := TQueue<TRect>.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FRectangles.Free;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
FSelection.Left := X;
FSelection.Top := Y;
FSelecting := true;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if FSelecting then
begin
FSelection.Right := X;
FSelection.Bottom := Y;
Invalidate;
end;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
FSelecting := false;
FSelection.Right := X;
FSelection.Bottom := Y;
Invalidate;
FSelection.NormalizeRect;
if not FSelection.IsEmpty then
begin
FRectangles.Enqueue(FSelection);
if FRectangles.Count > MAXRECTANGLECOUNT then
FRectangles.Dequeue;
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(FSelection);
for R in FRectangles do
Canvas.Rectangle(R);
end;
end.

How to "catch" onMouseWheel-Event inside a custom component [duplicate]

This question already has answers here:
How to add mouse wheel support to a component descended from TGraphicControl?
(6 answers)
Closed 3 years ago.
I am quite new to Delphi and wanted to practise a little bit.
While trying to implement a basic custom component I couldn't figure out how to "catch" events like "OnMouseWheel" or "OnMouseMove" etc..
(the component just should let the user zoom into an TImage)
At the moment I wrote some public functions like LMouseWheel(...), now the user of the component has to implement the OnMouseWheel-Function, but only has to call the public MouseWheel(...)-Method to get the component working. Is there a way, that the MouseWheel-Method gets called by default?
The code is an abstract of my custom component. What do I have to do, to immediately call the LMouseWheel(...)-Method, when the user scrolls the mouse wheel over my component?
unit TLZoomage;
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
{$IFDEF MSWINDOWS}
uses
Classes, SysUtils, FileUtil, Forms, LCLType, Controls, Graphics,
Dialogs, ExtCtrls, Spin, Types, Math;
type
{ TLZoomage }
TLZoomage = class(TImage)
private
{ Private-Deklarationen }
FStartZoom: integer;
FmaxZoom: integer;
FminZoom: integer;
FcurrentZoom: integer;
FzoomSpeed: integer;
mouseMoveOrigin: TPoint;
procedure setCurrentZoom(AValue: integer);
procedure setMaxZoom(AValue: integer);
procedure setMinZoom(AValue: integer);
procedure setStartZoom(AValue: integer);
protected
{ Protected-Deklarationen }
property currentZoom: integer read FcurrentZoom write setCurrentZoom;
public
{ Public-Deklarationen }
constructor Create(AOwner: TComponent); override;
//###################################################################
//###################################################################
//
// This should get called automatically
//
//###################################################################
//###################################################################
procedure LMouseWheel(Sender: TObject; Shift: TShiftState;
WheelDelta: integer; MousePos: TPoint; var Handled: boolean);
published
property maxZoom: integer read FmaxZoom write setMaxZoom;
property minZoom: integer read FminZoom write setMinZoom;
property startZoom: integer read FStartZoom write setStartZoom;
property zoomSpeed: integer read FzoomSpeed write FzoomSpeed;
end;
{$ENDIF}
procedure Register;
implementation
{$IFnDEF MSWINDOWS}
procedure Register;
begin
end;
{$ELSE}
procedure Register;
begin
RegisterComponents('test', [TLZoomage]);
end;
{ TLZoomage }
//###################################################################
//###################################################################
//
// This should get called automatically
//
//###################################################################
//###################################################################
procedure TLZoomage.LMouseWheel(Sender: TObject; Shift: TShiftState;
WheelDelta: integer; MousePos: TPoint; var Handled: boolean);
var
xZoomPoint: double;
yZoomPoint: double;
begin
if (ssCtrl in Shift) then
begin
xZoomPoint := MousePos.x / self.Width;
yZoomPoint := MousePos.y / self.Height;
// der Benutzer möchte zoomen
currentZoom := currentZoom + Sign(WheelDelta) * scrollSpeed;
self.Left := round(self.Left + MousePos.x - (xZoomPoint * self.Width));
self.Top := round(self.Top + MousePos.y - (yZoomPoint * self.Height));
end;
Handled:=true;
end;
procedure TLZoomage.setCurrentZoom(AValue: integer);
var
ChildScaleFactor: double;
ParentScaleFactor: double;
begin
FcurrentZoom := AValue;
if (FcurrentZoom < minZoom) then
FcurrentZoom := minZoom;
if (FcurrentZoom > maxZoom) then
FcurrentZoom := maxZoom;
if Assigned(self.Picture) then
begin
self.Width := round(self.Picture.Width * FcurrentZoom / 100);
self.Height := round(self.Picture.Height * FcurrentZoom / 100);
if Assigned(self.Parent) then
begin
if (self.Width < self.Parent.Width) and (self.Height < self.Parent.Height) and
(self.Height <> 0) then
begin
ChildScaleFactor := self.Width / self.Height;
ParentScaleFactor := self.Parent.Width / self.Parent.Height;
// Parent ist breiter -> Höhe gibt die größe vor
if (ParentScaleFactor > ChildScaleFactor) then
begin
self.Height := self.Parent.Height;
self.Width := round(ChildScaleFactor * self.Parent.Height);
end
else
// Parent ist höher -> Breite gibt die Größe vor
begin
self.Width := self.Parent.Width;
self.Height := round(self.Parent.Width / ChildScaleFactor);
end;
end;
end;
end;
end;
procedure TLZoomage.setMaxZoom(AValue: integer);
begin
FmaxZoom := AValue;
currentZoom := currentZoom;
end;
procedure TLZoomage.setMinZoom(AValue: integer);
begin
FminZoom := AValue;
currentZoom := currentZoom;
end;
procedure TLZoomage.setStartZoom(AValue: integer);
begin
currentZoom := AValue;
FstartZoom := currentZoom;
end;
procedure TLZoomage.limitImgPos();
begin
if Assigned(self.Parent) then
begin
// limit the Scrolling
if self.Left > 0 then
self.Left := 0;
if self.Left < -(self.Width - self.Parent.Width) then
self.Left := -(self.Width - self.Parent.Width);
if self.Top > 0 then
self.Top := 0;
if self.Top < -(self.Height - self.Parent.Height) then
self.Top := -(self.Height - self.Parent.Height);
end;
end;
constructor TLZoomage.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
maxZoom := 200;
minZoom := 10;
startZoom := 100;
FzoomSpeed := 10;
currentZoom := startZoom;
end;
{$ENDIF}
end.
Solution:
The simplest solution was, to override the following procedure / functions out of TControl, thanks to "Remy Lebeau":
function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; override;
procedure MouseDown(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override;
procedure MouseMove(Shift: TShiftState; X,Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override;
Delphi's VCL TControl has virtual DoMouseWheel(Down|Up)() and Mouse(Down|Move|Up)() methods that your component can override as needed:
function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; dynamic;
function DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean; dynamic;
function DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean; dynamic;
...
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); dynamic;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;
Delphi's FMX TControl has virtual Mouse(Down|Move|Up|Wheel)() methods:
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); virtual;
procedure MouseMove(Shift: TShiftState; X, Y: Single); virtual;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); virtual;
procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); virtual;
FreePascal's TControl has virtual Mouse(Down|Move|Up)() and DoMouseWheel(Down|Up)() methods that mirror VCL, as well as additional virtual DoMouseWheel(Horz|Left|Right) methods:
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); virtual;
procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); virtual;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); virtual;
...
function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelHorz(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelLeft(Shift: TShiftState; MousePos: TPoint): Boolean; virtual;
function DoMouseWheelRight(Shift: TShiftState; MousePos: TPoint): Boolean; virtual;
In all cases, the framework handles catching the actual mouse events from the OS and then calls the per-component virtual methods automatically as needed. This works even for graphical controls, as a parent windowed control will detect mouse activity over a graphical child control and delegate accordingly.
UPDATE: in the case of Delphi's VCL TControl (not sure about Delphi's FMX TControl, or FreePascal's TControl), delegation of mouse clicks works as expected, but delegation of mouse wheel movements does not. You have to take some extra steps to receive mouse wheel notifications in a graphical control:
How to add mouse wheel support to a component descended from TGraphicControl?

How to draw a selection rectangle between OnMouseDown and OnMouseUp? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I have code that lets the user select a rectangle on an image that will become a hole in the form. But before I create this hole from combined regions, I want to mark this rectangle area with a red color.
So in this picture, an area with the size of the small rectangle should be drawn red during dragging with the mouse:
My code till so far is:
private
{ Private declarations }
Point1, Point2: TPoint;
function ClientToWindow(const p: TPoint): TPoint;
procedure AdjustRegions;
function TForm1.ClientToWindow(const p: TPoint): TPoint;
begin
Result := ClientToScreen(p);
Result.X := Result.X - Left;
Result.Y := Result.Y - Top;
end;
procedure TForm1.AdjustRegions;
var
rForm, rWindow: hrgn;
headerHeight: Integer;
begin
if ((Point2.X - Point1.X) <= 0) or ((Point2.Y - Point1.Y) <= 0) then
SetWindowRgn(Self.Handle, 0, True)
else
begin
rForm := CreateRectRgn(0, 0, Width, Height);
rWindow := CreateRectRgn(
ClientToWindow(Point1).X,
ClientToWindow(Point1).Y,
ClientToWindow(Point2).X,
ClientToWindow(Point2).Y);
CombineRgn(rForm, rForm, rWindow, RGN_DIFF);
SetWindowRgn(Self.Handle, rForm, True);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FillChar(Point1, SizeOf(Point1), 0);
FillChar(Point2, SizeOf(Point2), 0);
end;
procedure TForm1.FormResize(Sender: TObject);
begin
AdjustRegions;
end;
procedure TForm1.img1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Point1.X := X;
Point1.Y := Y;
end;
procedure TForm1.img1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if X > Point1.X then
Point2.X := X
else
begin
Point2.X := Point1.X;
Point1.X := X;
end;
if Y > Point1.Y then
Point2.Y := Y
else
begin
Point2.Y := Point1.Y;
Point1.Y := Y;
end;
AdjustRegions;
end;
Any suggestions are welcome.
You can update the canvas in the OnMouseMove event.
This could look like:
unit Unit1;
interface
uses
Winapi.Windows, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms,
Vcl.ExtCtrls;
type
TForm1 = class(TForm)
PaintBox1: TPaintBox;
procedure FormResize(Sender: TObject);
procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure PaintBox1Paint(Sender: TObject);
private
FSelecting: Boolean;
FSelection: TRect;
procedure AdjustFormRegion;
function ClientToWindow(const P: TPoint): TPoint;
end;
implementation
{$R *.dfm}
procedure TForm1.AdjustFormRegion;
var
FormRegion: HRGN;
HoleRegion: HRGN;
begin
FSelection.NormalizeRect;
if FSelection.IsEmpty then
SetWindowRgn(Handle, 0, True)
else
begin
FormRegion := CreateRectRgn(0, 0, Width, Height);
HoleRegion := CreateRectRgn(
ClientToWindow(FSelection.TopLeft).X,
ClientToWindow(FSelection.TopLeft).Y,
ClientToWindow(FSelection.BottomRight).X,
ClientToWindow(FSelection.BottomRight).Y);
CombineRgn(FormRegion, FormRegion, HoleRegion, RGN_DIFF);
SetWindowRgn(Handle, FormRegion, True);
end;
end;
function TForm1.ClientToWindow(const P: TPoint): TPoint;
begin
Result := PaintBox1.ClientToScreen(P);
Dec(Result.X, Left);
Dec(Result.Y, Top);
end;
procedure TForm1.FormResize(Sender: TObject);
begin
AdjustFormRegion;
end;
procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
FSelection.Left := X;
FSelection.Top := Y;
FSelecting := True;
end;
procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if FSelecting then
begin
FSelection.Right := X;
FSelection.Bottom := Y;
PaintBox1.Invalidate;
end;
end;
procedure TForm1.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
FSelecting := False;
FSelection.Right := X;
FSelection.Bottom := Y;
PaintBox1.Invalidate;
AdjustFormRegion;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
PaintBox1.Canvas.Brush.Color := clRed;
PaintBox1.Canvas.Rectangle(FSelection);
end;
end.
Some general remarks:
You do not have to initialize a TPoint to zero, this will be done automatically. See the documentation:
Because a constructor always clears the storage it allocates for a new object, all fields start with a value of zero (ordinal types), nil (pointer and class types), empty (string types), or Unassigned (variants). Hence there is no need to initialize fields in a constructor's implementation except to nonzero or nonempty values.
Although an Image component can be used for custom drawing, it is designed for showing pictures. I suggest you change it into a PaintBox (or the Form itself).
Since you use Delphi XE5, make use of its TRect members like NormalizeRect, IsEmpty, etc...

How to draw a line using mouse drag?

I need to draw a line in delphi using the cursor, I already have created the line code, but I can't get what to do next? How can do that, I push the mouse, when the line needs to start and drag my mouse or simple I release mouse button and I draw the line.
procedure TForm1.Button1Click(Sender: TObject);
var
x0, y0, x1, y1: Integer;
begin
x0 := StrToInt(Edit1.Text);
y0 := StrToInt(Edit2.Text);
x1 := StrToInt(Edit3.Text);
y1 := StrToInt(Edit4.Text);
Brezenhems(x0 , Y0 , X1 , Y1);
end;
I hope that someone helps me
Thanks
Something like this:
unit Unit4;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm4 = class(TForm)
procedure FormResize(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
FStartPoint, FEndPoint: TPoint;
FDrawingLine: boolean;
bm: TBitmap;
procedure AddLineToCanvas;
procedure SwapBuffers;
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.FormCreate(Sender: TObject);
begin
bm := TBitmap.Create;
FDrawingLine := false;
end;
procedure TForm4.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
FStartPoint := Point(X, Y);
FDrawingLine := true;
end;
procedure TForm4.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if FDrawingLine then
begin
SwapBuffers;
Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
Canvas.LineTo(X, Y);
end;
end;
procedure TForm4.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
FDrawingLine := false;
FEndPoint := Point(X, Y);
AddLineToCanvas;
SwapBuffers;
end;
procedure TForm4.AddLineToCanvas;
begin
bm.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
bm.Canvas.LineTo(FEndPoint.X, FEndPoint.Y);
end;
procedure TForm4.FormPaint(Sender: TObject);
begin
SwapBuffers;
end;
procedure TForm4.SwapBuffers;
begin
BitBlt(Canvas.Handle, 0, 0, ClientWidth, ClientHeight,
bm.Canvas.Handle, 0, 0, SRCCOPY);
end;
procedure TForm4.FormResize(Sender: TObject);
begin
bm.SetSize(ClientWidth, ClientHeight);
end;
end.
Compiled sample EXE
Notice that this method is simple and robust, but not optimal in terms of performance. This will likely be an issue if you try to run this on a Windows 3.1-era computer.
Another technique that can be used, without the need to create a bitmap, is using the Pen.Mode property. Something like this:
TForm2 = class(TForm)
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);
private
PO,LP: TPoint;
draw: boolean;
public
{ Public declarations }
end;
procedure TForm2.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
PO.X:= X;
PO.Y:= Y;
LP.X:= X;
LP.Y:= Y;
draw:= true;
Canvas.Pen.Mode:= pmNotXor;
end;
procedure TForm2.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if draw then
begin
if (LP.X <> PO.X) or (LP.Y <> PO.Y) then
begin
Canvas.MoveTo(PO.X,PO.Y);
Canvas.LineTo(LP.X,LP.Y);
end;
LP.X:= X;
LP.Y:= Y;
Canvas.MoveTo(PO.X,PO.Y);
Canvas.LineTo(LP.X,LP.Y);
end;
end;
procedure TForm2.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if draw then draw:= false;
end;

FireMonkey PenMode equivalent - DrawLine

I am playing around with FireMonkey simply to test out a couple of things. One of which is implement "very simple" drawing on a canvas. eg Line, Rectangle etc...
First question is, is there an equivalent of the graphex demo supplied for VCL for FireMonkey?
Otherwise, for the purposes of the exercise, I'm trying to replicate that demo in FireMonkey and just now, the line drawing. I can get the line drawing working in so much as when I move the mouse around the line draws where expected. Unfortunately I can't get it to automatically erase the old line that was drawn at the previous point where the mouse was. This seems to be taken care of by the TPenMode property of the TPen property which is - so much as I can tell - a TStroke property in FireMonkey. ie setting the property to pmXor while drawing (moving the mouse) and then setting it to pmCopy when complete.
How would I do something similar with FireMonkey?
Here's the routine that's called during a MouseMove event of a TImage:
FDrawSurface.Bitmap.Canvas.BeginScene;
try
case FShapeToDraw of
doLine:
begin
FDrawSurface.Bitmap.Canvas.DrawLine(PointF(TopLeft.X, TopLeft.Y), PointF(BottomRight.X, BottomRight.Y), 100);
end;
end;
finally
FDrawSurface.Bitmap.Canvas.EndScene;
FDrawSurface.Bitmap.BitmapChanged;
end;
FDrawSurface is a TImage. TopLeft is a TPoint which contains the X and Y of where the mouse was as capture in an OnMouseDown event of the TImaeg and BottomRight is the current X and Y coords from the OnMouseMove event.
So every time I move a mouse, I get "addtional" lines on my image.
Thanks
AFAIK, there's no mode like this with FMX...
Moreover, what you draw on the canvas is not really saved (if you know how to directly save it, explain me in a comment): if you move your form outside your desktop, and bring it back, the canvas is cleaned...
So, to implement the graphex demo, you have to code it with others technics..
For example, use a TBitmap to store your real "image" and only use the canvas for the "preview"...
unit main;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Objects;
type
TfrmMain = class(TForm)
recBoard: TRectangle;
btnCopy: TButton;
Image1: TImage;
procedure recBoardMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
procedure recBoardMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
procedure recBoardMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
procedure recBoardMouseInOut(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure btnCopyClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
bmp: TBitmap;
pFrom, pTo: TPointF;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.fmx}
procedure TfrmMain.btnCopyClick(Sender: TObject);
begin
Image1.Bitmap.Assign(bmp);
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
pFrom := PointF(-1, -1);
bmp := TBitmap.Create(Round(recBoard.Width), Round(recBoard.Height));
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
bmp.Free;
end;
procedure TfrmMain.recBoardMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
if Button = TMouseButton.mbLeft then
begin
pFrom := PointF(X, Y);
pTo := PointF(X, Y);
end;
end;
procedure TfrmMain.recBoardMouseInOut(Sender: TObject);
begin
pFrom := PointF(-1, -1);
end;
procedure TfrmMain.recBoardMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
begin
if ((pFrom.X <> -1) and (pFrom.X <> -1)) then
with recBoard.Canvas do
begin
BeginScene;
if ssLeft in Shift then
begin
FillRect(RectF(0, 0, bmp.Width, bmp.Height), 0, 0, [], 255);
DrawBitmap(bmp, RectF(0, 0, bmp.Width, bmp.Height), RectF(0, 0, bmp.Width, bmp.Height), 255);
Stroke.Color := claBlue;
pTo := PointF(X, Y);
DrawEllipse(RectF(pFrom.X, pFrom.Y, pTo.X, pTo.Y), 255);
end;
EndScene;
end;
Self.Caption := Format('(%0.0f;%0.0f)', [X, Y]);
end;
procedure TfrmMain.recBoardMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
with bmp.Canvas do
begin
BeginScene;
DrawEllipse(RectF(pFrom.X, pFrom.Y, pTo.X, pTo.Y), 255);
EndScene;
end;
pFrom := PointF(-1, -1);
end;
end.
What I ended up doing - based on insight from Whiler above, was storing the bitmap's state at the start of the "draw routine" (ie on mouse down), then on MouseMove, before I render the new Line (in this example), I restore the state and then draw the new line...
procedure TFMXDrawSurface.DrawSurfaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
FOrigin := PointF(X, Y);
FMovePt := PointF(X, Y);
FPrevPt := PointF(X, Y);
FDrawing := True;
FTempDrawbitmap.Assign(FDrawSurface.Bitmap);
end;
procedure TFMXDrawSurface.DrawSurfaceMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
begin
if FDrawing then
begin
DrawShape(FOrigin, FMovePt);
FMovePt := PointF(X, Y);
DrawShape(FOrigin, FMovePt);
FPrevPt := PointF(X, Y);
end;
end;
procedure TFMXDrawSurface.DrawShape(TopLeft, BottomRight: TPointF);
var
R: TRectF;
begin
FDrawSurface.Bitmap.Canvas.BeginScene;
try
case FShapeToDraw of
doLine:
begin
// restore canvas to initial state so we don't keep old movement data around
R.TopLeft := PointF(0.0, 0.0);
R.BottomRight := PointF(FDrawSurface.Width, FDrawSurface.Height);
FDrawSurface.Bitmap.Canvas.DrawBitmap(FTempDrawBitmap, R, R, 100);
FDrawSurface.Bitmap.Canvas.RestoreState(FDrawState);
FDrawSurface.Bitmap.Canvas.DrawLine(PointF(TopLeft.X, TopLeft.Y), PointF(BottomRight.X, BottomRight.Y), 100);
end;
end;
finally
FDrawSurface.Bitmap.Canvas.EndScene;
FDrawSurface.Bitmap.BitmapChanged;
end;
end;
It works, but I don't know if it's the "right" way or not...

Resources