Reputation: 294
With code below it is possible to draw rectangles using the mouse. Each rectangle is stored in a TQueue
(list) which not can exceed 2 elements (this value can be customized). My goal with these two areas drawn is that the first can be cut and the second can not, where the final result looks like this:
How can I achieve this? The cut procedure must happen after both areas are drawn. All that I made until now was the inverse procedure (I think). Follow the code:
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 FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FSelecting: Boolean;
FSelection: TRect;
Region, Region2: hrgn;
pos1, pos2, pos3, pos4: Integer;
FRectangles: TQueue<TRect>;
public
{ Public declarations }
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);
var
I: Integer;
begin
FSelecting := false;
FSelection.Right := X;
FSelection.Bottom := Y;
Invalidate;
FSelection.NormalizeRect;
if not FSelection.IsEmpty then
begin
pos1 := FSelection.Left;
pos2 := FSelection.Top;
pos3 := X;
pos4 := Y;
FRectangles.Enqueue(FSelection);
if FRectangles.Count > MAXRECTANGLECOUNT then
FRectangles.Dequeue;
for I := 0 to FRectangles.Count - 1 do
begin
if I = 1 then
begin
Region := CreaterectRgn(0, 0, Width, Height);
Region2 := CreaterectRgn(pos1, pos2, pos3, pos4);
CombineRgn(Region, Region, Region2, RGN_DIFF);
SetWindowRgn(Handle, Region, True);
end;
end;
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;
Upvotes: 0
Views: 167
Reputation: 54802
All you need to do is to combine a third region to your combined region in a mode that would produce the outcome you require. Possible modes are explained in the function's documentation.
The below example is an accordingly modified version of the OnMouseUp
event handler. It assumes the larger rectangle is drawn first. Modifications include accounting for the caption and borders for the drawn rectangles (because mouse up handler provides client coordinates but SetWindowRegion
requires a region with window coordinates) and deleting the regions when they are not required any more.
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
Pt: TPoint;
I: 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
begin
Region := CreateRectRgn(0, 0, Width, Height);
Region2 := CreateRectRgnIndirect(FRectangles.Dequeue);
// offset region to account for caption and borders
Pt := ClientOrigin;
OffsetRgn(Region2, Pt.X - Left, Pt.Y - Top);
CombineRgn(Region, Region, Region2, RGN_DIFF);
DeleteObject(Region2);
Region2 := CreateRectRgnIndirect(FRectangles.Dequeue);
// offset region to account for caption and borders
OffsetRgn(Region2, Pt.X - Left, Pt.Y - Top);
CombineRgn(Region, Region, Region2, RGN_OR);
DeleteObject(Region2);
SetWindowRgn(Handle, Region, True);
DeleteObject(Region);
end;
end;
end;
.. and getting rid of the unused integer variables (pos1 .. pos4).
After the window region is set, two used rectangles are dequeued from the rectangles list. Since there were two, it is now empty.
Upvotes: 2