Link to home
Start Free TrialLog in
Avatar of nem2k4
nem2k4

asked on

Keep forms snapped together

I'm using some code I found on the net to make all forms in my application snap to each other's edges. This is working great, but I'd also like the forms to remain snapped together so that when I move one, all other forms snapped to it move with it, and any forms snapped to those forms move with those ones etc etc. I had a shot at it myself but I couldn't get much working... the form just gets stuck to the side of another form and I can't move it anymore.

This is the code I have so far:


procedure TMyForm.WindowPosChange(var Msg: TWMwindowposchanging);
var
    rWorkArea: TRect;
    StickAt : integer;
    wnd,parentwnd:HWND;
    sr:TRect;
    snapped:boolean;
    P:TPoint;
begin
    // =========================================================
    // SNAP TO OTHER FORMS
    stickat := 10;
    wnd := findwindowex(0,0,'TMyForm', nil);
    snapped:=false;
    parentwnd := 0;

    while(wnd <> 0) do begin

      if (wnd<>0) and (wnd<>self.Handle) and (GetWindowRect(wnd,sr)) then with Msg.WindowPos^ do begin
          //if we're within the snap stickatold then snap
          if (    (x <= (sr.right+stickat)) and
                (x >= (sr.right-stickat)) ) then begin
            if (((y > sr.top)or(y+height>sr.top)) and ((y < sr.bottom)or(y+height<sr.bottom))) then begin
                //disallow "open air" snaps
                snapped:=true;
                x := sr.right;
            end;
          end else if (((x + cx) >= (sr.left-stickat)) and
            ((x + cx) <= (sr.left+stickat))) then begin
            if (((y > sr.top)or(y+height > sr.top)) and (((y < sr.bottom)or(y+height < sr.bottom)))) then begin
                snapped:=true;
                x := sr.left-cx;
            end;
          end;

          if ( (y <= (sr.bottom+stickat)) and
            (y >= (sr.bottom-stickat)) ) then begin
            if (((x > sr.left)or(x+width > sr.left)) and ((x < sr.right)or(x+width < sr.right))) then begin
                snapped:=true;
                y := sr.bottom;
            end;
          end
          else if (((y + cy) <= (sr.top+stickat)) and
                ((y + cy) >= (sr.top-stickat))) then begin
            if (((x > sr.left)or(x+width > sr.left)) and ((x < sr.right)or(x+width < sr.right))) then begin
                snapped:=true;
                y := sr.top-cy;
            end;
          end;
      end;


      if snapped then break;

      // next window
      wnd := findwindowex(parentwnd, wnd, 'TMyForm', nil);

    end;
    // =========================================================
      
      
      
    // =========================================================
    /// KEEP FORMS SNAPPED TOGETHER
    wnd := findwindowex(0,0,'TMyForm', nil);
    snapped:=false;
    parentwnd := 0;

    while(wnd <> 0) do begin

      if (wnd<>0) and (wnd<>self.Handle) and (GetWindowRect(wnd,sr)) then begin
          // test to try and get the form to stay snapped to the right-hand side of another form
          if message.XPos = sr.Right then
            setwindowpos(wnd,0,sr.Left+message.XPos-left,sr.Top+message.YPos-top,0,0,SWP_NOZORDER or SWP_NOSIZE);
      end;


      if snapped then break;

      // next window
      wnd := findwindowex(parentwnd, wnd, 'TMyForm', nil);

    end;
    // =========================================================

end;


Any help appreciated :)
Avatar of sftweng
sftweng

Have you considered creating your application as a MDI application with FormStyle set to "tile" the child forms? If you have rejected that option, is it because the "child" forms are of different sizes?
You might like to have a look at the docking demo in %DELPHI%/Demos/Docking/dockex.dpr.
Avatar of nem2k4

ASKER

Whoops sorry I didn't mention this (I didn't think it would matter); I'm actually running lots of copies of the same application (each having only one form) and I want those forms to be able to dock together. And the forms can be of different sizes.
It seems like you are trying to change the position of windows in other processes in the " /// KEEP FORMS SNAPPED TOGETHER" Part. As far as I know setwindowpos only works for windows running in the same process.
So if you want to change the position of a window in another process you have to use some kind of interprocess communication like sockets or pipes. You must then notify the other process that you want to move its window to a desired position.
Avatar of nem2k4

ASKER

Well SetWindowPos works if I use

setwindowpos(wnd,0,0,0,0,0,SWP_NOZORDER or SWP_NOSIZE);

It moves the window to (0,0).

I just need to work out where to move it, and also I'm not sure if WM_WINDOWPOSCHANGING is the right message to handle here; I need to shift the snapped form whenever the form I am dragging is moved (even if by just one pixel)
hello  nem2k4 , , I have used the API  DeferWindowPos( )  function to move several windows "Together" as I move the Main Window, however your request for moving any window and having all the other windows follow will complicate this effort. . . The  DeferWindowPos was made for moving more than 2 windows at the same time, and works well for me, I used the WM_MOVE message for this particular code


  private
    { Private declarations }
    procedure WMMove(var Message: TWMMOVE); message WM_MOVE;


