Search code examples
delphiscreen-resolutiondelphi-10-seattlerectpaintbox

How make the same hole in two Form's on remote screen (client side), based on area drawn on server side?


I have the following code and want draw the same hole in two Form's on remote screen (client side), based on area drawn on server side.

I have the same Form (Form3) in both sides (server and client) that is a "mirror" where i'm drawing a area that must stays inside this same form on client side.

The Form3 in server side have 50% of max AlphaBlend value, this is necessary to see remote screen behind Form3.


Before all, i want say that i'm receiving the remote screen on server side and and mouse click positions works like expected.

Then this is my trouble:

enter image description here

The following code produce the result showed on image above. I think that this code is right but is missing align this hole with the Form3.

Someone could help with this? sorry if this is a bad question, but this is all my actual trouble and i tried express all in this question of better way that i'm able.

This is all relevant code:

Server side:

Form2 (where i see the remote screen):

unit Unit2;

interface

uses
 Unit1;

type
  TForm2 = class(TForm)
  Panel1: TPanel;
  CheckBox1: TCheckBox;
  ScrollBox1: TScrollBox;
  Image1: TImage;
  PaintBox1: TPaintBox;
  procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState;
      X, Y: Integer);
  procedure PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  procedure PaintBox1Paint(Sender: TObject);

  private
    { Private declarations }
    FSelecting: Boolean;
    FSelection: TRect;
    pos1, pos2, pos3, pos4: Integer;
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin

  if CheckBox1.Checked then
  begin
    FSelection.Left := X;
    FSelection.Top := Y;
    FSelecting := true;
  end;

end;

procedure TForm2.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
begin

  if FSelecting then
  begin
    FSelection.Right := X;
    FSelection.Bottom := Y;
    pbRec.Invalidate;
  end;

end;

procedure TForm2.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin

  if CheckBox1.Checked then
  begin
    FSelecting := false;
    FSelection.Right := X;
    FSelection.Bottom := Y;
    PaintBox1.Invalidate;

    FSelection.NormalizeRect;
    if FSelection.IsEmpty then
    begin
      // None selection was made on PaintBox
    end
    else
    begin
      pos1 := FSelection.Left;
      pos2 := FSelection.Top;
      pos3 := X;
      pos4 := Y;
    end;
  end;

end;

procedure TForm2.PaintBox1Paint(Sender: TObject);
begin
  if CheckBox1.Checked then
  begin
    PaintBox1.Canvas.Brush.Style := bsClear;
    PaintBox1.Canvas.Pen.Style := psSolid;
    PaintBox1.Canvas.Pen.Color := clRed;
    PaintBox1.Canvas.Rectangle(FSelection);
  end;
end;

procedure TForm2.Button1Click(Sender: TObject);
var
  Socket: TCustomWinSocket;
begin
  Socket := TCustomWinSocket(Form1.LV1.Selected.SubItems.Objects[0]);
  if CheckBox1.Checked then
  begin
      Socket.SendText(intToStr(pos1) + ';' + intToStr(pos2) + ';' +
        intToStr(pos3) + ';' + intToStr(pos4));
  end;
end;

procedure TForm2.Button2Click(Sender: TObject);
begin
  Form3 := TForm3.Create(Self);
  Form3.Show;
end;

Form2 .DFM:

object Panel1: TPanel
    Left = -1
    Top = 0
    Width = 773
    Height = 89
    Anchors = [akTop]
    BevelEdges = [beLeft, beRight]
    ParentDoubleBuffered = False
    TabOrder = 0
    end

object ScrollBox1: TScrollBox
    Left = 0
    Top = 0
    Width = 765
    Height = 472
    HorzScrollBar.Smooth = True
    HorzScrollBar.Tracking = True
    VertScrollBar.Smooth = True
    VertScrollBar.Tracking = True
    Align = alClient
    TabOrder = 1
    object Image1: TImage
      Left = 0
      Top = 0
      Width = 1362
      Height = 621
      AutoSize = True
    end

object PaintBox1: TPaintBox
      Left = 0
      Top = 0
      Width = 1362
      Height = 621
      Align = alClient
      OnMouseDown = PaintBox1MouseDown
      OnMouseMove = PaintBox1MouseMove
      OnMouseUp = PaintBox1MouseUp
      OnPaint = PaintBox1Paint
      ExplicitWidth = 1364
      ExplicitHeight = 622
    end

Form3 (the "mirror" Form that also is the same on client side), this Form is centralized according of remote screen resolution:

unit Unit3;

