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(va r 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.b ottom))) 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.X Pos-left,s r.Top+mess age.YPos-t op,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 :)
This is the code I have so far:
procedure TMyForm.WindowPosChange(va
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'
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)
//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'
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
end;
if snapped then break;
// next window
wnd := findwindowex(parentwnd, wnd, 'TMyForm', nil);
end;
// ==========================
end;
Any help appreciated :)
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/doc kex.dpr.
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.
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.
ASKER
Well SetWindowPos works if I use
setwindowpos(wnd,0,0,0,0,0 ,SWP_NOZOR DER 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)
setwindowpos(wnd,0,0,0,0,0
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-For m3.Width+3 0,
Top+Height-Form3.Height+30 , Form3.Width, Form3.Height,
SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE);
EndDeferWindowPos(Hdwp1);
end;
end;
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,
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-For
Top+Height-Form3.Height+30
SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE);
EndDeferWindowPos(Hdwp1);
end;
end;
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.
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?
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,
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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,