戻る

Delphiでウインドウの最大化時の大きさをコントロール(マルチモニタ対応)



参考にした情報

この内容はDelphi-ML「[Delphi:90115] ウインドウを最大化したまま最大化サイズを調節する」で付いたレスを参考にしています。 教えてくださる皆様、感謝感謝です。

参考にしたWebページ

GetMonitorInfoでモニタに関する情報を取得する

モニタの範囲を知るためにはWindows APIの「GetMonitorInfo」を使う。
このAPIはマルチモニタ対応のAPIで、ディスプレイモニタのハンドルを渡してあげる事によって、そのディスプレイモニタに関する情報を取得する事が出来ます。

情報はMONITORINFO 構造体によって返され、以下のような情報が帰ってきます。

モニタの範囲
MonitorInfo.rcMonitor.Bottom
MonitorInfo.rcMonitor.Top
MonitorInfo.rcMonitor.Left
MonitorInfo.rcMonitor.Right

モニタの有効面の範囲
MonitorInfo.rcWork.Bottom
MonitorInfo.rcWork.Top
MonitorInfo.rcWork.Left
MonitorInfo.rcWork.Right

以下の例では赤い四角がモニタの範囲、緑色の範囲がモニタの有効面の範囲になってます。
以下の例ではタスクバーの分が有効面から除外されています。
これは、ウインドウを最大化したときに使える範囲=有効面だからです。
なので、他のソフトでデスクトップを占有するタイプのアプリケーションがある場合はその分も 有効面から除外されます

#ref(): File not found: "Monitor1.jpg" at page "Delphiでウインドウの最大化時の大きさをコントロール(マルチモニタ対応)"


取得出来る値は、プライマリモニタの左上を原点として数値が決まります。
2台目以降のモニタの位置はある程度好きなように置ける様です。
プライマリモニタより左・上のモニタは取得出来る値が当然マイナスで帰って来ます。

Monitor2.jpg

※ぜんぜん関係ない話ですが、上の図はExcelで作っている物を、セル範囲のコピーってそのままペイント等に画像として貼り付けしてます。
知らなかったです…なんとなく「出来るかな?」と思ってやって出来てびっくり。

Delphiのコードだとこんな感じです

var
  FMonitorInfo: TMonitorInfo;
begin
  ZeroMemory(@FMonitorInfo, sizeof(FMonitorInfo));
  FMonitorInfo.cbSize := SizeOf(FMonitorInfo);
  GetMonitorInfo(FTargetForm.Monitor.Handle, @FMonitorInfo);

作っていて気になった事柄

procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND;

の関数の中で使われている「msg.MinMaxInfo.ptMaxPosition.X」「msg.MinMaxInfo.ptMaxPosition.Y」なのですが、よくよく調べてみるとココに入れる値は

 // GetMonitorInfo,Self.Top,Self.Leftで帰ってくる値は
 // プライマリモニタの左上を原点(0,0)とした値が帰ってくるが、
 // msg.MinMaxInfo.ptMaxPositionは
 // BorderStyleがbsSizeableおよびbsSingleの場合のみ
 // 各種モニタごとの有効面の左上を原点(0,0)
 // とした座標を指定しなければいけない
 // それ以外のBorderStyleでは
 // 各種モニタごとの左上を原点(0,0)とした座標を指定する。

という事らしい。
ウインドウのモードによって変わるなんて…あああ大混乱する…。

作ってみたコード

マルチディスプレイ対応です。
この内容はDelphi-ML「[Delphi:90115] ウインドウを最大化したまま最大化サイズを調節する」で付いたレスを参考にしています。
教えてくださる皆様、感謝感謝です。

2008/1/24修正 ウインドウがbsSizeable・bsSingle以外のモードの場合が動きおかしかったのを修正

ダウンロード

#ref(): File not found: "MultiMonitorParam20080127.ZIP" at page "Delphiでウインドウの最大化時の大きさをコントロール(マルチモニタ対応)"

{*****************************************************************************}
{                                                                             }
{       MultiMonitorParam                                                     }
{                                                                             }
{       Copyright (c) 2007 Moon Doldo                                         }
{                                                                             }
{この内容はDelphi-ML                                                          }
{[Delphi:90115] ウインドウを最大化したまま最大化サイズを調節する              }
{で付いたレスを参考にしています。                                             }
{                                                                             }
{*****************************************************************************}

unit MultiMonitorParam;

interface

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

