ウィンドウの位置を取りたいのにGetClientRectを使っていたり、描画範囲を取りたいのにGetWindowRectを使っていたりすると、座標がずれたように見えます。この記事では、GetWindowRectとGetClientRectが何を返すのかを、移動・サイズ変更・座標変換の実測結果と一緒に確認します。
後半ではScreenToClient、ClientToScreen、MapWindowPointsまで含めて、どの座標をどこへ渡すときに何を使うかを切り分けます。
使い分けの早見表
| やりたいこと | 使う関数 | 追加で使うもの | 注意点 |
|---|---|---|---|
| 画面上のどこにあるか知りたい | GetWindowRect | 必要ならScreenToClient / MapWindowPoints | スクリーン座標で返る。枠やタイトルバーも含む |
| 描画できる中身の幅と高さを知りたい | GetClientRect | なし | 左上は常に (0,0)。right / bottomは幅と高さになる |
| スクリーン座標の点を自分のウィンドウ基準へ直したい | ScreenToClient | 対象ウィンドウのHWND | 非クライアント部分を変換すると負値になり得る |
| クライアント座標の点を画面上へ出したい | ClientToScreen | 対象ウィンドウのHWND | ポップアップ表示位置やドラッグ開始位置で使う |
| 親子・兄弟ウィンドウ間で矩形を渡したい | MapWindowPoints | RECTなら2点として渡す | 複数の座標系をまたぐときは手計算しない |
迷ったときは、「画面全体を基準にしたいのか」「自分のクライアント領域を基準にしたいのか」で分けると判断しやすくなります。
検証環境
| OS | Windows 11 |
| IDE | Visual Studio 2026 |
| ワークロード | C++ によるデスクトップ開発 |
| MFC / ATL | 有効 |
| ビルド構成 | x64 / Debug |
| 確認対象 | MFCダイアログ自身のGetWindowRect / GetClientRect / ScreenToClient / ClientToScreen |
数値そのものはテーマ、DPI、Windowsの見た目設定で変わります。ただし、どの値が動いて、どの値が動かないかという関係は同じです。
GetWindowRectとGetClientRectの返り値の違い
まず押さえるべき点は、2つの関数は同じRECTを返していても、原点が違うことです。
| 観点 | GetWindowRect | GetClientRect |
|---|---|---|
| 座標系 | スクリーン座標 | クライアント座標 |
| 左上の意味 | 画面上のウィンドウ外形の左上 | 常にクライアント左上の (0,0) |
| 右下の意味 | 画面上のウィンドウ外形の右下 | クライアント幅と高さ |
| 含まれる範囲 | 枠、タイトルバー、スクロールバーを含む | 描画・配置できる中身だけ |
| 主な用途 | 位置合わせ、他ウィンドウとの比較、画面上の配置 | 描画、内部レイアウト、ヒットテスト |
RECTのright / bottomは、どちらも右端・下端の外側を指します。幅と高さは手で引き算せず、CRect::Width() とCRect::Height() で取るのが安全です。
CRect rcWindow;
CRect rcClient;
GetWindowRect(&rcWindow);
GetClientRect(&rcClient);
const int outerWidth = rcWindow.Width();
const int outerHeight = rcWindow.Height();
const int clientWidth = rcClient.Width();
const int clientHeight = rcClient.Height();
子ウィンドウでもGetWindowRectはスクリーン座標
見落としやすい点ですが、GetWindowRectは子ウィンドウや子コントロールに対して呼んでも、親クライアント基準ではなくscreen座標を返します。親ダイアログの中にあるボタンやスタティックでも同じです。
そのため、子コントロールの外形を取って別の子コントロールに重ねたいときは、GetWindowRectの結果をそのままMoveWindowへ渡してはいけません。親のクライアント座標へ直してから使います。
CRect rcButton;
m_wndTargetButton.GetWindowRect(&rcButton);
::MapWindowPoints(
HWND_DESKTOP,
GetSafeHwnd(),
reinterpret_cast<LPPOINT>(&rcButton),
2);
m_wndHighlight.MoveWindow(&rcButton);
| そのまま使うとどうなるか | 正しい考え方 |
|---|---|
| screen座標の矩形を親クライアント基準だと思って渡し、右下へずれる | GetWindowRectの結果を親基準へ変換してから使う |
| 子コントロール同士の位置比較でGetClientRectを見てしまい、両方 (0,0) に見える | 位置比較はGetWindowRect、内部サイズ比較はGetClientRectと分ける |
実務でのコツ
レイアウトや配置の計算をしている間は、なるべく1つの座標系の中だけで処理を進めます。GetClientRectの値で計算を終え、別の座標系(親のクライアント座標や画面座標)へ渡す直前にScreenToClient / ClientToScreen / MapWindowPointsを1回だけかける、という順番にしておくと、変換を何度も重ねて間違えることが減ります。
移動だけ変えたときに何が変わるか
最初の比較では、ウィンドウの外形サイズを変えずに位置だけを動かします。GetWindowRectのleft / topは移動に合わせて変わりますが、GetClientRectの原点は常に (0,0) のままです。

