GetWindowRectとGetClientRectの座標系の違い
Windows APIでウィンドウの位置や大きさを扱うとき、GetWindowRectとGetClientRectは似た名前の関数に見えます。しかし、返される矩形の基準点と含まれる範囲が異なるため、用途を取り違えると描画位置やマウス処理がずれます。GetWindowRectとGetClientRectの座標系の違いを理解するには、画面座標とクライアント座標を分けて考えることが重要です。
特に、タイトルバーや境界線を含むウィンドウ全体を測るのか、アプリケーションが描画できる内部領域だけを測るのかで選択が変わります。さらに、高DPI環境、複数モニター、子ウィンドウを扱う場合は、座標変換APIも組み合わせなければなりません。
座標系を分けて考える
GetWindowRectは、ウィンドウの外側を基準にした矩形を返します。戻り値のRECTは通常、画面座標で表され、左上の位置はデスクトップ全体におけるウィンドウの位置です。したがって、別のトップレベルウィンドウと位置を比較したり、画面上にポップアップを配置したりするときに適しています。
一方、GetClientRectが返すのは、クライアント領域の幅と高さです。通常のトップレベルウィンドウでは、RECTのleftとtopは0になります。この0,0は画面の左上ではなく、そのウィンドウのクライアント領域の左上を意味します。右端と下端には幅と高さが入り、位置情報としてそのまま画面座標に使うことはできません。
GetWindowRectが返す範囲
GetWindowRectの矩形には、タイトルバー、境界線、メニュー領域などの非クライアント領域が含まれます。ウィンドウ全体を再配置する、画面端からの距離を調べる、外枠を含めたスナップ位置を計算するといった処理では、この関数の結果を利用します。
RECTの右端と下端は、一般的なWindowsの矩形処理で排他的な境界として扱われます。幅は right - left、高さは bottom - top で求めます。Vista以降の環境では、リサイズ用の見えない境界がウィンドウ矩形に含まれる場合もあるため、表示されている輪郭と値が完全に一致しないことがあります。
GetClientRectの原点とサイズ
GetClientRectは、描画対象となるクライアント領域の寸法を取得する関数です。WM_PAINTで受け取る描画領域、子コントロールの配置、内部座標によるヒットテストなど、ウィンドウ内部の処理に向いています。クライアント座標の原点は常にクライアント領域の左上です。
用途を整理すると、次のように判断できます。
- ウィンドウ全体の画面上の位置を知りたい場合はGetWindowRectを使う
- タイトルバーや枠を除いた描画領域の大きさにはGetClientRectを使う
- クライアント領域を画面上へ移す場合はClientToScreenを使う
- 画面座標をクライアント座標へ戻す場合はScreenToClientを使う
- 親子ウィンドウ間の変換にはMapWindowPointsを検討する
GetClientRectのrightとbottomは、クライアント領域の幅と高さを表す値として扱えます。たとえば右下隅を指定するときは、境界の排他性を考慮して、最後の有効なピクセルと混同しないようにします。
戻り値を比較する
2つの関数の違いは、基準座標、対象範囲、代表的な用途に分けると把握しやすくなります。次の比較では、標準的なトップレベルウィンドウを前提にしています。
| 項目 | GetWindowRect | GetClientRect |
|---|---|---|
| 主な対象 | ウィンドウ全体 | クライアント領域 |
| 原点 | 通常は画面座標 | クライアント領域の左上 |
| 非クライアント領域 | 含む | 含まない |
| leftとtop | 画面上の位置 | 通常は0, 0 |
| rightとbottom | 右下の画面座標 | 幅と高さ |
| 主な用途 | 移動、外枠、画面配置 | 描画、内部配置、入力判定 |
たとえばウィンドウの画面上の左上を取得したいのにGetClientRectを使うと、常に0,0に近い値しか得られません。反対に、クライアント領域のサイズを知るためにGetWindowRectを使うと、タイトルバーや枠の分だけ大きな値になります。
座標変換で位置を合わせる
クライアント領域の左上を画面座標へ変換するには、POINT構造体に0,0を設定してClientToScreenを呼び出します。これにより、クライアント領域の原点が画面上のどこにあるかを取得できます。GetWindowRectのleft,topと比較すれば、非クライアント領域のおおよその寸法も確認できます。
POINT pt = { 0, 0 };
ClientToScreen(hwnd, &pt);
RECT client;
GetClientRect(hwnd, &client);
int screenWidth = client.right - client.left;
int screenHeight = client.bottom - client.top;
子ウィンドウの座標を別のウィンドウ基準へ変換する場合は、MapWindowPointsが便利です。単純にleftとtopを足し引きする方法は、親子関係や階層が変わると破綻しやすいため、Windowsが提供する変換関数を使う方が安全です。
高DPIと複数モニターの注意点
高DPI環境では、プロセスのDPI認識設定によってGetWindowRectなどの座標が論理ピクセルで返されることがあります。Per-Monitor DPI Awareではモニターごとに倍率が変わるため、別モニターへ移動したときに座標やサイズの見え方が変化します。座標値だけでなく、DPIコンテキストも確認する必要があります。
複数モニター構成では、プライマリモニターの左側や上側にある画面が負の座標を持つことがあります。座標が0以上になるとは限らないため、独自に範囲を制限すると配置が壊れます。DPIや画面構成を含む環境差を確認する際は、Windows APIの資料に加えて、セマフォ資源管理の資料のような周辺APIの解説も役立つ場合があります。
実装前に確認しておきたい条件は次のとおりです。
- プロセスがDPI仮想化の影響を受けていないか確認する
- モニターの左側や上側にある負の座標を許容する
- GetWindowRectの外枠に見えないリサイズ境界が含まれる可能性を考える
- ウィンドウが最小化されている場合の矩形を個別に扱う
- スクリーン座標と論理座標を混在させない
実装時に覚えておきたい判断基準
画面上でウィンドウを移動、整列、保存、復元するならGetWindowRectが基本です。ツールチップや独自ポップアップをウィンドウの外側へ配置する場合も、画面座標を返すこの関数が使いやすいでしょう。ただし、実際に見えている枠だけを厳密に扱う場合は、DWM関連APIなど別の情報が必要になることがあります。
内部描画やコントロール配置ではGetClientRectを使い、必要なときだけClientToScreenやScreenToClientで座標系を変換します。入力イベントの座標も、メッセージによってクライアント座標または画面座標が異なるため、受け取った時点で基準を確認することが大切です。
APIの使い分けを定着させる
実装の判断を簡単にするには、「測りたい範囲」と「その値を使う場所」を先に決めます。外枠を含む画面上の位置ならGetWindowRect、内部の寸法ならGetClientRect、異なる基準点へ移すなら座標変換APIという整理が有効です。
Windows APIの基本情報や関連する関数を調べるときは、Windows APIの資料も参照できます。関数名だけでなく、座標系、DPI、矩形の境界、対象ウィンドウの種類まで確認すると、見かけ上の数ピクセルのずれを原因から解消できます。
覚えておくべき核心は、GetWindowRectは通常ウィンドウ全体の画面座標、GetClientRectはクライアント領域のローカル座標を返すという違いです。必要に応じてClientToScreenやMapWindowPointsで基準をそろえれば、描画、配置、入力処理を一貫した座標で扱えます。