type
  // MonitorInfo保持クラス
  TMonitorInfoParam = class
  private
    FMonitorInfo : TMonitorInfo;
    FDisplayMonitorInfo: TMonitorInfo;
    FTargetForm: TForm;
    procedure SetTargetForm(const Value: TForm);
  protected
    // MonitorInfoの取得
    procedure GetMonitorInfoParam;

  public
    // 対象のウインドウを設定
    property TargetForm: TForm read FTargetForm write SetTargetForm;

    function MonitorInfo: TMonitorInfo;
    function DisplayMonitorInfo: TMonitorInfo;

    // モニタの数を取得
    function MonitorCount: Integer;

    // モニタ番号の取得
    function MonitorNum: Integer;
  end;

  // 最大化の管理クラス
  TMultiMonitorMaxInfoCalc = class(TMonitorInfoParam)
  private
    FMaxSizeY: Integer;
    FMaxSizeX: Integer;
    FMaxPositionY: Integer;
    FMaxPositionX: Integer;
    FMaxAlign: TAlign;

    FSetWMGetMinMaxInfo2: Boolean;

    procedure SetMaxSizeX(const Value: Integer);
    procedure SetMaxSizeY(const Value: Integer);
    procedure SetMaxPositionX(const Value: Integer);
    procedure SetMaxPositionY(const Value: Integer);
    procedure SetMaxAlign(const Value: TAlign);
  protected
    // フォームの大きさと位置の制限
    procedure WMClientGetMinMaxInfo(var msg : TWMGetMinMaxInfo);
    procedure WMCustomGetMinMaxInfo(var msg : TWMGetMinMaxInfo);
    procedure WMTopGetMinMaxInfo(var msg : TWMGetMinMaxInfo);
    procedure WMBottomGetMinMaxInfo(var msg : TWMGetMinMaxInfo);
    procedure WMLeftGetMinMaxInfo(var msg : TWMGetMinMaxInfo);
    procedure WMRightGetMinMaxInfo(var msg : TWMGetMinMaxInfo);

  public
    //TObjectのコンストラクタ定義
    constructor Create;
    //デストラクタ定義
    destructor Destroy; override;

    // フォームの大きさと位置の制限
    procedure WMGetMinMaxInfo(var msg : TWMGetMinMaxInfo);

    // フォームの大きさと位置の制限(既に最大化している場合)
    procedure WMGetMinMaxInfo2;

    // 通常の状態のフォームを適切な位置へ移動する
    procedure SetFormSafePosition;
  published
    // 設定したいウインドウサイズを指定する
    // alLeft, alRight, alCustomの場合のみ実行される
    property MaxSizeX: Integer read FMaxSizeX write SetMaxSizeX;
    // alTop, alBottom, alCustomの場合のみ実行される
    property MaxSizeY: Integer read FMaxSizeY write SetMaxSizeY;

    // 設定したい位置を指定する(各種モニタの有効面の左上を原点(0,0))
    // alCustomの場合のみ実行される
    property MaxPositionX: Integer read FMaxPositionX write SetMaxPositionX;
    property MaxPositionY: Integer read FMaxPositionY write SetMaxPositionY;

    // 最大化の際に、モニタ内でコントロールを揃える方法を指定します。
    property MaxAlign: TAlign read FMaxAlign write SetMaxAlign;
  end;

implementation

{ TMultiMonitorMaxInfoCalc }

constructor TMultiMonitorMaxInfoCalc.Create;
begin
  FMaxAlign := alCustom;

  FMaxSizeY := 0;
  FMaxSizeX := 0;
  FMaxPositionY := 0;
  FMaxPositionX := 0;

  FSetWMGetMinMaxInfo2 := False;
end;

destructor TMultiMonitorMaxInfoCalc.Destroy;
begin
  inherited;
end;

procedure TMultiMonitorMaxInfoCalc.SetFormSafePosition;
var
  WkMonitorInfo : TMonitorInfo;

  WkMonitorInfoWidth, WkMonitorInfoHeight: Integer;
  WkTargetFormRight, WkTargetFormBottom: Integer;

  WkLeft, WkTop: Integer;
begin
  if FTargetForm.WindowState = wsNormal then
  begin
    // マルチモニタ対応の有効面の取得
    WkMonitorInfo := Self.MonitorInfo;

    WkLeft := FTargetForm.Left;
    WkTop := FTargetForm.Top;

    // 左上が有効面からはみ出ていたら直す
    if WkLeft < WkMonitorInfo.rcWork.Left then
    begin
      WkLeft := WkMonitorInfo.rcWork.Left;
    end;
    if WkTop < WkMonitorInfo.rcWork.Top then
    begin
      WkTop := WkMonitorInfo.rcWork.Top;
    end;

    // 右下が有効面からはみ出ていたら直す
    // ただし、有効面のよりフォームが小さい場合のみ行う(左上を優先にするため)
    WkMonitorInfoWidth :=
      WkMonitorInfo.rcWork.Right - WkMonitorInfo.rcWork.Left;
    if FTargetForm.Width <= WkMonitorInfoWidth then
    begin
      WkTargetFormRight := WkLeft + FTargetForm.Width;
      if WkTargetFormRight > WkMonitorInfo.rcWork.Right then
      begin
        WkLeft := WkMonitorInfo.rcWork.Right - FTargetForm.Width;
      end;
    end;
    WkMonitorInfoHeight :=
      WkMonitorInfo.rcWork.Bottom - WkMonitorInfo.rcWork.Top;
    if FTargetForm.Height <= WkMonitorInfoHeight then
    begin
      WkTargetFormBottom := WkTop + FTargetForm.Height;
      if WkTargetFormBottom > WkMonitorInfo.rcWork.Bottom then
      begin
        WkTop := WkMonitorInfo.rcWork.Bottom - FTargetForm.Height;
      end;
    end;

    FTargetForm.Left := WkLeft;
    FTargetForm.Top := WkTop;
  end;
