Link to home
Start Free TrialLog in
Avatar of PeterLarsen
PeterLarsen

asked on

Hook two forms to eachother, like Winamp player do !

Hi Experts,

I have two forms that i would like to move simultaneously when i move one of the forms in run-time.

If i remember right, there is a windows function that can do that.

Any suggestions.

Regards
Peter
Avatar of Cesario Lababidi
Cesario Lababidi
Flag of Germany image

TFormEx v.0.9 beta
TFormEx adds Docking capability (dock to desktop or other forms), add background image or gradient pattern, add form minimum and maximum size, Force Form to always stay on desktop, Hide caption bar, move window by clicking in client area, and window transparency.
http://www.delphispirit.com/getvcl.php?vclid=437&rd=vcl/forms/complex/faformex.zip

TMagnetic v.1.01
By Zhong Wan. TMagnetic allows you move form like Winamp does, i.e. forms has ability to be docked to Screen, Explorer, TrayBar and other applications. Also you can change size of the forms without captions.

Fully functional
Source: Included
Exe-demo included

http://www.torry.net/vcl/forms/effects/zmagnetic.zip

best regards

Cesario
Avatar of PeterLarsen
PeterLarsen

ASKER

Hi Cesario,

Nice little thing you have there :-)
But this is pretty much what i already have by now.

What i want is some functions closer to the windows OS.

/PL
Sample code comes in 30 min ;-)

Cesario
I will look into your second suggestions later today !!
hehe, you'r fast Cesario !!
Here is the exmaple

Magnetic Window
by Wang Zhen- delphiques@email.com

One who uses Winamp may find it quite interesting that all of its child windows (the equalizer, the playlist or the mini-browser) will be aligned automatically when they are close to each other, just as if they are magnetic. After an hour's thinking and exploring, I've finally found a way to simulate this cool feature in my plug-in for Winamp. If you also want to equip your application with it, please have a look on the demonstration program below to get a general idea of my solution. The demo form will be "attracted" by Winamp when dragged near to Winamp's main window. It runs OK in Delphi 3 and should work well in any other versions, I suppose.

First, set up a new application and change the BorderStyle of Form1 to bsNone. Throw a button onto the form. Double click it and write "close;". Later, you will find it useful when you get bored. Now, let's do some typing. Here are the global variables.

var
     Form1: TForm1;                   //yeah, the magnetic window...
     LastX, LastY: Integer;           //keep the coordinates of cursor
     WinampRect:TRect;              //stores the region of target window
     hwnd_Winamp:HWND;        //the handle of Winamp's main window

Then fill the OnMouseDown and OnMouseMove events of Form1 as shown below.

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;  
      Shift: TShiftState; X, Y: Integer);
const
      ClassName='Winamp v1.x';   //the classname of Winamp's main window
      { Replace the line with "ClassName='TAppBuilder'" if your Delphi itself hungers for some magnetic power. }
begin

//save the coordinates
LastX := X;
LastY := Y;
//try to locate Winamp
hwnd_Winamp := FindWindow(ClassName,nil);
if hwnd_Winamp>0 then
      GetWindowRect(hwnd_Winamp, WinampRect);

end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
     nLeft,nTop:integer;
begin
//check whether the form is being dragged
     if HiWord(GetAsyncKeyState(VK_LBUTTON)) > 0 then
     begin
           //calculate new position
           nleft := Left + X - LastX;
           nTop := Top + Y - LastY;
//adjust the position if Winamp is found
ifhwnd_Winamp>0 then
        Magnetize(nleft,ntop);
     SetBounds(nLeft,nTop,width,height);
   end;

end;

Have a pause here. Before unveiling the codes of "magnetize()", I wanna give an unofficial definition of "window-magnetization". That is, "a process of adjusting the position of source window to align it and target window on one plane surface when they are close to some predefined extent". A little bit confusing huh? Well, sample codes are sometimes better than dull explanation. Let's continue...

 

procedure TForm1.Magnetize(var nl,nt:integer);
     //two simple embedded functions
     function Min(a,b:integer):integer;
     begin
     ifa>b then result:=b else result:=a;
     end;
     function Max(a,b:integer):integer;
     begin
     if a < b then result:=b else result:=a;
     end;
var
     H_Overlapped,V_Overlapped:boolean;
     tw,ww,wh:integer;
