Kawaii-Hachii
Kawaii-Hachii

Reputation: 1061

Delphi 7 : Center form position on multiple monitors

I have a TForm and I set the "Position" to poMainFormCenter.

When I open that form, it is displayed correctly in the center of the Main Form.

But, on multiple screens (2 monitors), when I put the application in the secondary monitor, that form is not displayed in the center of the Main Form.

It is displayed still in the primary monitor, positioned in in the edge of the screen.

There is nothing fancy on my app, I only set that Position property.

Anyone know how to fix this?

I am using Delphi 7 and Windows XP SP3.

Upvotes: 2

Views: 13525

Answers (6)

Paul Blake
Paul Blake

Reputation: 1

I know this is an old thread but I was just trying to fix this problem with regards to modal dialog forms and found the following to work (after reading the above post by James Johnson)

On OnActivate:

X := (Application.MainForm.ClientWidth - Width) div 2;
Y := (Application.MainForm.ClientHeight - Height) div 2;
self.SetBounds(x,y,self.width,self.height);

Upvotes: 0

cvsingh
cvsingh

Reputation: 106

Hey David you can use poOwnerFormCenter Instead of poMainFormCenter. It will Solve your problem. Read this post.

Upvotes: 0

James Johnston
James Johnston

Reputation: 9492

None of the other answers here mention the cause of the problem in the first place, which is a bug in the VCL. From forms.pas on my system, with some snipping for brevity:

procedure TCustomForm.CMShowingChanged(var Message: TMessage);
var
  X, Y: Integer;
  NewActiveWindow: HWnd;
  CenterForm: TCustomForm;
begin
        if (FPosition = poScreenCenter) or
           ((FPosition = poMainFormCenter) and (FormStyle = fsMDIChild)) then
        begin
          if FormStyle = fsMDIChild then
          begin
            X := (Application.MainForm.ClientWidth - Width) div 2;
            Y := (Application.MainForm.ClientHeight - Height) div 2;
          end else
          begin
            X := (Screen.Width - Width) div 2;
            Y := (Screen.Height - Height) div 2;
          end;
          if X < 0 then X := 0;
          if Y < 0 then Y := 0;
          SetBounds(X, Y, Width, Height);
          if Visible then SetWindowToMonitor;
        end
        else if FPosition in [poMainFormCenter, poOwnerFormCenter] then
        begin
          CenterForm := Application.MainForm;
          if (FPosition = poOwnerFormCenter) and (Owner is TCustomForm) then
            CenterForm := TCustomForm(Owner);
          if Assigned(CenterForm) then
          begin
            X := ((CenterForm.Width - Width) div 2) + CenterForm.Left;
            Y := ((CenterForm.Height - Height) div 2) + CenterForm.Top;
          end else
          begin
            X := (Screen.Width - Width) div 2;
            Y := (Screen.Height - Height) div 2;
          end;
          if X < 0 then X := 0;
          if Y < 0 then Y := 0;
          SetBounds(X, Y, Width, Height);
          if Visible then SetWindowToMonitor;
        end
        else if FPosition = poDesktopCenter then
        begin
          if FormStyle = fsMDIChild then
          begin
            X := (Application.MainForm.ClientWidth - Width) div 2;
            Y := (Application.MainForm.ClientHeight - Height) div 2;
          end else
          begin
            X := (Screen.DesktopWidth - Width) div 2;
            Y := (Screen.DesktopHeight - Height) div 2;
          end;
          if X < 0 then X := 0;
          if Y < 0 then Y := 0;
          SetBounds(X, Y, Width, Height);
        end;

The key to this bug seems to be the following snippets, repeated several times in the function:

      if X < 0 then X := 0;
      if Y < 0 then Y := 0;

So, if you try to center the form on a monitor to the left or above the primary monitor (remember that origin is at upper-left corner of primary monitor), it will get snapped to the primary monitor from this check. It seems that this code wasn't updated when VCL was updated to support multiple monitors. Which is amusing, since two lines later are calls to SetWindowToMonitor.

The code was probably there from when only single monitors were supported in Windows 95 / Windows NT 4.0. In a single-monitor environment, negative coordinates are always off-screen, and it makes sense to snap to onscreen coordinates, which are always positive. However, the code fails miserably in the presence of multiple monitors, which allows for negative onscreen coordinates.

Working around this bug is left as an exercise to the reader. There are a number of possible solutions.

Upvotes: 3

Loren Pechtel
Loren Pechtel

Reputation: 9083

Jlouro has the right idea except for looking at the mouse. Screen.Monitors[] contains information on each screen.