end;

procedure TMultiMonitorMaxInfoCalc.SetMaxAlign(const Value: TAlign);
begin
  FMaxAlign := Value;
  WMGetMinMaxInfo2;
end;

procedure TMultiMonitorMaxInfoCalc.SetMaxPositionX(const Value: Integer);
begin
  FMaxPositionX := Value;
end;

procedure TMultiMonitorMaxInfoCalc.SetMaxPositionY(const Value: Integer);
begin
  FMaxPositionY := Value;
end;

procedure TMultiMonitorMaxInfoCalc.SetMaxSizeX(const Value: Integer);
begin
  FMaxSizeX := Value;
end;

procedure TMultiMonitorMaxInfoCalc.SetMaxSizeY(const Value: Integer);
begin
  FMaxSizeY := Value;
end;

procedure TMultiMonitorMaxInfoCalc.WMBottomGetMinMaxInfo(
  var msg: TWMGetMinMaxInfo);
var
  WkMonitorInfo : TMonitorInfo;

  WkSizeX, WkSizeY: Integer;
  WkPositionX, WkPositionY: Integer;
begin
  // マルチモニタ対応の有効面の取得
  WkMonitorInfo := Self.MonitorInfo;

  WkSizeX :=
    WkMonitorInfo.rcWork.Right - WkMonitorInfo.rcWork.Left;
  WkSizeY :=
    Min(FMaxSizeY,
    WkMonitorInfo.rcWork.Bottom - WkMonitorInfo.rcWork.Top);

  WkPositionX := WkMonitorInfo.rcWork.Left;
  WkPositionY := WkMonitorInfo.rcWork.Bottom - WkSizeY;

  msg.MinMaxInfo.ptMaxSize.X := WkSizeX;
  msg.MinMaxInfo.ptMaxSize.Y := WkSizeY;

  // GetMonitorInfo,Self.Top,Self.Leftで帰ってくる値は
  // プライマリモニタの左上を原点(0,0)とした値が帰ってくるが、
  // msg.MinMaxInfo.ptMaxPositionは
  // BorderStyleがbsSizeableおよびbsSingleの場合のみ
  // 各種モニタごとの有効面の左上を原点(0,0)
  // とした座標を指定しなければいけない
  // それ以外のBorderStyleでは
  // 各種モニタごとの左上を原点(0,0)とした座標を指定する。
  if (TargetForm.BorderStyle = bsSizeable) or
    (TargetForm.BorderStyle = bsSingle) or
    (FSetWMGetMinMaxInfo2 = True) then
  begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcWork.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcWork.Top;
    FSetWMGetMinMaxInfo2 := False;
  end
  else begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcMonitor.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcMonitor.Top;
  end;
  msg.MinMaxInfo.ptMaxPosition.X :=
    WkPositionX;
  msg.MinMaxInfo.ptMaxPosition.Y :=
    WkPositionY;
end;

procedure TMultiMonitorMaxInfoCalc.WMCustomGetMinMaxInfo(
  var msg: TWMGetMinMaxInfo);
var
  WkMonitorInfo : TMonitorInfo;

  WkSizeX, WkSizeY: Integer;
  WkPositionX, WkPositionY: Integer;