const
     MagneticForce:integer=50;
     { in other words, when two windows are nearer than 50 pixels, they will be aligned automatically.  Remember, 50 is used for demonstration purposes only.
        It's quite exaggerative if you want to simulate the effect of Winamp. }
begin

ww := WinampRect.Right-WinampRect.Left;
tw := Max(WinampRect.Right,nl+Width)-Min(WinampRect.Left,nl);
H_Overlapped := tw<=(Width+ww);

wh := WinampRect.Bottom-WinampRect.Top;
tw := Max(WinampRect.Bottom,nt+Height)-Min(WinampRect.Top,nt);
V_Overlapped := tw<=(Height+wh);

//change the position when the two windows are close enough
if H_Overlapped then
     begin
          if Abs(WinampRect.Bottom-nt)< MagneticForce then nt := WinampRect.Bottom
          else if Abs(nt+Height-WinampRect.Top)< MagneticForce then nt := WinampRect.Top-Height;
     end;

if V_Overlapped then
     begin
          if Abs(WinampRect.Right-nl)< MagneticForce then nl := WinampRect.Right
          else if Abs(nl+Width-WinampRect.Left)< MagneticForce then nl := WinampRect.Left-Width;
     end;

end;

Voila, now press F9 and feel it!


Here is the right Example:

How can I reposition a form relative to another form, which is being dragged by the mouse? I am thinking on a kind of movement synchronization. TControl.WMMove is unfortunately declared private.



The following is a primitive example, but it should get you started:



unit FollowForm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Forms, Controls, Buttons,
  StdCtrls, ExtCtrls;

type
  TFrmFollow = class(TForm)
    BtnValidate: TBitBtn;
    BtnSave: TBitBtn;
    BtnPreview: TBitBtn;
    BtnPrint: TBitBtn;
    BtnExit: TBitBtn;
    BtnHelp: TBitBtn;
    procedure BtnExitClick(Sender: TObject);
  private
    FOldOwnerWindowProc: TWndMethod;  {WindowProc for FOwnerForm}
    FOwnerForm: TForm;
    {Window subclassing methods:}
    procedure HookForm;
    procedure UnhookForm;
    procedure WndProcForm(var AMsg: TMessage);
  protected
    procedure CreateWnd;
      override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

var
  FrmFollow: TFrmFollow;

implementation

{$R *.DFM}


resourcestring
  SRGSBadUseOfFF = 'FollowForm can only be owned by another form';


constructor TFrmFollow.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  if AOwner <> nil then
  begin
    if AOwner is TForm then
      FOwnerForm := TForm(AOwner)
    else
      {Owner is not a form}
      raise Exception.CreateRes(@SRGSBadUseOfFF);
  end;
end;


procedure TFrmFollow.CreateWnd;
begin
  inherited;
  if csDesigning in ComponentState then
    Exit;  {Don't need to hook when designing}
  if Enabled and Assigned(FOwnerForm) then
    HookForm;  {Hook the main form's Window}
end;


destructor TFrmFollow.Destroy;
begin
  if not (csDesigning in ComponentState) then
    UnhookForm;  {Stop interfering ...}
  inherited Destroy;
end;


procedure TFrmFollow.HookForm;
begin
  {Hook the windows procedure of my owner only if I have an owner, the Owner's
  window handle has been created and we are not in design mode.}
  FOldOwnerWindowProc := nil;
  if Assigned(FOwnerForm) and FOwnerForm.HandleAllocated then
  begin
    if not (csDesigning in ComponentState) then
    begin
      FOldOwnerWindowProc := FOwnerForm.WindowProc;
      FOwnerForm.WindowProc := WndProcForm;
    end;
  end;
end;


procedure TFrmFollow.UnhookForm;
begin
  {If we are "hooked" then undo what Hookform did}
  if Assigned(FOldOwnerWindowProc) then
  begin
    if (FOwnerForm <> nil) and (FOwnerForm.HandleAllocated) then
    begin
      FOwnerForm.WindowProc := FOldOwnerWindowProc;
    end;
    FOldOwnerWindowProc := nil;
    FOwnerForm := nil;
  end;
end;


{WndProcForm is our replacement for our WindowProc. We grab any Windows
messages that we need here.}

procedure TFrmFollow.WndProcForm(var AMsg: TMessage);
var
  cmdType: Word;
  xPos: Word;
  yPos: Word;
begin
  if Enabled then
  begin
    case AMsg.Msg of
      WM_MOVE:
      begin
        xPos := FOwnerForm.Left;
        yPos := FOwnerForm.Top;
        Caption := Format('%d:%d', [xPos, yPos]);
        SetBounds(xPos + 12, yPos + 12, Width, Height);
        BringToFront;
      end;
      WM_SIZE, WM_EXITSIZEMOVE:
      begin
        BringToFront;
      end;
      WM_SYSCOMMAND:
      begin
        cmdType := AMsg.WParam and $FFF0;
        case cmdType of
          SC_MAXIMIZE, SC_SIZE:
          begin
            xPos := FOwnerForm.Left;
            yPos := FOwnerForm.Top;
            Caption := Format('%d:%d', [xPos, yPos]);
            SetBounds(xPos, yPos, Width, Height);
            BringToFront;
          end;
        end;
      end;
    end;
  end;
  {Call the default windows procedure}
  FOldOwnerWindowProc(AMsg);
end;


procedure TFrmFollow.BtnExitClick(Sender: TObject);
begin
  Close;
end;

end.



Tip by Ralph Friedman

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
the Rect1 var is for someting else and should not be there
Hi all,

And thanks for your comments.

As i wrote earlier, i want functions closer to the OS.
I use DeferWindowPos myself, which prevent flickering and separation between the two (or more) forms.

But it doesn't look good in older OS (Windows OS that dont move the entire form, but only a frame). You gotta see it before you understand why.

Anyway - i need a better way to capture the move (other than WM_Moving) or a better way to control the moving FocusRect myself.

/Peter
Well, i can't say that you have answered my question, but...

I have found that SystemParametersInfo.SPI_GETDRAGFULLWINDOWS tells whether a moving window is repainted or only shown by a FocusRect.

If the window is repainted i use WM_MOVING and DeferWindowPos. DeferWindowPos is easy to use when the number of moving windows are unknown. Also the ability to move the windows at the exact same time is important
If only a FocusRect is shown i use WM_MOVING and WM_MOVE.

The code provided by Slick812 is the simplest way to move two windows simultaneously. So the points go to him.I have taken the idea and use it when the system dont meet the  requirements to use DeferWindowPos, e.g. short of memory etc.

Thanks for your time.
Peter