DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

フォームが移動できる範囲を制限する

例えば、フォームがデスクトップ領域の外に出ないようにしたり、フォームが画面の上部にくっついたようにしか移動できないようにしたり、垂直、あるいは水平にしか移動できないようにするといったように、フォームが特定の箇所にしか移動できないようにする方法を紹介します。

フォーム移動後に位置を修正する方法

簡単なのは、「フォームの位置や大きさが変更されたことを知る」と「フォームの位置と大きさを取得、変更する」で紹介している方法を使って、フォームが移動した直後にフォームの位置を修正するという方法です。

以下の例では、LocationChangedイベントを使用して、フォームがプライマリディスプレイの作業領域の外に移動できないようにしています。

VB.NET
コードを隠すコードを選択
'LocationChangedイベントハンドラ
Private Sub Form1_LocationChanged(sender As Object, e As EventArgs) _
    Handles MyBase.LocationChanged

    'フォームが最小化、最大化状態の時は無視
    If Me.WindowState <> FormWindowState.Normal Then
        Return
    End If

    'フォームが移動できる範囲
    Dim area As Rectangle = Screen.PrimaryScreen.WorkingArea

    'フォームの大きさが移動できる範囲より大きくならないようにする
    If area.Width < Me.Width Then
        Me.Width = area.Width
    End If
    If area.Height < Me.Height Then
        Me.Height = area.Height
    End If

    'フォームの左の位置を修正
    If Me.Left < area.Left Then
        Me.Left = area.Left
    ElseIf area.Left + area.Width < Me.Left + Me.Width Then
        Me.Left = area.Left + area.Width - Me.Width
    End If

    'フォームの上の位置を修正
    If Me.Top < area.Top Then
        Me.Top = area.Top
    ElseIf area.Top + area.Height < Me.Top + Me.Height Then
        Me.Top = area.Top + area.Height - Me.Height
    End If
End Sub
C#
コードを隠すコードを選択
//LocationChangedイベントハンドラ
private void Form1_LocationChanged(object sender, EventArgs e)
{
    //フォームが最小化、最大化状態の時は無視
    if (this.WindowState != FormWindowState.Normal)
    {
        return;
    }

    //フォームが移動できる範囲
    Rectangle area = Screen.PrimaryScreen.WorkingArea;

    //フォームの大きさが移動できる範囲より大きくならないようにする
    if (area.Width < this.Width)
    {
        this.Width = area.Width;
    }
    if (area.Height < this.Height)
    {
        this.Height = area.Height;
    }

    //フォームの左の位置を修正
    if (this.Left < area.Left)
    {
        this.Left = area.Left;
    }
    else if (area.Left + area.Width < this.Left + this.Width)
    {
        this.Left = area.Left + area.Width - this.Width;
    }

    //フォームの上の位置を修正
    if (this.Top < area.Top)
    {
        this.Top = area.Top;
    }
    else if (area.Top + area.Height < this.Top + this.Height)
    {
        this.Top = area.Top + area.Height - this.Height;
    }
}

WM_MOVINGメッセージを使用する方法

上記の方法では、ユーザーがフォームを移動した直後にフォームが再び移動するため、見た目はよくありません。「フォームの位置や大きさが変更されたことを知る」で紹介しているWM_MOVINGを使用すれば、この問題は解消されます。

この方法は、ユーザーがフォームを移動させる場合にだけ有効です。ユーザーがフォームをリサイズする場合も考慮するなら、WM_SIZINGも使用する必要があります。また、プログラムによってフォームを移動させる場合も考慮するのであれば、上記LocationChangedイベントを使用した方法も併用してください。

以下の例では、この方法を使ってフォームがプライマリディスプレイの作業領域の外に移動できないようにしています。ここではユーザーがフォームをリサイズすることも考慮して、WM_MOVINGとWM_SIZINGを監視しています。

VB.NET
コードを隠すコードを選択
'Imports System.Runtime.InteropServices
'Imports System.Security.Permissions