begin
  // マルチモニタ対応の有効面の取得
  WkMonitorInfo := Self.MonitorInfo;

  // 位置の値を保持
  // プライマリモニタの左上を原点(0,0)とした値に直す
  WkPositionX :=
    FMaxPositionX + WkMonitorInfo.rcWork.Left;
  WkPositionY :=
    FMaxPositionY + WkMonitorInfo.rcWork.Top;

  // 有効面から左上にはみ出ている場合は直す
  if WkPositionX < WkMonitorInfo.rcWork.Left then
  begin
    WkPositionX := WkMonitorInfo.rcWork.Left;
  end;
  if WkPositionY < WkMonitorInfo.rcWork.Top then
  begin
    WkPositionY := WkMonitorInfo.rcWork.Top;
  end;

  // 有効面以上の大きさにならないように大きさを修正する
  WkSizeX :=
    Min(FMaxSizeX,
    WkMonitorInfo.rcWork.Right - WkMonitorInfo.rcWork.Left - FMaxPositionX);
  WkSizeY :=
    Min(FMaxSizeY,
    WkMonitorInfo.rcWork.Bottom - WkMonitorInfo.rcWork.Top - FMaxPositionY);

  msg.MinMaxInfo.ptMaxSize.X := WkSizeX;
  msg.MinMaxInfo.ptMaxSize.Y := WkSizeY;

  // GetMonitorInfo,Self.Top,Self.Leftで帰ってくる値は
  // プライマリモニタの左上を原点(0,0)とした値が帰ってくるが、
  // msg.MinMaxInfo.ptMaxPositionは
  // BorderStyleがbsSizeableおよびbsSingleの場合のみ
  // 各種モニタごとの有効面の左上を原点(0,0)
  // とした座標を指定しなければいけない
  // それ以外のBorderStyleでは
  // 各種モニタごとの左上を原点(0,0)とした座標を指定する。
  if (TargetForm.BorderStyle = bsSizeable) or
    (TargetForm.BorderStyle = bsSingle) or
    (FSetWMGetMinMaxInfo2 = True) then
  begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcWork.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcWork.Top;
    FSetWMGetMinMaxInfo2 := False;
  end
  else begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcMonitor.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcMonitor.Top;
  end;
  msg.MinMaxInfo.ptMaxPosition.X :=
    WkPositionX;
  msg.MinMaxInfo.ptMaxPosition.Y :=
    WkPositionY;
end;

procedure TMultiMonitorMaxInfoCalc.WMGetMinMaxInfo(
  var msg: TWMGetMinMaxInfo);
var
  WkMonitorInfo : TMonitorInfo;

  WkSizeX, WkSizeY: Integer;
  WkPositionX, WkPositionY: Integer;
  WkPositionRight, WkPositionBottom: Integer;
begin
  case FMaxAlign of
    alNone:
    begin
      // 何もしないデフォルトの動作
      Exit;
    end;
    alClient:
    begin
      WMClientGetMinMaxInfo(msg);
    end;
    alCustom:
    begin
      WMCustomGetMinMaxInfo(msg);
    end;
    alTop:
    begin
      WMTopGetMinMaxInfo(msg);
    end;
    alBottom:
    begin
      WMBottomGetMinMaxInfo(msg);
    end;
    alLeft:
    begin
      WMLeftGetMinMaxInfo(msg);
    end;
    alRight:
    begin
      WMRightGetMinMaxInfo(msg);
    end;
  else
    ;
  end;
end;


procedure TMultiMonitorMaxInfoCalc.WMGetMinMaxInfo2;
var
  WkMonitorInfo : TMonitorInfo;
  WkWindowPlacement, WkSaveWindowPlacement : TWindowPlacement;

  WkSmCxframe, WkSmCyframe: Integer;
  WkSizeX, WkSizeY: Integer;
  WkPositionX, WkPositionY: Integer;
