「TIPS:スクリーン座標←→クライアント座標の変換を行うには?」では、スクリーン座標とクライアント座標の違いを説明し、両者間で座標を相互の座標系の値に変換する方法を説明した。その中で、クライアント座標とは、Windowsアプリケーションが持つ「ウィンドウ領域」の中に含まれる描画可能な領域(クライアント領域)を基準にした座標系であると説明した(ここではクライアント<座標>とクライアント<領域>という単語の違いに注意すること)。
それではこのウィンドウ領域とクライアント領域は、どこが違うのだろうか。またこれらの領域は、.NETプログラミングではどのようにして取得すればよいのだろうか。本稿ではこれらの内容について解説する。
ウィンドウ領域とクライアント領域の違い
Windows OSレベルでは、デスクトップ上のGUI要素はすべてウィンドウとして管理されている。例えばボタンなどのコントロールも1つのウィンドウである。このウィンドウ全域がウィンドウ領域である。すなわちウィンドウ領域を求めることは、ウィンドウ内部とその外部との境界領域(Bounds)を求めることに等しい。
各ウィンドウは、それぞれその内部に(プログラマーが自由にコントロールを配置したり、描画したりすることが可能な)クライアント領域を持っている。また、クライアント領域以外の部分(スクロール・バーや、境界線、タイトル・バー、メニュー・バーなど)は「非クライアント領域」と呼ばれる。つまり「ウィンドウ領域」は「クライアント領域」+「非クライアント領域」で成り立っている。
次の画面では、このウィンドウ領域とクライアント領域の違いが示されている。
ウィンドウ領域とクライアント領域の違い
ウィンドウ内部と外部の境界領域が「ウィンドウ領域」(Bounds)で、ウィンドウ領域内のプログラムから描画可能な部分が「クライアント領域」(ClientRectangle)である。
(1)フォームのクライアント領域。境界線やタイトル・バーなどの要素を除いた領域で、プログラムから描画可能な領域である。
(2)フォームのウィンドウ領域。クライアント領域に境界線やタイトル・バーなどの要素を含めた領域で、外部との境界となる領域である。
(3)ボタンのクライアント領域。ボタンの場合は境界線やタイトル・バーなどの要素がないため(4)のウィンドウ領域と同じになる。
(4)ボタンのウィンドウ領域。
それでは、これらのクライアント領域とウィンドウ領域(のサイズおよび位置)を、実際にプログラムから取得してみよう。なお本稿では、それぞれのウィンドウのクライアント座標により、これらの領域の値を取得する。
クライアント領域を取得するには?
まずはフォームやコントロールなどのウィンドウのクライアント領域を取得しよう。これには、ウィンドウのClientRectangleプロパティ(データ型はRectangle型)を参照すればよい。ちなみにこのプロパティは、読み取り専用で設定はできない。
以下のサンプル・コードは、実際にClientRectangleプロパティを活用してウィンドウのクライアント領域を取得するメソッドである。このメソッドのパラメータにボタンなどのコントロールもしくはフォームのオブジェクトを指定して呼び出すと、その戻り値としてクライアント領域をRectangle型で取得することができる。
using System.Diagnostics;
// ウィンドウのクライアント領域を取得する
private Rectangle GetClientRectangle(Control ctrl)
{
//////////////////////////////////////////////////
// 検証用のコード
// ClientRectangle.Locationプロパティ=Point(0, 0)
// ClientRectangle.Sizeプロパティ=ClientSizeプロパティ
Debug.Assert(ctrl.ClientRectangle.Location == new Point(0, 0));
Debug.Assert(ctrl.ClientRectangle.Size == ctrl.ClientSize);
//////////////////////////////////////////////////
return ctrl.ClientRectangle;
}
' ウィンドウのクライアント領域を取得する
Private Function GetClientRectangle(ByVal ctrl As Control) As Rectangle
'////////////////////////////////////////////////
' 検証用のコード
' ClientRectangle.Locationプロパティ=Point(0, 0)
' ClientRectangle.Sizeプロパティ=ClientSizeプロパティ
Debug.Assert(ctrl.ClientRectangle.Location.Equals(New Point(0, 0)))
Debug.Assert(ctrl.ClientRectangle.Size.Equals(ctrl.ClientSize))
'////////////////////////////////////////////////
Return ctrl.ClientRectangle
End Function
「クライアント領域の左上隅の原点」(=ClientRectangle.Locationプロパティ)は当然ながら常に(0, 0)(つまり、X座標が「0」、Y座標が「0」)である。
なお、「クライアント領域のサイズ」(=ClientRectangle.Sizeプロパティ)はよく使われるため、わざわざClientRectangleプロパティを呼び出さなくても、ウィンドウのClientSizeプロパティで直接取得することもできる。
上記コード中の「//////……」で囲まれている部分のコードは、この2つの内容を検証するためのデバッグ・コードである。ここで使われているDebugクラス(System.Diagnostics名前空間)の静的なAssertメソッドは、パラメータの(条件式の)値がfalseのときに、警告メッセージ(=Assert)を表示させるためのものだ。
ちなみに、このClientSizeプロパティは読み取り専用ではないため、値を設定することも可能だ。つまりこのプロパティからクライアント領域のサイズを指定することができるわけだ。
ClientSizeプロパティによりクライアント領域のサイズを指定した場合、そのクライアント領域を持つウィンドウ領域のサイズも自動的に変更される。例えばフォームのロード時に次のようなコードを呼び出すとしよう。
this.ClientSize =new Size(100, 100);
すると本稿のサンプル・アプリケーションでは、クライアント領域のサイズは指定どおり(100, 100)に変わり、ウィンドウ領域のサイズは自動的に(123, 134)に変更される(Windows XP上の筆者の環境の場合。ウィンドウ領域のサイズはOS環境によって異なる可能性がある)。
ウィンドウ領域を取得・設定するには?
次にフォームやコントロールなどのウィンドウのウィンドウ領域を取得してみよう。これには、ウィンドウのBoundsプロパティ(データ型はRectangle型)を参照すればよい。ちなみにこのプロパティは、取得だけでなく設定も可能である。
このBoundsプロパティのウィンドウ領域の値は、そのウィンドウがフォームかコントロールかによって意味(座標系)が異なるので注意が必要だ。
フォームの場合、Boundsプロパティの値は<スクリーン座標>で取得される。このため、先ほどのメソッドなどで取得した「フォームのクライアント領域」と比較するならば、フォームのクライアント座標へ変換する必要がある。この変換方法は、前掲のTIPSで紹介したとおりだ。本稿では説明を割愛する。
対してコントロールの場合、Boundsプロパティの値は<親コンテナのクライアント座標>で取得される。従って、先ほどのメソッドなどで取得した「ボタンのクライアント領域」と比較するならば、いったん(<親コンテナ>のクライアント座標から)スクリーン座標に変換して、それをさらに<ボタン>のクライアント座標に変換する必要がある。
ちなみに親コンテナは、そのコントロールのParentプロパティからControlクラス(System.Windows.Forms名前空間)のオブジェクトとして取得できる。フォーム上に直接コントロールを配置した場合は、そのコントロールの親コンテナはそのフォームである。パネルなどのコンテナ上にコントロールを配置すれば、そのコンテナが親コンテナとなる。
以上の内容を実際に実装したのが、次のサンプル・コードのメソッドである。このメソッドでは、そのパラメータにボタンなどのコントロールもしくはフォームのオブジェクトを指定して呼び出すと、その戻り値としてウィンドウ領域をRectangle型で取得することができる。
// ウィンドウのウィンドウ領域を取得する
private Rectangle GetWindowRectangle(Control ctrl)
{
//////////////////////////////////////////////////
// 検証用のコード
// Bounds.Locationプロパティ=Locationプロパティ
// =Point(Leftプロパティ, Topプロパティ)
// Bounds.Sizeプロパティ=Sizeプロパティ
// =Size (Rightプロパティ - Leftプロパティ,
// Bottomプロパティ - Topプロパティ)
Debug.Assert(ctrl.Bounds.Location == ctrl.Location);
Debug.Assert(ctrl.Location == new Point(ctrl.Left, ctrl.Top));
Debug.Assert(ctrl.Bounds.Size == ctrl.Size);
Debug.Assert(ctrl.Size ==
new Size(ctrl.Right - ctrl.Left, ctrl.Bottom - ctrl.Top));
//////////////////////////////////////////////////
if (ctrl is Form) // フォームかどうか
{
// フォームの左上隅の原点を(クライアント座標で)取得する
Point winRectLocation =
ctrl.PointToClient(ctrl.Bounds.Location);
// フォームのウィンドウ領域を(クライアント座標で)取得する
Rectangle winRect =
new Rectangle(winRectLocation, ctrl.Bounds.Size);
return winRect;
}
else
{
// コントロールの左上隅の原点を(スクリーン座標で)取得する
Point screenLocation =
ctrl.Parent.PointToScreen(ctrl.Bounds.Location);
// スクリーン座標をコントロールのクライアント座標に変換する
Point winRectLocation =
ctrl.PointToClient(screenLocation);
// コントロールのウィンドウ領域を(クライアント座標で)取得する
Rectangle winRect =
new Rectangle(winRectLocation, ctrl.Bounds.Size);
return winRect;
}
}
' ウィンドウのウィンドウ領域を取得する
Private Function GetWindowRectangle(ByVal ctrl As Control) As Rectangle
'////////////////////////////////////////////////
' 検証用のコード
' Bounds.Locationプロパティ=Locationプロパティ
' =Point(Leftプロパティ, Topプロパティ)
' Bounds.Sizeプロパティ=Sizeプロパティ
' =Size (Rightプロパティ - Leftプロパティ,
' Bottomプロパティ - Topプロパティ)
Debug.Assert(ctrl.Bounds.Location.Equals(ctrl.Location))
Debug.Assert(ctrl.Location.Equals(New Point(ctrl.Left, ctrl.Top)))
Debug.Assert(ctrl.Bounds.Size.Equals(ctrl.Size))
Debug.Assert(ctrl.Size.Equals( _
New Size(ctrl.Right - ctrl.Left, ctrl.Bottom - ctrl.Top)))
'////////////////////////////////////////////////
If TypeOf ctrl Is Form Then ' フォームかどうか
' フォームの左上隅の原点を(クライアント座標で)取得する
Dim winRectLocation As Point = _
ctrl.PointToClient(ctrl.Bounds.Location)
' フォームのウィンドウ領域を(クライアント座標で)取得する
Dim winRect As Rectangle = _
New Rectangle(winRectLocation, ctrl.Bounds.Size)
Return winRect
Else
' コントロールの左上隅の原点を(スクリーン座標で)取得する
Dim screenLocation As Point = _
ctrl.Parent.PointToScreen(ctrl.Bounds.Location)
' スクリーン座標をコントロールのクライアント座標に変換する
Dim winRectLocation As Point = _
ctrl.PointToClient(screenLocation)
' コントロールのウィンドウ領域を(クライアント座標で)取得する
Dim winRect As Rectangle = _
New Rectangle(winRectLocation, ctrl.Bounds.Size)
Return winRect
End If
End Function
コード中にあるis演算子(C#)やTypeOf〜Is演算子(VB.NET)は、オブジェクトがその型に属するかどうかをBool値(Boolean)で返すためのものだ。
「ウィンドウ領域の左上隅の原点」(=Bounds.Locationプロパティ)は、スクリーン上でのフォームの位置を指定したり、フォームなどの親コンテナ上でのコントロールの位置を指定したりするのに使われるので、簡単に取得・設定するためのLocationプロパティが用意されている。
また、「ウィンドウ領域のサイズ」(=Bounds.Sizeプロパティ)も同様によく使われるため、ウィンドウのSizeプロパティで取得・設定できるようになっている。
先ほどのメソッドと同じように、上記コード中の「//////……」で囲まれているコードは、これらの内容を検証するためのものだ。
なおコントロールのサイズ指定では、通常はこのSizeプロパティが用いられる。しかしフォームのサイズ指定の場合はSizeプロパティではなく、前述したClientSizeプロパティが用いられることが一般的だ。これは、Windowsフォームのデザインをするうえで実際にコントロールなどを配置したり描画したりできる「フォームのクライアント領域」のサイズを指定した方が、開発者にとってより扱いやすいからだろう。
サンプル・プログラムの実装と実行
以上で説明した「クライアント領域を取得するメソッド」と「ウィンドウ領域を取得するメソッド」のサンプル・コードを実際に利用して、そこで得た領域情報をラベルに設定しているのが、次のサンプル・プログラムだ。なおソース・コード全体は、このサンプル・プログラムの説明欄のリンク(下の「C#版のダウンロード」「VB.NET版のダウンロード」)からダウンロードできる。
private void Form1_Load(object sender, System.EventArgs e)
{
// フォームのクライアント領域情報をラベルに表示する
label1.Text = GetClientRectangle(this).ToString();
// フォームのウィンドウ領域情報をラベルに表示する
label2.Text = GetWindowRectangle(this).ToString();
// ボタンのクライアント領域の情報をラベルに表示する
label3.Text = GetClientRectangle(button1).ToString();
// ボタンのウィンドウ領域情報をラベルに表示する
label4.Text = GetWindowRectangle(button1).ToString();
}
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' フォームのクライアント領域情報をラベルに表示する
label1.Text = GetClientRectangle(Me).ToString()
' フォームのウィンドウ領域情報をラベルに表示する
label2.Text = GetWindowRectangle(Me).ToString()
' ボタンのクライアント領域の情報をラベルに表示する
label3.Text = GetClientRectangle(button1).ToString()
' ボタンのウィンドウ領域情報をラベルに表示する
label4.Text = GetWindowRectangle(button1).ToString()
End Sub
このサンプル・プログラムを実行すると、冒頭で示した画面のサンプル・アプリケーションが実行される。
カテゴリ:Windowsフォーム 処理対象:ウィンドウ
使用ライブラリ:Rectangle構造体(System.Drawing名前空間)
使用ライブラリ:Debugクラス(System.Diagnostics名前空間)
使用ライブラリ:Controlクラス(System.Windows.Forms名前空間)
関連TIPS:スクリーン座標←→クライアント座標の変換を行うには?
Copyright© Digital Advantage Corp. All Rights Reserved.