フォームを画面の中央に配置する

タグの編集
投稿者 FORZA  (社会人) 投稿日時 2008/12/16 18:47:27
VB2008、初心者です。

フォームの表示位置についてですが、初期表示位置はStartPositionプロパティで設定できますが
フォームを表示した後、任意のタイミングで画面の中央に配置させるにはどうしたら良いでしょうか?

よろしくお願いします。
投稿者 るしぇ  (社会人) 投稿日時 2008/12/16 22:13:15
とりあえず、初期表示位置が中央なら、その座標を
変数に覚えておくだけでもできちゃうね。
投稿者 るきお  (社会人) 投稿日時 2008/12/16 22:29:57
こんにちは。

とりあえず以下でできます。
もっとスマートは書き方はないでしょうか?

   
Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click

    Dim screenSize As Size = Screen.PrimaryScreen.Bounds.Size
    Me.Location = New Point((screenSize.Width - Me.Width) \ 2, (screenSize.Height - Me.Height) \ 2)

End Sub
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2008/12/16 22:52:48
私なら、こう書くかな。

るきおさんの案(2行)に比べると、長く(3行)なっていますが、
 ・マルチモニタの場合、常に第1画面に移動してしまう。
   → 現在表示させている画面上の中央に配置。
 ・フォームが大きい場合、タスクバーの上に重なって配置されてしまう。
   → タスクバーを除いた、作業領域内に配置するようにした。
という点が異なります。(ついでに、画面解像度の変更に対応させる方法も書いていますが)


作成したいアプリの仕様に応じて、適当に手を加えながら使ってみてください。

たとえば、「常に最前面に表示させるアプリ」の場合は、作業領域ではなく、
タスクバーを無視したデスクトップ全体を利用した方が都合が良い場合があるでしょう。
他には、現在の画面ではなく、全画面領域(仮想画面)の中央に配置したい場合とか、
あるいは、現在の画面には大きすぎるが、隣の画面になら収まるので隣に表示…と
いった複雑な条件を付与したい場合もあるかも知れませんしね。


Public Class Form1
    Private Sub MoveToCenter()
        '現在のデスクトップを取得(マルチモニタの場合は、フォームの位置に応じた画面) 
        Dim r As Rectangle = Screen.FromRectangle(Me.DesktopBounds).WorkingArea
        '中央に配置 
        Dim s As Size = r.Size - Me.Size
        Me.SetDesktopLocation(r.Left + s.Width \ 2, r.Top + s.Height \ 2)
    End Sub

    Private Sub Button1_Click(ByVal sender As ObjectByVal e As EventArgs) Handles Button1.Click
        MoveToCenter()
    End Sub

    Private Sub Form1_Load(ByVal sender As ObjectByVal e As EventArgs) Handles Me.Load
        Button1.Text = "中央に配置"
        '画面解像度が変更された場合に、自動的に位置調整するなら、下記コメントを解除。 
        'AddHandler Microsoft.Win32.SystemEvents.DisplaySettingsChanged, AddressOf Button1_Click 
    End Sub

    Private Sub Form1_FormClosing(ByVal sender As ObjectByVal e As FormClosingEventArgs) Handles Me.FormClosing
        'RemoveHandler Microsoft.Win32.SystemEvents.DisplaySettingsChanged, AddressOf Button1_Click 
    End Sub
End Class
投稿者 FORZA  (社会人) 投稿日時 2008/12/16 22:54:10
るしぇさん
>とりあえず、初期表示位置が中央なら、その座標を
>変数に覚えておくだけでもできちゃうね。
1つ書き忘れました。フォームのサイズが変更されている場合も考慮したいので、
初期表示位置とは必ずしも一致しないのです。すみません・・・。

るきおさん
画面のサイズを取得する方法がわからなかったので助かりました。
でも確かにもっとスマートなやり方があってもよさそうですね・・・。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2008/12/16 23:02:02
> でも確かにもっとスマートなやり方があってもよさそうですね・・・。 

短く書く方法はありますよ。
MyBase.CenterToScreen()
ただ、「開発者が利用するためのメソッドではなく、.NET が内部的に利用するためのメソッド」なので、これがスマートな方法かと問われると、聊か懐疑的ですけれども。
投稿者 FORZA  (社会人) 投稿日時 2008/12/16 23:40:46
魔界の仮面弁士さん、ありがとうございます。
るきおさんのと合わせて参考にさせて頂きました。