interface

uses
 ...

type
  TForm3 = class(TForm)
    Panel1: TPanel;
    Image1: TImage;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure CreateParams(var pr: TCreateParams); override;
  public
    { Public declarations }
  end;

var
  Form3: TForm3;

implementation

uses 
 Unit1;

{$R *.dfm}

procedure TForm3.FormCreate(Sender: TObject);
var
  MyString: String;
  Splitted: TArray<String>;
begin
  MyString := Form1.LV1.Selected.SubItems[6]; // Resolution of remote screen
  Splitted := MyString.Split(['x']);

  Self.Left := (Integer(Splitted[0]) - Self.Width) div 2;
  Self.Top := (Integer(Splitted[1]) - Self.Height) div 2;
end;

procedure TForm3.CreateParams(var pr: TCreateParams);
begin
  inherited;
  pr.WndParent := Form2.Handle;
  pr.ExStyle := pr.ExStyle or WS_EX_TOPMOST or WS_EX_TRANSPARENT;
  pr.ExStyle := WS_EX_TRANSPARENT or WS_EX_TOPMOST;
end;

Form3 .DFM:

object Form3: TForm3
  Left = 328
  Top = 143
  BorderStyle = bsNone
  ClientHeight = 567
  ClientWidth = 526
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object Panel1: TPanel
    Left = 0
    Top = 0
    Width = 801
    Height = 569
    TabOrder = 0
    object Image1: TImage
      Left = 1
      Top = 1
      Width = 799
      Height = 567
      Align = alClient
      ExplicitLeft = 2
      ExplicitTop = 0
      ExplicitHeight = 447
    end

    object Label1: TLabel
      Left = 92
      Top = 69
      Width = 28
      Height = 13
      Caption = 'Nome'
      Color = clBtnFace
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clBlack
      Font.Height = -11
      Font.Name = 'MS Sans Serif'
      Font.Style = []
      ParentColor = False
      ParentFont = False
    end

Client side:

Form2 ("locker" Form):

unit Unit2;

private
    { Private declarations }
    procedure CreateParams(var Params: TCreateParams); override;

  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.WndParent := Application.Handle;
  Params.ExStyle := Params.ExStyle or WS_EX_TOPMOST or WS_EX_TRANSPARENT;
  Params.ExStyle := WS_EX_TRANSPARENT or WS_EX_TOPMOST;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  windowstate := wsmaximized;
  Top := 0;
  Left := 0;
  Height := Screen.Height;
  Width := Screen.Width;
end;

{

Properties of Form2:

Align => alNone
AlphaBlend => True
BorderStyle => BsNone

}

end.

Form3 (the same of server side):

unit Unit3;

interface

uses
 ...