procedure TForm1.WMMove(var Message: TWMMOVE);
var
Hdwp1: THandle;
NumForms: Integer;
begin
NumForms := 0;
if (Form2 <> nil) and Active then
  Inc(NumForms);

if (Form3 <> nil) and Active then
  Inc(NumForms);

Label1.Caption := 'NumForms is '+IntToStr(NumForms)+' '+IntToStr(F2Top);
if NumForms = 0 then Exit;
if (NumForms = 1) and (Form2 <> nil) then
  MoveWindow(Form2.Handle, Left-30, Top-30, Form2.Width, Form2.Height, True);
if (NumForms = 1) and (Form3 <> nil) then
  MoveWindow(Form3.Handle, Left+Width-Form3.Width+30, Top+Height-Form3.Height+30, Form3.Width, Form3.Height, True);

if (NumForms = 2) then
  begin
  {the DeferWindowPos can move several windows at once}
  Hdwp1 := BeginDeferWindowPos(2);
  DeferWindowPos(Hdwp1,      // handle to internal structure
    Form2.Handle,      // handle to window to position
    Handle,      // placement-order handle
    Left-30,      // horizontal position
    Top-30,      // vertical position
    Form2.Width,      // width
    Form2.Height,      // height
    SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE  // window-positioning flags
   );

  {you need to call DeferWindowPos once for each window allocated
  in the BeginDeferWindowPos(2) function}
  DeferWindowPos(Hdwp1, Form3.Handle, HWND_BOTTOM,Left+Width-Form3.Width+30,
            Top+Height-Form3.Height+30, Form3.Width, Form3.Height,
            SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE);
  EndDeferWindowPos(Hdwp1);
  end;
end;
Avatar of nem2k4

ASKER

That code looks very promising :) Only thing I need to do is to keep the form positions relative to one another. I need to work out how many pixels Form1 was moved by and then move the others by the same amount.
Avatar of nem2k4

ASKER

Ok I have this:




unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
  private
 { Private declarations }
 oldx,oldy:integer;
    procedure WMMove(var Message: TWMMOVE); message WM_MOVE;

  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  Form2: TForm;
  Form3: TForm;

implementation

{$R *.dfm}

procedure TForm1.WMMove(var Message: TWMMOVE);
var
Hdwp1: THandle;
NumForms: Integer;
xshift,yshift:integer;
begin
NumForms := 0;

if (Form2 <> nil) and Active then
  Inc(NumForms);

if (Form3 <> nil) and Active then
  Inc(NumForms);

xshift := message.XPos-oldx;
yshift := message.YPos-oldY;
Label1.Caption := 'NumForms is '+IntToStr(NumForms);//+' '+IntToStr(F2Top);
label2.Caption := 'XShift is '+inttostr(xshift);


if NumForms = 0 then Exit;
if (NumForms = 1) and (Form2 <> nil) then
  MoveWindow(Form2.Handle, Left-30, Top-30, Form2.Width, Form2.Height, True);
if (NumForms = 1) and (Form3 <> nil) then
  MoveWindow(Form3.Handle, Left+Width-Form3.Width+30, Top+Height-Form3.Height+30, Form3.Width, Form3.Height, True);

if (NumForms = 2) then
  begin
  label3.Caption := 'form2.x is'+inttostr(form2.Left);
  label4.Caption := 'form1.x is'+inttostr(form1.Left);
  {the DeferWindowPos can move several windows at once}
  Hdwp1 := BeginDeferWindowPos(2);
  DeferWindowPos(Hdwp1,     // handle to internal structure
    Form2.Handle,     // handle to window to position
    Handle,     // placement-order handle
    form2.Left+xshift, //Left-30,     // horizontal position
    form2.Top+yshift, //Top-30,     // vertical position
    Form2.Width,     // width
    Form2.Height,     // height
    SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE  // window-positioning flags
   );

  {you need to call DeferWindowPos once for each window allocated
  in the BeginDeferWindowPos(2) function}
  DeferWindowPos(Hdwp1, Form3.Handle, HWND_BOTTOM,
  form3.Left+xshift,
            form3.Top+yshift,
             Form3.Width,
              Form3.Height,
            SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE);
  EndDeferWindowPos(Hdwp1);
  end;
  oldx:= message.XPos;
  oldy:= message.YPos;
end;

end.



It works ok I guess, bit of a hack using those oldx and oldy variables though... is there any better way to get how many pixels across and down form1 has moved? I tried (Message.X-Form1.Left) but that didn't work.

Also the windows "ghost" a bit when they move, any way to prevent that?
ASKER CERTIFIED SOLUTION
Avatar of Member_2_248744
Member_2_248744
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
oh , because of the way I had to move the forms around, You can NOT use their  "Left" and "TOP" window positions for anything, they may not be correct,