CenterToScreen()は是非使いたいところなのですが、
これは対象となるフォームのクラス外(モジュールの関数内等)でも使えますか?
(ヘルプの「直接これを呼び出さないでください。」が気になりますが。。)
投稿者 neptune  (社会人) 投稿日時 2008/12/17 00:40:15
横から失礼します。

弁士さん>
>        '中央に配置 
>        Dim s As Size = r.Size - Me.Size
size構造体って演算できるんですね。新発見です。
構造体なのになんか不自然な感じはしますが、内部でメンバ毎に演算しているんでしょうね。
他にもこのような構造体ってあるのでしょうか?

横入り質問ですみません。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2008/12/17 01:04:15
> CenterToScreen()は是非使いたいところなのですが、

CenterToScreenは、私のコードともるきおさんのコードとも異なる動作になります。

るきおさんのコードは、常に第一画面に配置され、
私のコードは、現在のフォーム(の大部分)が配置されている画面に配置され、
CenterToScreen は、マウスカーソルのある画面に配置されます。

また、複数画面にまたがるような、巨大なフォームに対する動作も異なります。


> これは対象となるフォームのクラス外(モジュールの関数内等)でも使えますか?

Proteced メソッドなので、継承先のクラスでのみ使用可能です。

外部から呼びたいのであれば、継承先クラスに Public メソッドを用意するか、
あるいはリフレクションで呼び出すかの二拓になると思います。

たとえば上記のようにすると、外部から「フォーム.MoveToCenter()」で呼び出せるようになります。
「スマートなコード」とは程遠い気がしますけれどね。
(無理に外部から呼び出す程の物でもありませんし)

Imports System
Imports System.Reflection
Imports System.Windows.Forms
Imports System.Runtime.CompilerServices
Module Sample
    <Extension()> Public Sub MoveToCenter(ByVal f As Form)
        GetType(Form).InvokeMember("CenterToScreen", _
        BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.InvokeMethod, _
        Nothing, f, New Object() {})
    End Sub
End Module


> size構造体って演算できるんですね。新発見です。

Size 構造体に対する加減算は、.NET 1.0/VB.NET 2002の頃からサポートされていました。
しかし以前のバージョンでは、『演算子のオーバーロード』がサポートされていないため、減算処理には
Dim s As Size = Size.op_Subtraction(r.Size, Me.Size)
という表現が必要でした。
今のような
Dim s As Size = r.Size - Me.Size
という記述を行えるようになったのは、.NET 2.0/VB2005(およびそれ以降のバージョン)からです。

> 他にもこのような構造体ってあるのでしょうか?

加減算であれば、
 日付型(Date) ± 日付型(Date) = 期間(TimeSpan)
 日付型(Date) ± 期間(TimeSpan) = 日付型(Date)
とかですかね。
この他、代入演算子のオーバーロードもサポートされたため、
System.Data.SqlTypes.SqlDecimal や System.IntPtr 型などに、数値を
直接代入するような構文も許可されるようになっています。
投稿者 FORZA  (社会人) 投稿日時 2008/12/17 01:59:52
魔界の仮面弁士さん、ありがとうございます。
CenterToScreenを外部から呼び出すとなると
逆により複雑になってしまうようですね。

スマートさを重視したいとなるとやはり画面とフォームのサイズから
位置を算出してという方法になるでしょうか。
CenterToScreenについては「開発者が利用するためのメソッドではなく~」
というのもありますので、どの画面に表示するかについては状況に応じながら
とりあえずは先述の方法で進めていこうかと思います。


>Dim s As Size = r.Size - Me.Size
構造体の演算については私も驚きました。
以後、参考にさせて頂きます。
投稿者 neptune  (社会人) 投稿日時 2008/12/17 02:15:04
こんにちは

魔界の仮面弁士さん、ありがとうございました。
Size は楽チンですし、TimeSpanも便利そうですね。今後何か使うときは
活用したいと思います。

FORZAさんお邪魔しました。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2008/12/19 06:34:24
一ヶ所訂正。大嘘を書いてしまいました。

> 代入演算子のオーバーロード
ごめんなさい。代入演算子はオーバーロードできません。

SqlDecimal や IntPtr に、数値を直接代入できるのは、
型変換演算子がオーバーロードされているからであって、
代入演算子がオーバーロードされているからではありません。
投稿者 neptune  (社会人) 投稿日時 2008/12/19 07:50:28
魔界の仮面弁士 さん>

ありがとうございます。my虎の巻も訂正しておきます。