type
  TForm3 = class(TForm)
    Panel1: TPanel;
    Label1: TLabel;
    procedure FormShow(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure CreateParams(var pr: TCreateParams); override;
  public
    { Public declarations }
  end;

var
  Form3: TForm3;

implementation

uses 
 Unit2;

{$R *.dfm}

procedure TForm3.FormCreate(Sender: TObject);
begin
  Self.Left := (GetSystemMetrics(SM_CXSCREEN) - Self.Width) div 2;
  Self.Top := (GetSystemMetrics(SM_CYSCREEN) - Self.Height) div 2;
end;

procedure TForm3.FormShow(Sender: TObject);
begin
  ShowWindow(Application.Handle, SW_HIDE);
end;

procedure TForm3.CreateParams(var pr: TCreateParams);
begin
  inherited;
  pr.WndParent := Form2.Handle;
end;

{

Properties of Form3:

Align => alNone
BorderStyle => BsNone

}

end.

Receiving the area on client side:

procedure CS1_Read(Self: Pointer; Sender: TObject; Socket: TCustomWinSocket);
var
 X1, X2, Y1, Y2: Integer;
 List: TStrings;
 FormRegion, HoleRegion: HRGN;
 StrCommand: string;

begin

if Pos(';', StrCommand) > 0 then
begin

    List := TStringList.Create;
    try

      ExtractStrings([';'], [], PChar(StrCommand), List);

      Form3 := TForm3.Create(Form2); // The Form2 already was created and is showing

      X1 := Round(StrToIntDef(List[0], 0) - Form2.Left);
      Y1 := Round(StrToIntDef(List[1], 0) - Form2.Top);
      X2 := Round(StrToIntDef(List[2], 0) - Form2.Left);
      Y2 := Round(StrToIntDef(List[3], 0) - Form2.Top);


      FormRegion := CreateRectRgn(0, 0, Form3.Width, Form3.Height);
      HoleRegion := CreateRectRgn(X1, Y1, X2, Y2);
      CombineRgn(FormRegion, FormRegion, HoleRegion, RGN_DIFF);
      SetWindowRgn(Form3.handle, FormRegion, true);

      FormRegion := CreateRectRgn(0, 0, Form2.Width, Form2.Height);
      HoleRegion := CreateRectRgn(X1, Y1, X2, Y2);
      CombineRgn(FormRegion, FormRegion, HoleRegion, RGN_DIFF);
      SetWindowRgn(Form2.handle, FormRegion, true);

      Form3.ShowModal;
      Form3.Release;

    finally
      List.Free;
    end;
  end;
end;

Solution

  • On the client side you have a semitransparent gray form (Form2) that has the size of the screen. On top of that form you have a opaque white form (Form3) centered on the screen. In Form3 you have a rectangular hole at Top = Y and Left = X in coordinates of Form3.

    I understand that your problem is that you want to draw a hole in Form2 that is aligned with the hole in Form3.

    You need to convert the coordinate system of Form3 to that of Form2 with a simple addition:

    Form2.Hole.Left := Form3.Left + Form3.Hole.Left;
    Form2.Hole.Top  := Form3.Top  + Form3.Hole.Top;
    

    That would align the holes. You seem to attempt something like that in your calculation, but you refer to Form2.Left and Form2.Top which is useless as they are both 0.

    If I misunderstood your question and you actually would like Form3 hole to be aligned with the Form2 hole, then you would need to move Form3 to top - left of the screen and not center it ...

    ... Or, considering your comment: if i'm drawing on server side in a area far of Form3 (client) for example more to left side of screen, draw only the hole of Form2 and if i draw more to middle of screen, draw the both hole aligned would be accomplshed by simply swapping the terms:

    Form3.Hole.Left := Form2.Hole.Left - Form3.Left
    Form3.Hole.Top  := Form2.Hole.Top  - Form3.Top
    

    This converts the Form2 coordinates to Form3 coordintaes, which may become negative values (iow outside of the form) in situations like your example.

    Adapting the above to your code, you need to first deal with Form2 region with the received Form2.Hole coordinates, then subtract Form3 coordinates (Left and Top) from X1..Y2 and then deal with Form3 region*.

      X1 := Round(StrToIntDef(List[0], 0) - Form2.Left); // Form2 props can be removed as hardcoded to 0
      Y1 := Round(StrToIntDef(List[1], 0) - Form2.Top);  // -"-
      X2 := Round(StrToIntDef(List[2], 0) - Form2.Left); // -"-
      Y2 := Round(StrToIntDef(List[3], 0) - Form2.Top);  // -"-
    
      FormRegion := CreateRectRgn(0, 0, Form2.Width, Form2.Height);
      HoleRegion := CreateRectRgn(X1, Y1, X2, Y2);
      CombineRgn(FormRegion, FormRegion, HoleRegion, RGN_DIFF);
      SetWindowRgn(Form2.handle, FormRegion, true);
    
      X1 := X1 - Form3.Left;
      Y1 := Y1 - Form3.Top;
      X2 := X2 - Form3.Left;
      Y2 := Y2 - Form3.Top;
    
      FormRegion := CreateRectRgn(0, 0, Form3.Width, Form3.Height);
      HoleRegion := CreateRectRgn(X1, Y1, X2, Y2);
      CombineRgn(FormRegion, FormRegion, HoleRegion, RGN_DIFF);
      SetWindowRgn(Form3.handle, FormRegion, true);
    

    Edit

    It appears counterintuitive that your Server.Form2 isn't the same size as the screen (and therefore Client.Form2). But maybe I never really understood for what actual purpose the setup is used.

    Anyway, with equally sized, centered Form3's, but with different screen sizes at Server and at Client, you need to adjust the Form3.Hole coordinates at the client with half the difference of Server and Client screen extents, or, since the Form3 forms are centered, you can calculate the horizontal and vertical correction like

    ResolutionCorrectionX := Server.Form3.Left - Client.Form3.Left;
    ResolutionCorrectionY := Server.Form3.Top - Client.Form3.Top;
    

    that you then add to the X- and Y-coordinates for HoleRegion of Form3.

    X1 := X1 - Form3.Left + ResolutionCorrectionX; // and similar for X2, Y1 and Y2
    

    Btw, just of curiosity, why are you using Round() for calculations based on integers?