マルチディスプレイ時の各ディスプレイの座標を取得する

icon 項目のみ表示/展開表示の切り替え

概要

EnumDisplayMonitors APIを用い、パソコンに接続されている複数のモニターの各大きさと、仮想スクリーン上の位置を取得します。
取得後、15%の大きさでモニターの大きさを図示し、座標および大きさを表示します。
ウィンドウサイズは、仮想スクリーンの大きさに合わせてプログラムで調整していますので、マウス操作で大きさを変更できないようにしています。
以下に動作例を示します。

ディスクトップで右クリックで画面の解像度を選択した結果(Windows 7での動作例)

本プログラムの実行結果


着色されている部分のそれぞれのモニターの図示しており、2個のモニターが接続されていることを示します。左上、右上で図示している座標は仮想スクリーン上の座標、モニター番号の下にモニターの大きさを図示しています。
複数のモニターを覆っている四角が仮想スクリーンです。これの例では左上座標が-768,-12、右下座標が1920,1354が仮想スクリーンの座標となります。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

プロジェクトの作成

Win32プロジェクト Windowsアプリケーション

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

プログラムソースの概要

_tWinMain関数

Windowsから最初に_tWinMain関数が呼び出されます。 ウィンドウを作成する場合は、RegisterClass APIによりウィンドウクラスを定義してからCreateWindow APIを呼び出しウィンドウを作成します。 Windowsは入力等のイベントが発生するとアプリケーションにメッセージを送付します。 メッセージはキューに保管されます。アプリケーションはメッセージを取り出し、該当ウィンドウにメッセージを配信します。 メッセージの取り出しから配信までループで処理を行いウィンドウから終了メッセージが届くと、ループを抜けるように記述します。 これらの一連の処理は、通常にCreateWindow APIの後に記述しこれをメッセージループと呼んでいます。 詳細は以下を参照してください。 メッセージループについて

WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)ウィンドウプロシージャー

RegisterClass APIにより登録することによりWindowsから呼び出されます。
第2引数にメッセージの種類が格納されていますので、switchステートメントによりメッセージごとの処理を振り分けます。
自分で処理しないメッセージはDefWindowProc APIに渡せばWindowsが標準的な処理を行ってくれます。

case WM_CREATE:

ウィンドウの初期化時に呼び出されます。
GetSystemMetrics APIによりモニター数を取得します。
構造体monのメンバーrectにモニター数分のRECTをnew演算子で動的メモリを確保して格納します。
コールバック関数myinfoenumproc、構造体monを渡してEnumDisplayMonitors APIを呼び出します。
GetSystemMetrics APIにより仮想スクリーンサイズを取得します。
SetClientWindowSize関数を呼び出し、クライアントサイズを仮想スクリーンサイズに対応したサイズに変更します。

case WM_PAINT:

ウィンドウを再描画する必要があるときに呼び出されます。
他のウィンドウに隠れ再びフォアグラウンドになった場合などウィンドウの再描画は Windowsが面倒を見ないのでプログラマの仕事となっています。
BeginPaint APIを呼び出して、描画に必要なデバイスコンテキストのハンドルを取得します。
BeginPaint APIの第2引数のポインタにはPAINTSTRUCT構造体のポインタを渡します。
BeginPaint API終了後、PAINTSTRUCT構造体には再描画が必要な領域の座標等の値が格納されています。
次にソリッドブラシの作成を行います。 ブラシの作成方法の詳細は以下を参照してください。
GDIのブラシの作成方法
ブラシを作成したらそのブラシで描画させるためにSelectObject APIによりブラシを選択します。 戻り値は、前回のペンのハンドルですので保存しておき、デバイスコンテキストの解放前に ブラシの設定をもとに戻すときに使用します。
Rectangle APIにより仮想スクリーンサイズの15%の大きさの四角形を描画します。
DrawText APIでテキストを描画するとディフォルトでは背景が白で描画されるので、背景モードを変える必要があります。
SetBkMode APIにより背景モードを透過処理に変更します。
テキストを白色に変更するためにSetTextColor APIを呼び出します。
以下の処理をモニターごとに実施します。
_stprintf_s関数により描画する文字列を作成し、rect構造体にテキスト描画する矩形の範囲を指定し、 DrawText APIを呼び出します。
DrawText APIは、TextOut APIに比べてセンタリング、右寄せ、クリップ、折り返し等の高度な処理ができます。
モニターごとの処理が終了したら、SelectObject APIでブラシを元に戻します。
再描画が終了したことをWindowsに知らせるためにEndPaint APIを使用してます。このAPIを呼び出さないと何度もWM_PAINTメッセージが発生し暴走します。

case WM_DESTROY:

ウィンドウが閉じるときに呼び出されます。
構造体monのメンバーrectが示す動的メモリをdelete []演算子で解放します。
PostQuitMessage APIにより終了コードを指定して、ウィンドウプロシージャーを終了させます。

SetClientWindowSize関数

ウィンドウのクライアントサイズを指定したサイズに変更します。

ウィンドウサイズとは上図の赤で囲まれた範囲の左上と右下座標、 クライアントサイズとは上図の青で囲まれた範囲の左上と右上座標を示しています。
ウィンドウサイズを変更するAPIはありますが、クライアントサイズを直接変更できるAPIはありません。
ここでは、ウィンドウサイズとクライアントサイズを取得し、それぞれの幅の差と高さの差を算出し、 クライアントサイズに加算して、 ウィンドウサイズを変更すれば結果としてクライアントサイズを希望のサイズに変更できます。
ウィンドウサイズの取得にはGetWindowRect APIをもちいます。結果は、スクリーン座標です。
クライアントサイズの取得にはGetClientRect APIを用います。結果は、クライアント座標の左上からの相対値ですので、左上の座標は(0,0)となります。
新しい、ウィンドウサイズの幅と高さを算出後、SetWindowPos APIを呼び出して、ウィンドウサイズを変更します。
SetWindowPos APIには、位置(ウィンドウの左上)を固定して幅と高さのみ有効となるようにフラグの設定をします。

myinfoenumproc関数

EnumDisplayMonitors APIから全部のモニターについてモニター毎に呼び出される関数です。
この関数のプロトタイプ宣言は以下の通りです。
BOOL CALLBACK myinfoenumproc(HMONITOR hMon, HDC hdcMon, LPRECT lpMon, LPARAM dwDate);
第3引数lpmonには、モニターの左上と右下の座標を示すRECT構造体へのポインタが格納されています。
第4引数dwDataには、EnumDisplayMonitors APIの第4引数に渡した値が渡されます。ここでは複数のモニターの諸元を格納しているMONITORS型へのポインタとなっています。
第4引数をMONITORSのポインタmonへキャストして代入します。
第3引数のモニターの座標をmonへ格納後、モニター数を示すメンバ変数maxを1個進めます。

プログラムソース

//       マルチモニターの緒元取得プログラム
//      Visual C++ 2013 32/64bit

#include <windows.h>
#include <stdio.h>
#include <tchar.h>

//      接続されているモニタの諸元を格納
struct MONITORS{
        int max;        //      モニター数
        RECT* rect;     //      各モニターの座標
        MONITORS(){
                max = 0;
                rect = 0;
        }
};

TCHAR szClassNme[] = TEXT("EnumDisplay");

//      ウィンドウプロシージャー
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//      クライアントサイズの変更
void SetClientWindowSize(HWND hWnd, int cx, int cy);
//      EnumDisplayMonitors関数からモニター毎に呼び出される関数
BOOL CALLBACK myinfoenumproc(HMONITOR hMon, HDC hdcMon, LPRECT lpMon, LPARAM dwDate);


int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst,
                                         TCHAR* lpszCmdLine, int nCmdShow){
        HWND hWnd;
        MSG lpMsg;
        WNDCLASS wc;

        if (!hPreInst) {
                wc.style = CS_HREDRAW | CS_VREDRAW;
                wc.lpfnWndProc = WndProc;
                wc.cbClsExtra = 0;
                wc.cbWndExtra = 0;
                wc.hInstance = hInstance;
                wc.hIcon = NULL;
                wc.hCursor = LoadCursor(NULL, IDC_ARROW);
                wc.hbrBackground = (HBRUSH__ *)GetStockObject(WHITE_BRUSH);
                wc.lpszMenuName = NULL;
                wc.lpszClassName = szClassNme;
                if (!RegisterClass(&wc))
                        return FALSE;
        }
        hWnd = CreateWindow(szClassNme,
                TEXT("EnumDisplay"),
                WS_OVERLAPPED   //      オーバーラップウィンドウ タイトルバーと枠
                | WS_CAPTION    //      タイトルバーと枠
                | WS_SYSMENU    //      タイトルバー上にウィンドウメニュー
                | WS_MINIMIZEBOX,       //      最小化ボタン
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL);
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);
        while (GetMessage(&lpMsg, NULL, 0, 0)) {
                TranslateMessage(&lpMsg);
                DispatchMessage(&lpMsg);
        }
        return int(lpMsg.wParam);
}