<StructLayout(LayoutKind.Sequential)> _
Private Structure RECT
    Private _left As Integer
    Private _top As Integer
    Private _right As Integer
    Private _bottom As Integer

    Public Property Left() As Integer
        Get
            Return _left
        End Get
        'Widthを維持してLeftを変更
        Set(value As Integer)
            _right = _right - _left + value
            _left = value
        End Set
    End Property
    Public Property Top() As Integer
        Get
            Return _top
        End Get
        Set(value As Integer)
            _bottom = _bottom - _top + value
            _top = value
        End Set
    End Property

    Public Property Width() As Integer
        Get
            Return _right - _left
        End Get
        Set(value As Integer)
            _right = _left + value
        End Set
    End Property
    Public Property Height() As Integer
        Get
            Return _bottom - Top
        End Get
        Set(value As Integer)
            _bottom = Top + value
        End Set
    End Property
End Structure

<SecurityPermission(SecurityAction.Demand, _
    Flags:=SecurityPermissionFlag.UnmanagedCode)> _
Protected Overrides Sub WndProc(ByRef m As Message)
    Const WM_SIZING As Integer = &H214
    Const WM_MOVING As Integer = &H216

    If m.Msg = WM_MOVING OrElse m.Msg = WM_SIZING Then
        'フォームが移動できる範囲
        Dim area As Rectangle = Screen.PrimaryScreen.WorkingArea

        'フォームの位置を取得
        Dim rect As RECT = CType( _
            System.Runtime.InteropServices.Marshal.PtrToStructure( _
                m.LParam, GetType(RECT)), RECT)

        'フォームの大きさが移動できる範囲より大きくならないようにする
        If area.Width < rect.Width Then
            rect.Width = area.Width
        End If
        If area.Height < rect.Height Then
            rect.Height = area.Height
        End If

        'フォームの左の位置を修正
        If rect.Left < area.Left Then
            rect.Left = area.Left
        ElseIf area.Left + area.Width < rect.Left + rect.Width Then
            rect.Left = area.Left + area.Width - rect.Width
        End If

        'フォームの上の位置を修正
        If rect.Top < area.Top Then
            rect.Top = area.Top
        ElseIf area.Top + area.Height < rect.Top + rect.Height Then
            rect.Top = area.Top + area.Height - rect.Height
        End If

        'フォームの位置を設定
        System.Runtime.InteropServices.Marshal.StructureToPtr( _
            rect, m.LParam, True)
    End If

    MyBase.WndProc(m)
End Sub
C#
コードを隠すコードを選択
//using System.Runtime.InteropServices;
//using System.Security.Permissions;

[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
    private int _left;
    private int _top;
    private int _right;
    private int _bottom;

    public int Left
    {
        get { return _left; }
        //Widthを維持してLeftを変更
        set
        {
            _right = _right - _left + value ;
            _left = value;
        }
    }
    public int Top
    {
        get { return _top; }
        set
        {
            _bottom = _bottom - _top + value;
            _top = value;
        }
    }

    public int Width
    {
        get { return _right - _left; }
        set { _right = _left + value; }
    }
    public int Height
    {
        get { return _bottom - Top; }
        set { _bottom = Top + value; }
    }
}

[SecurityPermission(SecurityAction.Demand,
    Flags = SecurityPermissionFlag.UnmanagedCode)]
protected override void WndProc(ref Message m)
{
    const int WM_SIZING = 0x214;
    const int WM_MOVING = 0x216;

    if (m.Msg == WM_MOVING || m.Msg == WM_SIZING)
    {
        //フォームが移動できる範囲
        Rectangle area = Screen.PrimaryScreen.WorkingArea;

        //フォームの位置を取得
        RECT rect = (RECT)
            System.Runtime.InteropServices.Marshal.PtrToStructure(
            m.LParam, typeof(RECT));

        //フォームの大きさが移動できる範囲より大きくならないようにする
        if (area.Width < rect.Width)
        {
            rect.Width = area.Width;
        }
        if (area.Height < rect.Height)
        {
            rect.Height = area.Height;
        }

        //フォームの左の位置を修正
        if (rect.Left < area.Left)
        {
            rect.Left = area.Left;
        }
        else if (area.Left + area.Width < rect.Left + rect.Width)
        {
            rect.Left = area.Left + area.Width - rect.Width;
        }

        //フォームの上の位置を修正
        if (rect.Top < area.Top)
        {
            rect.Top = area.Top;
        }
        else if (area.Top + area.Height < rect.Top + rect.Height)
        {
            rect.Top = area.Top + area.Height - rect.Height;
        }

        //フォームの位置を設定
        System.Runtime.InteropServices.Marshal.StructureToPtr(
            rect, m.LParam, true);
    }

    base.WndProc(ref m);
}

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。