begin
  // 最大化の時のみ実行される
  if IsZoomed(FTargetForm.Handle) then
  begin
    if (FMaxAlign <> alNone) and (FMaxAlign <> alClient) then
    begin
      // ウィンドウの配置情報を保持
      ZeroMemory(@WkSaveWindowPlacement, sizeof(WkSaveWindowPlacement));
      WkSaveWindowPlacement.length := sizeof(WkSaveWindowPlacement);
      GetWindowPlacement(FTargetForm.Handle, @WkSaveWindowPlacement);

      // フォームの最大化スタイルを無理やり消しちゃう
      SetWindowLong(
        FTargetForm.Handle,
        GWL_STYLE,
        GetWindowLong(FTargetForm.Handle, GWL_STYLE) and not WS_MAXIMIZE);

      // さらに最大化
      // ただ、この方法だとBorderStyleがbsSizeable・bsSingle以外の場合でも
      // WMGetMinMaxInfoがbsSizeable・bsSingleとして認識してしまうため、
      // その対策のためのフラグを立てて
      // 必ずBorderStyleがbsSizeable・bsSingleの扱いで動作させる
      // もっとスマートな方法ないのかな・・・
      FSetWMGetMinMaxInfo2 := True;
      ShowWindow(FTargetForm.Handle, SW_MAXIMIZE);

      // 新しい位置情報を取得
      ZeroMemory(@WkWindowPlacement, sizeof(WkWindowPlacement));
      WkWindowPlacement.length := sizeof(WkWindowPlacement);
      GetWindowPlacement(FTargetForm.Handle, @WkWindowPlacement);

      // リストア時の情報のみ上書き
      WkWindowPlacement.rcNormalPosition := WkSaveWindowPlacement.rcNormalPosition;

      //保持していたウィンドウの配置情報を戻す
      SetWindowPlacement(FTargetForm.Handle, @WkWindowPlacement);
    end
    else begin
      // 上記のコードで全部まかなえると思ったら通常の最大化だけおかしくなるので
      // 最大化のコードだけ以下のものを使う

      // 通常の最大化

      // 通常、Windowsでは最大化した際にウインドウの枠は有効面の外にはみ出る
      // 大きさは有効面より左右2辺、上下2辺の枠の分だけ大きくなる。
      // ウインドウの位置も左1辺、上1辺分だけ左上にずれる事になる。

      WkMonitorInfo := Self.MonitorInfo;

      if FTargetForm.BorderStyle <> bsNone then
      begin
        // ウインドウの枠の幅を取得
        WkSmCxframe := GetSystemMetrics(SM_CXFRAME);
        WkSmCyframe := GetSystemMetrics(SM_CYFRAME);
      end
      else begin
        // BorderStyleがbsNoneの場合は枠がないため枠の大きさを考慮しない
        WkSmCxframe := 0;
        WkSmCyframe := 0;
      end;

      WkPositionX := WkMonitorInfo.rcWork.Left - WkSmCxframe;
      WkPositionY := WkMonitorInfo.rcWork.Top - WkSmCyframe;
      WkSizeX :=
        WkMonitorInfo.rcWork.Right - WkMonitorInfo.rcWork.Left +
        (WkSmCxframe * 2);
      WkSizeY :=
        WkMonitorInfo.rcWork.Bottom - WkMonitorInfo.rcWork.Top +
        (WkSmCyframe * 2);

      // 隣のモニタに枠の部分がはみでて見えてしまうのを防止するために
      // ちらつきを最小限にして元のサイズに戻す→最大化をする

      // ウィンドウの配置情報を保持
      ZeroMemory(@WkSaveWindowPlacement, sizeof(WkSaveWindowPlacement));
      WkSaveWindowPlacement.length := sizeof(WkSaveWindowPlacement);
      GetWindowPlacement(FTargetForm.Handle, @WkSaveWindowPlacement);

      WkWindowPlacement.length  := SizeOf(WkWindowPlacement);
      WkWindowPlacement.showCmd := SW_SHOWNORMAL;
      WkWindowPlacement.flags   := WPF_RESTORETOMAXIMIZED;
      WkWindowPlacement.rcNormalPosition :=
        Rect(WkPositionX, WkPositionY,
        WkPositionX + WkSizeX, WkPositionY + WkSizeY);
      SetWindowPlacement(FTargetForm.Handle, @WkWindowPlacement);

      // 再最大化する
      ShowWindow(FTargetForm.Handle, SW_SHOWMAXIMIZED);

      // 保持していたウィンドウの配置情報を戻す
      SetWindowPlacement(FTargetForm.Handle, @WkSaveWindowPlacement);
    end;
  end;
end;

procedure TMultiMonitorMaxInfoCalc.WMLeftGetMinMaxInfo(
  var msg: TWMGetMinMaxInfo);
var
  WkMonitorInfo : TMonitorInfo;

  WkSizeX, WkSizeY: Integer;
  WkPositionX, WkPositionY: Integer;
begin
  // マルチモニタ対応の有効面の取得
  WkMonitorInfo := Self.MonitorInfo;

  WkPositionX := WkMonitorInfo.rcWork.Left;
  WkPositionY := WkMonitorInfo.rcWork.Top;

  WkSizeX :=
    Min(FMaxSizeX,
    WkMonitorInfo.rcWork.Right - WkMonitorInfo.rcWork.Left);
  WkSizeY :=
    WkMonitorInfo.rcWork.Bottom - WkMonitorInfo.rcWork.Top;

  msg.MinMaxInfo.ptMaxSize.X := WkSizeX;
  msg.MinMaxInfo.ptMaxSize.Y := WkSizeY;


  // GetMonitorInfo,Self.Top,Self.Leftで帰ってくる値は
  // プライマリモニタの左上を原点(0,0)とした値が帰ってくるが、
  // msg.MinMaxInfo.ptMaxPositionは
  // BorderStyleがbsSizeableおよびbsSingleの場合のみ
  // 各種モニタごとの有効面の左上を原点(0,0)
  // とした座標を指定しなければいけない
  // それ以外のBorderStyleでは
  // 各種モニタごとの左上を原点(0,0)とした座標を指定する。
  if (TargetForm.BorderStyle = bsSizeable) or
    (TargetForm.BorderStyle = bsSingle) or
    (FSetWMGetMinMaxInfo2 = True) then
  begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcWork.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcWork.Top;
    FSetWMGetMinMaxInfo2 := False;
  end
  else begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcMonitor.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcMonitor.Top;
  end;
  msg.MinMaxInfo.ptMaxPosition.X :=
    WkPositionX;
  msg.MinMaxInfo.ptMaxPosition.Y :=
    WkPositionY;
end;

procedure TMultiMonitorMaxInfoCalc.WMClientGetMinMaxInfo(
  var msg: TWMGetMinMaxInfo);
var
  WkMonitorInfo : TMonitorInfo;

  WkSizeX, WkSizeY: Integer;
  WkPositionX, WkPositionY: Integer;
