FLASHCODER
FLASHCODER

Reputation: 294

How preserve a determined area of form to not be cut?

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:

enter image description here

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

Answers (1)

Sertac Akyuz
Sertac Akyuz

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

Related Questions