左右の比較ビューでは、移動前と移動後でWindowRectの位置だけが変わり、ClientRectはどちらも (0,0) 起点のまま表示されます。GetClientRectは自分のクライアント領域の中しか見ていないためです。
CRect rcBeforeWindow;
CRect rcBeforeClient;
CRect rcAfterWindow;
CRect rcAfterClient;
GetWindowRect(&rcBeforeWindow);
GetClientRect(&rcBeforeClient);
MoveWindow(48, 48, rcBeforeWindow.Width(), rcBeforeWindow.Height());
GetWindowRect(&rcAfterWindow);
GetClientRect(&rcAfterClient);
この違いを知らないまま、別ウィンドウの横に並べる処理や、ドラッグ開始位置の記録にGetClientRectを使うと、すべてのウィンドウが原点付近にあるように見えてしまいます。
サイズ変更でどこまで差が広がるか
次に、位置だけでなくサイズも変更してみます。外形のサイズとクライアント領域のサイズは一致せず、その差は枠やタイトルバーの厚みのぶんです。

比較ビューでは、変更後のOuter sizeとClient sizeが別の値で表示されます。差分は枠やタイトルバーの厚みで、Windows 11のような現行環境ではこの差が見た目以上に効きます。
ダイアログ内のリストやプレビュー領域を追従させるなら、基準はGetWindowRectではなくGetClientRectです。外形サイズをそのまま使うと、下端や右端がはみ出します。
void CChildLayoutDlg::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
if (!::IsWindow(m_wndList.m_hWnd))
return;
CRect rcClient;
GetClientRect(&rcClient);
const int margin = 12;
m_wndList.MoveWindow(
margin,
margin,
rcClient.Width() - margin * 2,
rcClient.Height() - margin * 2);
}
OnSizeの中で使う基準値をGetClientRectの戻り値だけに統一しておくと、後から座標変換まわりの不具合を切り分けやすくなります。
ScreenToClient(WindowRect.LeftTop) が負になる理由
GetWindowRectの左上をそのままScreenToClientへ渡すと、しばしば負の値になります。これは失敗ではなく、ウィンドウ外形の左上がクライアント領域の左上より外側にあるからです。