begin
  // マルチモニタ対応の有効面の取得
  WkMonitorInfo := Self.MonitorInfo;

  // BorderStyleがそれ以外の場合は
  // 最大化した際に有効面を無視してしまう仕様のため、
  // 自前で有効面の制限を設定する。

  WkSizeX :=
    WkMonitorInfo.rcWork.Right - WkMonitorInfo.rcWork.Left;
  WkSizeY :=
    WkMonitorInfo.rcWork.Bottom - WkMonitorInfo.rcWork.Top;

  WkPositionX := WkMonitorInfo.rcWork.Left;
  WkPositionY := WkMonitorInfo.rcWork.Top;

  msg.MinMaxInfo.ptMaxSize.X := WkSizeX;
  msg.MinMaxInfo.ptMaxSize.Y := WkSizeY;

  // GetMonitorInfo,Self.Top,Self.Leftで帰ってくる値は
  // プライマリモニタの左上を原点(0,0)とした値が帰ってくるが、
  // msg.MinMaxInfo.ptMaxPositionは
  // BorderStyleがbsSizeableおよびbsSingleの場合のみ
  // 各種モニタごとの有効面の左上を原点(0,0)
  // とした座標を指定しなければいけない
  // それ以外のBorderStyleでは
  // 各種モニタごとの左上を原点(0,0)とした座標を指定する。
  if (TargetForm.BorderStyle = bsSizeable) or
    (TargetForm.BorderStyle = bsSingle) or
    (FSetWMGetMinMaxInfo2 = True) then
  begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcWork.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcWork.Top;
    FSetWMGetMinMaxInfo2 := False;
  end
  else begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcMonitor.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcMonitor.Top;
  end;
  msg.MinMaxInfo.ptMaxPosition.X :=
    WkPositionX;
  msg.MinMaxInfo.ptMaxPosition.Y :=
    WkPositionY;
end;

procedure TMultiMonitorMaxInfoCalc.WMRightGetMinMaxInfo(
  var msg: TWMGetMinMaxInfo);
var
  WkMonitorInfo : TMonitorInfo;

  WkSizeX, WkSizeY: Integer;
  WkPositionX, WkPositionY: Integer;
begin
  // マルチモニタ対応の有効面の取得
  WkMonitorInfo := Self.MonitorInfo;

  WkSizeX :=
    Min(FMaxSizeX,
    WkMonitorInfo.rcWork.Right - WkMonitorInfo.rcWork.Left);
  WkSizeY :=
    WkMonitorInfo.rcWork.Bottom - WkMonitorInfo.rcWork.Top;

  WkPositionX := WkMonitorInfo.rcWork.Right - WkSizeX;
  WkPositionY := WkMonitorInfo.rcWork.Top;

  msg.MinMaxInfo.ptMaxSize.X := WkSizeX;
  msg.MinMaxInfo.ptMaxSize.Y := WkSizeY;

  // GetMonitorInfo,Self.Top,Self.Leftで帰ってくる値は
  // プライマリモニタの左上を原点(0,0)とした値が帰ってくるが、
  // msg.MinMaxInfo.ptMaxPositionは
  // BorderStyleがbsSizeableおよびbsSingleの場合のみ
  // 各種モニタごとの有効面の左上を原点(0,0)
  // とした座標を指定しなければいけない
  // それ以外のBorderStyleでは
  // 各種モニタごとの左上を原点(0,0)とした座標を指定する。
  if (TargetForm.BorderStyle = bsSizeable) or
    (TargetForm.BorderStyle = bsSingle) or
    (FSetWMGetMinMaxInfo2 = True) then
  begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcWork.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcWork.Top;
    FSetWMGetMinMaxInfo2 := False;
  end
  else begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcMonitor.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcMonitor.Top;
  end;
  msg.MinMaxInfo.ptMaxPosition.X :=
    WkPositionX;
  msg.MinMaxInfo.ptMaxPosition.Y :=
    WkPositionY;
end;

procedure TMultiMonitorMaxInfoCalc.WMTopGetMinMaxInfo(
  var msg: TWMGetMinMaxInfo);
var
  WkMonitorInfo : TMonitorInfo;

  WkSizeX, WkSizeY: Integer;
  WkPositionX, WkPositionY: Integer;