//      ウィンドウプロシージャー

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
    HDC hdc;
    PAINTSTRUCT ps;
    int n;
        TCHAR buf[128];
        static MONITORS mon;    //      各モニターのサイズを格納
        static RECT vs;         //      仮想スクリーンサイズ
        static double sx = 0.15;
        static double sy = 0.15;
        static int cx = 16;
        static int cy = 16;
    switch (msg) {
                case WM_CREATE:{
                        n = GetSystemMetrics(SM_CMONITORS);     //      モニター数取得
                        mon.rect = new RECT[n]; //      モニター数分の座標格納変数を確保
                        //      各モニターの座標を取得
                        EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)myinfoenumproc, (LPARAM)&mon);
                        //      仮想スクリーンサイズを取得
                        vs.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
                        vs.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
                        vs.right = vs.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
                        vs.bottom = vs.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);
                        int x = vs.right - vs.left;
                        int y = vs.bottom - vs.top;
                        //      クライアントウィンドウサイズを変更
                        SetClientWindowSize(hWnd, cx * 2 + int(double(x*sx)), cy * 2 + int(double(y*sy)));
                        break; 
                }
                case WM_PAINT:{
                        HBRUSH hBrush, hOldBrush;
                        hdc = BeginPaint(hWnd, &ps);
                        int x=vs.right-vs.left;
                        int y=vs.bottom-vs.top;

                        Rectangle(hdc , cx , cy , int(double(x)*sx+cx) , int(double(y)*sy+cy));

                        hBrush=CreateSolidBrush(RGB(92, 115, 169));
                        hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);

                        SetBkMode(hdc, TRANSPARENT);    //      透過処理
                        SetTextColor(hdc, RGB(255, 255, 255));

                        for (n = 0; n < mon.max; n++) {
                                int x1=int(double(mon.rect[n].left-vs.left)*sx)+cx;
                                int y1 = int(double(mon.rect[n].top - vs.top)*sy) + cy;
                                int x2 = int(double(mon.rect[n].right - vs.left)*sx) - 1 + cx;
                                int y2 = int(double(mon.rect[n].bottom - vs.top)*sy) - 1 + cy;
                                RECT rect;

                                Rectangle(hdc , x1 , y1 ,       x2 , y2);

                                _stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), TEXT("モニター%i"), n);
                                rect.left = x1; rect.right = x2;        rect.top = y1 -12; rect.bottom = y2 + 4;
                                DrawText(hdc, buf, -1, &rect, DT_CENTER + DT_VCENTER + DT_SINGLELINE);

                                _stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), TEXT("%i*%i"), mon.rect[n].right - mon.rect[n].left, mon.rect[n].bottom - mon.rect[n].top);
                                rect.left=x1;   rect.right=x2;  rect.top=y1+8; rect.bottom=y2+24;
                                DrawText(hdc,buf,-1,&rect,DT_CENTER + DT_VCENTER + DT_SINGLELINE);
                                _stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), TEXT("%i,%i"), mon.rect[n].left, mon.rect[n].top);
                                rect.left=x1+4; rect.right=x2;  rect.top=y1+4; rect.bottom=y2;
                                DrawText(hdc,buf,-1,&rect,DT_LEFT + DT_TOP + DT_SINGLELINE);

                                _stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), TEXT("%i,%i"), mon.rect[n].right, mon.rect[n].bottom);
                                rect.left=x1;   rect.right=x2-4;        rect.top=y1; rect.bottom=y2-4;
                                DrawText(hdc,buf,-1,&rect,DT_RIGHT + DT_BOTTOM + DT_SINGLELINE);
                        }
                        DeleteObject(hBrush);
                        EndPaint(hWnd, &ps);
                        break; 
                }
        case WM_DESTROY:
                        delete [] mon.rect;
            PostQuitMessage(0);
            break;
        default:
            return(DefWindowProc(hWnd, msg, wParam, lParam));
    }
    return (0L);
}

//      クライアントサイズの変更

void SetClientWindowSize(HWND hWnd, int cx, int cy){
        RECT wsize, csize;
        GetWindowRect(hWnd, &wsize);
        GetClientRect(hWnd, &csize);
        int nx, ny;
        nx = (wsize.right - wsize.left) - csize.right + cx;
        ny = (wsize.bottom - wsize.top) - csize.bottom + cy;
        SetWindowPos(hWnd, NULL, 0, 0, nx, ny, SWP_NOMOVE | SWP_NOZORDER);
}

//      EnumDisplayMonitors関数からモニター毎に呼び出される関数

BOOL CALLBACK myinfoenumproc(HMONITOR hMon, HDC hdcMon, LPRECT lpMon, LPARAM dwDate){
        MONITORS* mon = (MONITORS*)dwDate;
        mon->rect[mon->max].bottom = lpMon->bottom;
        mon->rect[mon->max].left = lpMon->left;
        mon->rect[mon->max].top = lpMon->top;
        mon->rect[mon->max].right = lpMon->right;
        ++mon->max;
        return TRUE;
}

ソースファイルと実行ファイルのダウンロード

ダウンロード enumdisplay.zip(44.4kByte)
ZIPファイルに含まれるファイル
enumdisplay.cpp
enumdisplay.exe