I have a standard procedure that goes through the list of monitors and figures out where the upper left corner is to decide what monitor to put it on. While my code does not center (I was simply after ensuring that the window is entirely within whatever monitor it came up on) the idea remains the same. Note that you must consider the case where the window shows up not on ANY monitor--I handle that by throwing it to the first monitor. (This would come about when the saved position is on a monitor that doesn't exist anymore--either removed or running on a different machine.)

It's been a long time since I messed with this, it hasn't given me any trouble in ages and so I haven't tested it on anything more recent than XP/Delphi 7.

Note that this is only about ensuring that the form is visible and entirely on one monitor, there is no attempt to center it.

Function        PointInBox(x, y, x1, y1, x2, y2 : Integer) : Boolean;

Begin
    Result := (X >= X1) And (X <= X2) And (Y >= Y1) And (Y <= Y2);
End;

Function        Overlapping(x11, y11, x12, y12, x21, y21, x22, y22 : Integer) : Boolean;

Var
    tx1, ty1, tx2, ty2      : Integer;

Begin
    Tx1 := Max(x11, x21);
    Tx2 := Min(x12, x22);
    Ty1 := Max(y11, y21);
    Ty2 := Min(y12, y22);
    Result := (Tx1 < Tx2) And (Ty1 < Ty2);
End;

Function        GetWhere(Form : TForm) : Integer;

Var
    Loop        : Integer;
    Where       : Integer;

Begin
    Where           := -1;
    For Loop := 1 to Screen.MonitorCount do
        With Screen.Monitors[Loop - 1] do
            If PointInBox(Form.Left, Form.Top, Left, Top, Left + Width - 1, Top + Height - 1) then
                Where := Loop - 1;
    If Where = -1 then // Top left corner is wild, check for anything
        For Loop := 1 to Screen.MonitorCount do
            With Screen.Monitors[Loop - 1] do
                If Overlapping(Form.Left, Form.Top, Form.Left + Form.Width - 1, Form.Top + Form.Height - 1, Left, Top, Left + Width - 1, Top + Height - 1) then
                    Where := Loop - 1;
    Result := Where;
End;

Procedure   GetLimits(Where : Integer; var X, Y, WWidth, WHeight : Integer);

Var
    R               : TRect;

Begin
    If Where < 0 then
        Begin
            SystemParametersInfo(Spi_GetWorkArea, 0, @R, 0);
            X           := R.Left;
            Y           := R.Top;
            WWidth  := R.Right - R.Left + 1;
            WHeight := R.Bottom - R.Top + 1;
        End
    Else With Screen.Monitors[Where] do
        Begin
            X           := Left;
            Y           := Top;
            WWidth  := Width;
            WHeight := Height;
        End;
End;

Procedure   EnsureValidDisplay(Form : TForm);

Var
    Left            : Integer;
    Top         : Integer;
    Width           : Integer;
    Height      : Integer;
    Where           : WindowPlacement;

Begin
    GetLimits(GetWhere(Form), Left, Top, Width, Height);
    Where.Length    := SizeOf(Where);
    Where.Flags     := 0;
    GetWindowPlacement(Form.Handle, @Where);
    If Form.Left < Left then
        Where.rcNormalPosition.Left := Left
    Else If Form.Left + Form.Width > Left + Width then
        Where.rcNormalPosition.Left := Left + Width - Form.Width;
    If Form.Top < Top then
        Where.rcNormalPosition.Top      := Top
    Else If Form.Top + Form.Height > Top + Height then
        Where.rcNormalPosition.Top      := Top + Height - Form.Height;
    If Form.Width > Width then
        Where.rcNormalPosition.Right    := Where.rcNormalPosition.Left + Width
    Else
        Where.rcNormalPosition.Right    := Where.rcNormalPosition.Left + Form.Width;
    If Form.Height > Height then
        Where.rcNormalPosition.Bottom   := Where.rcNormalPosition.Top + Height
    Else
        Where.rcNormalPosition.Bottom   := Where.rcNormalPosition.Top + Form.Height;
    SetWindowPlacement(Form.Handle, @Where);
End;

Upvotes: 6

Kawaii-Hachii
Kawaii-Hachii

Reputation: 1061

I was able to workaround this by using the code below on the Form OnActivate:

Self.Left := MainForm.Left + ((MainForm.Width div 2) - (Self.Width div 2)); Self.Top := MainForm.Top + ((MainForm.Height div 2) - (Self.Height div 2));

MainForm is the "main" form of the application.

Upvotes: 2

Jlouro
Jlouro

Reputation: 4545

I use this on the create event:

C_FollowMouse :BOOLEAN=TRUE; // Global Const - Follow mouse. Opens App in the monitor where the mouse is.
C_Monitor   :BYTE=0;    // Default Monitor


    Procedure   TfrmMain.ScreenPOS;
    Var  pt:tpoint;
        _lMonitor :BYTE;
    Begin
        if NOT Screen.MonitorCount > 1 then Begin
            Position := poScreenCenter;
            Exit;
        End;

        _lMonitor := C_Monitor;
        if C_FollowMouse then Begin
            _lMonitor := 0;
            getcursorpos(pt);
            if pt.X < 0 then
            _lMonitor := 1;
    End;
    Left:= Screen.Monitors[_lMonitor].Left + Round( (Screen.Monitors[_lMonitor].Width - Width ) / 2);
    Top:=Screen.Monitors[_lMonitor].Top + Round( (Screen.Monitors[_lMonitor].Height - Height ) / 2)
  End;

Just tested it with 2 monitors. Is all I have. If you have more, post back the changes.

Upvotes: 2

Related Questions