begin
  // マルチモニタ対応の有効面の取得
  WkMonitorInfo := Self.MonitorInfo;

  WkPositionX := WkMonitorInfo.rcWork.Left;
  WkPositionY := WkMonitorInfo.rcWork.Top;

  WkSizeX :=
    WkMonitorInfo.rcWork.Right - WkMonitorInfo.rcWork.Left;
  WkSizeY :=
    Min(FMaxSizeY,
    WkMonitorInfo.rcWork.Bottom - WkMonitorInfo.rcWork.Top);

  msg.MinMaxInfo.ptMaxSize.X := WkSizeX;
  msg.MinMaxInfo.ptMaxSize.Y := WkSizeY;

  // GetMonitorInfo,Self.Top,Self.Leftで帰ってくる値は
  // プライマリモニタの左上を原点(0,0)とした値が帰ってくるが、
  // msg.MinMaxInfo.ptMaxPositionは
  // BorderStyleがbsSizeableおよびbsSingleの場合のみ
  // 各種モニタごとの有効面の左上を原点(0,0)
  // とした座標を指定しなければいけない
  // それ以外のBorderStyleでは
  // 各種モニタごとの左上を原点(0,0)とした座標を指定する。
  if (TargetForm.BorderStyle = bsSizeable) or
    (TargetForm.BorderStyle = bsSingle) or
    (FSetWMGetMinMaxInfo2 = True) then
  begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcWork.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcWork.Top;
    FSetWMGetMinMaxInfo2 := False;
  end
  else begin
    WkPositionX := WkPositionX - WkMonitorInfo.rcMonitor.Left;
    WkPositionY := WkPositionY - WkMonitorInfo.rcMonitor.Top;
  end;
  msg.MinMaxInfo.ptMaxPosition.X :=
    WkPositionX;
  msg.MinMaxInfo.ptMaxPosition.Y :=
    WkPositionY;
end;

{ TMonitorInfoParam }

function TMonitorInfoParam.DisplayMonitorInfo: TMonitorInfo;
begin
  GetMonitorInfoParam;
  result := FDisplayMonitorInfo;
end;


procedure TMonitorInfoParam.GetMonitorInfoParam;
begin
  // マルチモニタ対応のモニタ情報の取得(プライマリモニタの左上とした座標)
  ZeroMemory(@FMonitorInfo, sizeof(FMonitorInfo));
  FMonitorInfo.cbSize := SizeOf(FMonitorInfo);
  if GetMonitorInfo(FTargetForm.Monitor.Handle, @FMonitorInfo) = False then
  begin
    // 念のためリトライ(いるのかな・・・?)
    Sleep(100);
    if GetMonitorInfo(FTargetForm.Monitor.Handle, @FMonitorInfo) = False then
    begin
      raise Exception.Create('APIのGetMonitorInfo関数でエラーが発生しました' + #13#10 +
        'プログラミングエラーです、開発者にお問い合わせください');
    end;
  end;

  // 現在のモニタの左上を原点とした座標の取得
  FDisplayMonitorInfo.cbSize := SizeOf(FDisplayMonitorInfo);
  FDisplayMonitorInfo.dwFlags := FMonitorInfo.dwFlags;
  FDisplayMonitorInfo.rcMonitor.Top := 0;
  FDisplayMonitorInfo.rcMonitor.Left := 0;
  FDisplayMonitorInfo.rcMonitor.Bottom :=
    FMonitorInfo.rcMonitor.Bottom - FMonitorInfo.rcMonitor.Top;
  FDisplayMonitorInfo.rcMonitor.Right :=
    FMonitorInfo.rcMonitor.Right - FMonitorInfo.rcMonitor.Left;
  FDisplayMonitorInfo.rcWork.Top :=
    FMonitorInfo.rcWork.Top - FMonitorInfo.rcMonitor.Top;
  FDisplayMonitorInfo.rcWork.Left :=
    FMonitorInfo.rcWork.Left - FMonitorInfo.rcMonitor.Left;
  FDisplayMonitorInfo.rcWork.Bottom :=
    FMonitorInfo.rcWork.Bottom - FMonitorInfo.rcMonitor.Top;
  FDisplayMonitorInfo.rcWork.Right :=
    FMonitorInfo.rcWork.Right - FMonitorInfo.rcMonitor.Left;
end;

function TMonitorInfoParam.MonitorCount: Integer;
begin
  result := Screen.MonitorCount;
end;

function TMonitorInfoParam.MonitorInfo: TMonitorInfo;
begin
  GetMonitorInfoParam;
  result := FMonitorInfo;
end;

function TMonitorInfoParam.MonitorNum: Integer;
begin
  result := FTargetForm.Monitor.MonitorNum;
end;

procedure TMonitorInfoParam.SetTargetForm(const Value: TForm);
begin
  FTargetForm := Value;
end;