CRect rcWindow;
GetWindowRect(&rcWindow);
CPoint pt = rcWindow.TopLeft();
ScreenToClient(&pt);
// pt.x / pt.y は負になることがある
変換後の値が負になるのは、タイトルバーや枠のぶんだけ、WindowRect.LeftTopがクライアント原点より上と左にあるからです。逆に、ClientToScreen(0,0) は「実際のクライアント左上が画面上のどこにあるか」を返します。
- GetWindowRect().TopLeft() は外形の左上です。
- ClientToScreen(CPoint(0, 0)) はクライアント原点の画面位置です。
- 両者の差分が、非クライアント領域の厚みとして現れます。
この挙動を知らないと、負値を見て「変換に失敗した」と誤解しがちです。実際には、座標系を正しくまたいだ結果として自然な値です。
ClientToScreenとScreenToClientの使い分け
ScreenToClientとClientToScreenは点の向きを反転しただけの関数ですが、使う場所ははっきり分かれます。
| 場面 | 使う関数 | 理由 |
|---|---|---|
| マウスカーソルや外部APIから受けたscreen座標を自分の中へ入れる | ScreenToClient | 自分のクライアント座標へ直してから描画やヒットテストを行うため |
| 自分の中で決めた点をメニューやポップアップの表示位置へ出す | ClientToScreen | 画面上の絶対位置が必要になるため |
| 複数点やRECTを別ウィンドウ基準へまとめて変換する | MapWindowPoints | 点ごとの変換より安全で読みやすい |
たとえば、クライアント内の計算結果を右クリックメニューの表示位置へ使うなら、最後にClientToScreenを通します。
CPoint ptPopup(16, 16);
ClientToScreen(&ptPopup);
pPopupMenu->TrackPopupMenu(
TPM_LEFTALIGN | TPM_TOPALIGN,
ptPopup.x,
ptPopup.y,
this);
一方、別APIからscreen座標で受けた点を自分の描画処理へ渡すなら、先にScreenToClientをかけます。描画や子コントロール配置へ渡す前に、どの座標系かをそろえるのが基本です。
親子ウィンドウ間ではMapWindowPointsを使う場面
ScreenToClientとClientToScreenは1つのウィンドウを基準に点を変換する関数です。親子や兄弟ウィンドウの間で矩形を受け渡すなら、MapWindowPointsで2点まとめて変換するほうが安全です。
CRect rcChildScreen;
m_wndPreview.GetWindowRect(&rcChildScreen);
::MapWindowPoints(
HWND_DESKTOP,
m_wndPanel.GetSafeHwnd(),
reinterpret_cast<LPPOINT>(&rcChildScreen),
2);
m_wndOverlay.MoveWindow(&rcChildScreen);
この例では、まず子ウィンドウの外形をscreen座標で取り、その後m_wndPanelのクライアント座標へ変換しています。RECTは2点として扱うため、最後の引数は2です。
Microsoft Learnでも、レイアウトのミラーリングが絡む場合はScreenToClientではなくMapWindowPointsを使うよう案内されています。通常の左から右のレイアウトでも、複数ウィンドウをまたぐ矩形変換はMapWindowPointsと覚えておくと事故が減ります。
よくある誤用
描画範囲にGetWindowRectを使う
CDCの描画座標はクライアント基準です。GetWindowRectをそのまま描画へ渡すと、画面上の位置とクライアント内の位置が混ざります。
// NG
RECT rc;
GetWindowRect(&rc);
pDC->FillSolidRect(&rc, RGB(255, 0, 0));
// OK
GetClientRect(&rc);
pDC->FillSolidRect(&rc, RGB(255, 0, 0));
中身の幅をGetWindowRectで取る
GetWindowRectの幅と高さには枠やタイトルバーが含まれます。中身の幅を欲しいのにこれを使うと、余白計算がずれて、子コントロールが下にはみ出しやすくなります。
別ウィンドウとの位置比較にGetClientRectを使う
GetClientRectはどのウィンドウでも (0,0) 起点です。別ウィンドウとの相対位置を知りたいなら、比較材料はGetWindowRectです。
right / bottomを実座標の内側だと思い込む
RECTの右下は排他的です。右端のピクセルに線を引きたいときは、right - 1、bottom - 1を意識しないと1ピクセルはみ出します。
GetWindowRectの注意点
GetWindowRectは便利ですが、現行Windowsでは2つ注意点があります。DPI仮想化と、見えないリサイズ枠を含む場合があることです。
DPI仮想化される
Microsoft Learnでは、GetWindowRectはDPI仮想化されると説明されています。高DPI環境や混在DPI環境で値を扱うときは、見た目のピクセル数と完全一致しないことがあります。
見えないリサイズ枠を含むことがある
Windows Vista以降では、GetWindowRectの外形に見えないリサイズ枠が含まれる場合があります。スクリーンショット上の見た目とぴったり一致する可視境界が必要なら、DwmGetWindowAttributeとDWMWA_EXTENDED_FRAME_BOUNDSを使う選択肢があります。
ただし、通常のMFCダイアログ配置や、相対位置の比較であれば、まずはGetWindowRect / GetClientRectの違いを正しく使い分けるほうが先です。
動かないときの確認表
| 症状 | まず見る場所 | 原因になりやすいこと | 対処 |
|---|---|---|---|
| ScreenToClient後に負値が出る | 変換前の点の意味 | 非クライアント部分の点を変換している | 失敗ではない。必要な点がクライアント内かを確認する |
| 別ウィンドウへ渡す矩形が少しずれる | 座標変換処理 | screen座標のままMoveWindowへ渡している | MapWindowPointsかScreenToClientで親基準へそろえる |
| 見た目の境界とGetWindowRectが一致しない | DPIと見えない枠 | DPI仮想化、不可視リサイズ枠 | 必要ならDwmGetWindowAttributeを検討する |
| 右端や下端で1pxずれる | RECTの使い方 | right / bottomを内側座標だと思っている | 排他的境界として扱い、幅高さはWidth() / Height() を使う |
トラブルの多くはAPIそのものより、screen座標とclient座標を混ぜたまま次のAPIへ渡していることが原因です。変換前後の座標系をコメントで残すだけでも、かなり追いやすくなります。
まとめ
- GetWindowRectはscreen座標で外形を返し、GetClientRectはclient座標で中身を返します。
- 位置の比較はGetWindowRect、描画と内部レイアウトはGetClientRectが基本です。
- ウィンドウ左上をScreenToClientすると負値になることがありますが、非クライアント領域ぶんの自然な結果です。
- clientの点をscreenへ出すときはClientToScreen、screenの点を自分の中へ入れるときはScreenToClientを使います。
- 親子・兄弟ウィンドウ間で矩形を渡すなら、手計算よりMapWindowPointsのほうが安全です。
- GetWindowRectはDPI仮想化され、見えないリサイズ枠を含む場合があります。