end.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, TypInfo, MultiMon, Math, MultiMonitorParam;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    MaxAlignRadioGroup: TRadioGroup;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Panel1Resize(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure MaxAlignRadioGroupClick(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
    FMultiMonitorMaxInfoCalc: TMultiMonitorMaxInfoCalc;

    // 最大化時にWM_GETMINMAXINFOでフォームの大きさを調整した際に
    // フォームが移動できてしまうのを防止するためのコード
    procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND;

    // フォームの大きさと位置の制限
    procedure WMGetMinMaxInfo(var msg : TWMGetMinMaxInfo); message WM_GETMINMAXINFO;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.WMSysCommand(var Message: TWMSysCommand);
begin
  if IsZoomed(Self.Handle) and ((Message.CmdType and $FFF0) = SC_MOVE) then
  begin
    Exit;
  end;
  inherited;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  WkMonitorInfo: TMonitorInfo;
  WkDisplayMonitorInfo: TMonitorInfo;
begin
  //マルチモニタ対応のGetMonitorInfoを使う
  WkMonitorInfo := FMultiMonitorMaxInfoCalc.MonitorInfo;
  WkDisplayMonitorInfo := FMultiMonitorMaxInfoCalc.DisplayMonitorInfo;

  ShowMessage(
    '●モニタの情報' + #13#10 +
    'モニタの数(1~?):' +
    IntToStr(FMultiMonitorMaxInfoCalc.MonitorCount) + #13#10 + 'フォームが表示されているモニタ番号(0~?):' + IntToStr(Self.Monitor.MonitorNum) + #13#10#13#10 +
    '●モニタの位置とサイズを表す(プライマリモニタ左上原点)' + #13#10 +
    'Left  :' + IntToStr(WkMonitorInfo.rcMonitor.Left) + #13#10 +
    'Top   :' + IntToStr(WkMonitorInfo.rcMonitor.Top) + #13#10 +
    'Right :' + IntToStr(WkMonitorInfo.rcMonitor.Right) + #13#10 +
    'Bottom:' + IntToStr(WkMonitorInfo.rcMonitor.Bottom) + #13#10#13#10 +
    '●有効面のモニタの位置とサイズを表す(プライマリモニタ左上原点)' + #13#10 +
    'Left  :' + IntToStr(WkMonitorInfo.rcWork.Left) + #13#10 +
    'Top   :' + IntToStr(WkMonitorInfo.rcWork.Top) + #13#10 +
    'Right :' + IntToStr(WkMonitorInfo.rcWork.Right) + #13#10 +
    'Bottom:' + IntToStr(WkMonitorInfo.rcWork.Bottom) + #13#10#13#10 +
    '●モニタの位置とサイズを表す(各種モニタ左上原点)' + #13#10 +
    'Left  :' + IntToStr(WkDisplayMonitorInfo.rcMonitor.Left) + #13#10 +
    'Top   :' + IntToStr(WkDisplayMonitorInfo.rcMonitor.Top) + #13#10 +
    'Right :' + IntToStr(WkDisplayMonitorInfo.rcMonitor.Right) + #13#10 +
    'Bottom:' + IntToStr(WkDisplayMonitorInfo.rcMonitor.Bottom) + #13#10#13#10 +
    '●有効面のモニタの位置とサイズを表す(各種モニタ左上原点)' + #13#10 +
    'Left  :' + IntToStr(WkDisplayMonitorInfo.rcWork.Left) + #13#10 +
    'Top   :' + IntToStr(WkDisplayMonitorInfo.rcWork.Top) + #13#10 +
    'Right :' + IntToStr(WkDisplayMonitorInfo.rcWork.Right) + #13#10 +
    'Bottom:' + IntToStr(WkDisplayMonitorInfo.rcWork.Bottom));
end;

procedure TForm1.WMGetMinMaxInfo(var msg: TWMGetMinMaxInfo);
begin
  // OnCreateする前にイベントが来てしまう処置
  if FMultiMonitorMaxInfoCalc <> nil then
  begin
    FMultiMonitorMaxInfoCalc.WMGetMinMaxInfo(msg);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FMultiMonitorMaxInfoCalc := TMultiMonitorMaxInfoCalc.Create;
  FMultiMonitorMaxInfoCalc.TargetForm := Self;
  FMultiMonitorMaxInfoCalc.MaxSizeX := 800;
  FMultiMonitorMaxInfoCalc.MaxSizeY := 800;
  FMultiMonitorMaxInfoCalc.MaxPositionX := 10;
  FMultiMonitorMaxInfoCalc.MaxPositionY := 10;

  MaxAlignRadioGroup.ItemIndex := Ord(FMultiMonitorMaxInfoCalc.MaxAlign);
end;

procedure TForm1.Panel1Resize(Sender: TObject);
begin
  Self.Caption := Self.Caption + 'r';
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FMultiMonitorMaxInfoCalc);
end;

procedure TForm1.MaxAlignRadioGroupClick(Sender: TObject);
begin
  FMultiMonitorMaxInfoCalc.MaxAlign := TAlign(MaxAlignRadioGroup.ItemIndex);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ShowMessage(
    '●フォームの情報' + #13#10 +
    'Top:' + IntToStr(Self.Top) + #13#10 +
    'Left:' + IntToStr(Self.Left) + #13#10 +
    'Width:' + IntToStr(Self.Width) + #13#10 +
    'Height:' + IntToStr(Self.Height) + #13#10 +
    'WindowState:' + GetEnumName(TypeInfo(TWindowState), Ord(Self.WindowState)));
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  FMultiMonitorMaxInfoCalc.SetFormSafePosition;
end;

end.