マルチディスプレイ時の各ディスプレイの座標及び解像度を取得する

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

概要

 EnumDisplayMonitors APIを用い、パソコンに接続されている複数のモニターの各大きさと、仮想スクリーン上の位置を取得します。また、表示倍率を元にして解像度を取得します。
 取得後、15%の大きさでモニターの大きさを図示し、座標および大きさを表示します。
 ウィンドウサイズは、仮想スクリーンの大きさに合わせてプログラムで調整していますので、マウス操作で大きさを変更できないようにしています。なお、Windows 8.1以降ではモニターごとに異なる解像度を設定できます。本プログラムはモニターごとの解像度の表示とウィンドウの移動によりモニター解像度が変更となった場合文字サイズ等を自動的に変更できるようにしています。具体的には高解像との環境では文字サイズを大きくして綺麗に表示できるようにしています。着色されている部分のそれぞれのモニターの図示しており、2個のモニターが接続されていることを示します。左上、右上で図示している座標は仮想スクリーン上の座標、モニター番号の下にモニターの大きさを図示しています。
 複数のモニターを覆っている四角が仮想スクリーンです。これの例では左上座標が-735,-1080、右下座標が3200,1800が仮想スクリーンの座標となります。

 本プログラムはVistaからサポートしている高DPIに対応するため複雑ことを行っています。
 マニフェストでの高DPIの宣言は不要です。

テスト環境

コンパイラ

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

動作確認

Windows 7
Windows 8.1
Windows 10 Version 1803

プログラムソースの概要

_tWinMain関数

 Windowsから最初に_tWinMain関数が呼び出されます。
 高DPIに対応するために以下の処理をしています。

SetThreadDpiAwarenessContext

 Windows 10 Version 1703より古い場合、プログラムの初期時にSetThreadDpiAwarenessContext APIが存在するかチェックし存在する場合、Windows10のバージョンをチェックします。
 バージョンが1607の場合は、非クライアント領域の大きさを変更させるためWM_NCCREATEメッセージ発生時にEnableNonClientDpiScalingを呼び出します。
 バージョンが1703以降の場合は、SetThreadDpiAwarenessContext APIでDPI_AWARENESS_PER_MONITOR_AWARE_V2を指定することにより Windowsに非クライアント領域の大きさの変更を自動にさせます。

SetProcessDPIAware

 SetThreadDpiAwarenessContext APIが使えない場合、SetProcessDPIAware API(Windows Vista以降)が使えるか確認します。
 事前にSetProcessDPIAware API(Windows Vista以降)を実行するとプログラム自身がスケーリング処理をすることになり正常なDPI値が返されます。実行しない場合、常に96DPIが返されます。Windows XPではこのAPIは存在しないので96DPIと仮定します。なおAPIが存在するかどうかは動的にライブラリをロードし関数のエントリポイントが存在するかどうかで判断しています。
 DPIはGetDpiForMonitor API(Windows 8.1以降)が使える場合はこの関数でウィンドウが属するモニターのDPIを、使えない場合GetDeviceCaps(hdc,LOGPIXELSX)によりディスクトップのDPIを取得します。
SetProcessDPIAware宣言無し(Windows 7)

 サイズが100%/150%の比率になっている。逆の表現ではWindowsにより自動的に150%スケーリングされていることによりプログラムでは96DPIで扱える。
SetProcessDPIAware宣言無し(Windows 7)

Windowの作成

 ウィンドウを作成する場合は、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により仮想スクリーンサイズを取得します。
 MonitorFromPointによりディスプレイのハンドルを取得しGetDpiForMonitorによりDPIを取得します。このAPIが使えない場合はGetDeviceCapsを使用してDPIを取得します。
 SetClientWindowSize関数を呼び出し、クライアントサイズをDPI及び仮想スクリーンサイズに対応したサイズに変更します。

WM_NCCREATE

 Wndows10(Version1607)の時だけ非クライアント領域の処理するためEnableNonClientDpiScaling関数を呼び出します。

case WM_DPICHANGED:

 解像度が異なるモニターにウィンドウが移動したとき、又はモニターの解像度が変更となった時に発生するイベントです。Windows 8.1以降でサポートされています。
 変更後の解像度をnowDPIx,nowDPIy,nowScaleに保存します。
 SetClientWindowSize関数を呼び出し、クライアントサイズをDPI及び仮想スクリーンサイズに対応したサイズに変更します。

case WM_PAINT:

 ウィンドウを再描画する必要があるときに呼び出されます。
 他のウィンドウに隠れ再びフォアグラウンドになった場合などウィンドウの再描画は Windowsが面倒を見ないのでプログラマの仕事となっています。
 現在のDPIを取得します。(WM_CREATE参照)
 DPIに応じたフォントを作成します。
 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には、位置(ウィンドウの左上)を固定して幅と高さのみ有効となるようにフラグの設定をします。
 DPIに応じてフォントサイズ等を設定しなおして描画します。

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個進めます。

win10ver2

 Windows 10のバージョンを取得する関数です。
 詳細は、Windows 10のバージョン番号をRtlGetVersionのビルド番号から取得するを参照してください。

プログラムソース

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

#include <windows.h>
#include <stdio.h>
#if _MSC_VER>1500
#include <shellscalingapi.h>
#endif
#include <tchar.h>

#ifndef _DPI_AWARENESS_CONTEXTS_
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
#endif

typedef HRESULT(_stdcall* EnableNonClientDpiScalingFunc)(HWND);
typedef HRESULT(_stdcall* SetThreadDpiAwarenessContextFunc)(DPI_AWARENESS_CONTEXT);

HMODULE hModUser32 = 0;
EnableNonClientDpiScalingFunc NcDpifunc = 0;
SetThreadDpiAwarenessContextFunc ThreadAwareFunc = 0;

#ifndef _DPI_AWARENESS_CONTEXTS_

typedef enum _DPI_AWARENESS {
	DPI_AWARENESS_INVALID = -1,
	DPI_AWARENESS_UNAWARE = 0,
	DPI_AWARENESS_SYSTEM_AWARE = 1,
	DPI_AWARENESS_PER_MONITOR_AWARE = 2
} DPI_AWARENESS;

#if _MSC_VER<=1500
typedef enum MONITOR_DPI_TYPE {
	MDT_EFFECTIVE_DPI = 0,
	MDT_ANGULAR_DPI = 1,
	MDT_RAW_DPI = 2,
	MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE;
#endif

#define WM_DPICHANGED                   0x02E0

#define DPI_AWARENESS_CONTEXT_UNAWARE              ((DPI_AWARENESS_CONTEXT)-1)
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE         ((DPI_AWARENESS_CONTEXT)-2)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE    ((DPI_AWARENESS_CONTEXT)-3)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
#define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5)
#endif


typedef HRESULT(_stdcall *GetDpiForMonitorFunc)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *);
typedef BOOL(_stdcall *SetProcessDPIAwareFunc)(VOID);

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

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

//	ウィンドウプロシージャー
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);
BOOL CALLBACK myinfoenumproc2(HMONITOR hMon, HDC hdcMon, LPRECT lpMon, LPARAM dwDate);
int win10ver2(void);
int WIN10VER;

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst,
					 TCHAR* lpszCmdLine, int nCmdShow){
	HWND hWnd;
	MSG lpMsg;
	WNDCLASS wc;
	WIN10VER = win10ver2();
	hModUser32 = LoadLibrary(L"User32.dll");
	if (hModUser32){
		if (WIN10VER == 1607)	//	AnniversaryUpdate
			NcDpifunc = (EnableNonClientDpiScalingFunc)GetProcAddress(hModUser32, "EnableNonClientDpiScaling");
		else
			NcDpifunc = 0;
		ThreadAwareFunc = (SetThreadDpiAwarenessContextFunc)GetProcAddress(hModUser32, "SetThreadDpiAwarenessContext");
		if (ThreadAwareFunc){
			if (WIN10VER<1703)
				(*ThreadAwareFunc)(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
			if (1703 <= WIN10VER && WIN10VER<1809)
				(*ThreadAwareFunc)(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
			if (1809 <= WIN10VER)
				(*ThreadAwareFunc)(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED);
		}
		else{	//	Windows Vista以降
			SetProcessDPIAwareFunc ProcessAwareFunc = (SetProcessDPIAwareFunc)GetProcAddress(hModUser32, "SetProcessDPIAware");
			if (ProcessAwareFunc)
				(*ProcessAwareFunc)();
		}
	}

	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("EnumDisplay2"),
		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);
	}
	if (hModUser32)
		FreeLibrary(hModUser32);
	return int(lpMsg.wParam);
}

void ScaleRect(RECT* rect,int scale){
	rect->top *= scale;
	rect->left *= scale;
	rect->bottom *= scale;
	rect->right *= scale;
	rect->top /= 100;
	rect->left /= 100;
	rect->bottom /= 100;
	rect->right /= 100;
}

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

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;
	static int cwx = 0;
	static int cwy = 0;
	static int nowDPIx = 96;
	static int nowDPIy = 96;
	static UINT nowScale = 100;
	static HMODULE hMod;
	static GetDpiForMonitorFunc func = 0;
	switch (msg) {
		case WM_CREATE:{
			n = GetSystemMetrics(SM_CMONITORS);	//	モニター数取得
			mon.rect = new RECT[n];	//	モニター数分の座標格納変数を確保
			mon.horizontalDPI = new UINT[n];
			mon.verticalDPI = new UINT[n];
			mon.mi = new MONITORINFOEX[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);

			hMod = LoadLibrary(L"Shcore.dll");

			func = (GetDpiForMonitorFunc)GetProcAddress(hMod, "GetDpiForMonitor");
			if (func==0)
				FreeLibrary(hMod);

			for (n = 0; n < mon.max; n++){
				POINT    pt;
				pt.x = mon.rect[n].left + 1;
				pt.y = mon.rect[n].top + 1;
				HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
				if (func){
					HRESULT hr = (*func)(hMonitor, MDT_EFFECTIVE_DPI, &mon.horizontalDPI[n], &mon.verticalDPI[n]);
				}else{
					HDC hdc = GetDC(0);	//	ディスクトップ全体のHDC
					mon.temp = n;
					EnumDisplayMonitors(hdc, &mon.rect[n], (MONITORENUMPROC)myinfoenumproc2, (LPARAM)&mon);
				}
				mon.mi[n].cbSize = sizeof(MONITORINFOEX);
				GetMonitorInfo(hMonitor,&mon.mi[n]);
			}
			int x = vs.right - vs.left;
			int y = vs.bottom - vs.top;

			HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL);
			UINT hDPI, vDPI;
			if (func)
				(*func)(hMonitor, MDT_EFFECTIVE_DPI, &hDPI, &vDPI);
			else{
				HDC hdc = GetDC(NULL);	//	ディスクトップ
				hDPI = GetDeviceCaps(hdc, LOGPIXELSX);
				vDPI = GetDeviceCaps(hdc, LOGPIXELSY);
			}
			nowDPIx = hDPI;
			nowDPIy = vDPI;
			nowScale = MulDiv(nowDPIx, 100, 96);
			//	クライアントウィンドウサイズを変更
			SetClientWindowSize(hWnd, (cwx=(cx * 2 + int(double(x*sx))))*nowScale/100, (cwy=(cy * 5 + int(double(y*sy))))*nowScale/100);
			break; 
		}
		case WM_NCCREATE: {	//	Windows 10 Version 1607のみ呼び出す
			if (NcDpifunc)
				(*NcDpifunc)(hWnd);
			return DefWindowProc(hWnd, msg, wParam, lParam);
		}
		case WM_DPICHANGED:{
			nowDPIx = LOWORD(wParam);
			nowDPIy = HIWORD(wParam);
			nowScale=MulDiv(nowDPIx, 100, 96);
			RECT& rect = *((RECT*)lParam);
			SetClientWindowSize(hWnd, cwx*nowScale / 100, cwy *nowScale / 100);
			RedrawWindow(hWnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE);
			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*nowScale/100, cy*nowScale/100, int(double(x)*sx + cx)*nowScale/100, int(double(y)*sy + cy)*nowScale/100);
			hBrush=CreateSolidBrush(RGB(92, 115, 169));
			hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
			SetBkMode(hdc, TRANSPARENT);	//	透過処理
			SetTextColor(hdc, RGB(255, 255, 255));
			HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL);
			UINT hDPI, vDPI;
			if (func)
				(*func)(hMonitor, MDT_EFFECTIVE_DPI, &hDPI, &vDPI);
			else{
				HDC hdc = GetDC(NULL);	//	ディスクトップ
				hDPI = GetDeviceCaps(hdc, LOGPIXELSX);
				vDPI = GetDeviceCaps(hdc, LOGPIXELSY);
			}
			TEXTMETRIC metrics;
			GetTextMetrics(hdc, &metrics);
			int height = metrics.tmHeight;
			LOGFONT lf;
			memset(&lf, 0, sizeof(lf));
			lf.lfHeight = height*nowScale;
			lf.lfHeight /= 100;
			HFONT font = CreateFontIndirect(&lf);
			HFONT OldFont = (HFONT)SelectObject(hdc, font);
			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*nowScale/100, y1*nowScale/100, x2*nowScale/100, y2*nowScale/100);

				_stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), TEXT("%s"),mon.mi[n].szDevice);
				rect.left = x1;	rect.right = x2;	rect.top = y1 - 12-16; rect.bottom = y2 + 4-16;
				ScaleRect(&rect, nowScale);
				DrawText(hdc, buf, -1, &rect, DT_CENTER + DT_VCENTER + DT_SINGLELINE);

				_stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), TEXT("モニター%i"), n);
				rect.left = x1;	rect.right = x2;	rect.top = y1 -12; rect.bottom = y2 + 4;
				ScaleRect(&rect, nowScale);
				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;
				ScaleRect(&rect, nowScale);
				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;
				ScaleRect(&rect, nowScale);
				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;
				ScaleRect(&rect, nowScale);
				DrawText(hdc, buf, -1, &rect, DT_RIGHT + DT_BOTTOM + DT_SINGLELINE);
				POINT pt;
				pt.x = mon.rect[n].left + 1;
				pt.y = mon.rect[n].top + 1;
				HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
				if (func){
					HRESULT hr = (*func)(hMonitor, MDT_EFFECTIVE_DPI, &mon.horizontalDPI[n], &mon.verticalDPI[n]);
				}
	
				_stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), TEXT("DPI %i*%i"), mon.horizontalDPI[n], mon.verticalDPI[n]);
				rect.left = x1;	rect.right = x2;	rect.top = y1 + 28; rect.bottom = y2 + 44;
				ScaleRect(&rect, nowScale);
				DrawText(hdc, buf, -1, &rect, DT_CENTER + DT_VCENTER + DT_SINGLELINE);

			}
			SelectObject(hdc, OldFont);
			DeleteObject(font);
			DeleteObject(hBrush);
			EndPaint(hWnd, &ps);
			break; 
		}
		case WM_DESTROY:
			delete [] mon.rect;
			delete[] mon.horizontalDPI;
			delete[] mon.verticalDPI;
			delete[] mon.mi;
			if (func){
				FreeLibrary(hMod);
				hMod = 0;
				func = 0;
			}
			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;
}

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

BOOL CALLBACK myinfoenumproc2(HMONITOR hMon, HDC hdcMon, LPRECT lpMon, LPARAM dwDate){
	MONITORS* mon = (MONITORS*)dwDate;
	mon->horizontalDPI[mon->temp] = GetDeviceCaps(hdcMon, LOGPIXELSX);
	mon->verticalDPI[mon->temp] = GetDeviceCaps(hdcMon, LOGPIXELSY);
	return TRUE;
}

//	DLL内の関数へのポインタ型を定義
typedef void (WINAPI *RtlGetVersion_FUNC)(OSVERSIONINFOEXW*);

int win10ver2(void){
	int ver = 0;
	HMODULE hMod;
	OSVERSIONINFOEXW osw;
	hMod = LoadLibrary(TEXT("ntdll.dll"));
	RtlGetVersion_FUNC func;
	if (hMod){
		func = (RtlGetVersion_FUNC)GetProcAddress(hMod, "RtlGetVersion");
		if (func == 0){
			FreeLibrary(hMod);
			return FALSE;
		}
		ZeroMemory(&osw, sizeof(osw));
		osw.dwOSVersionInfoSize = sizeof(osw);
		func(&osw);
		FreeLibrary(hMod);
		if (osw.dwMajorVersion == 10){
			switch (osw.dwBuildNumber){
			case 10240: ver = 1507; break;
			case 10586: ver = 1511; break;
			case 14393: ver = 1607; break;
			case 15063: ver = 1703; break;
			case 16299: ver = 1709; break;
			case 17134: ver = 1803; break;
			case 17763: ver = 1809; break;
			default: ver = 1809; break;
			}
			return ver;
		}
	}
	return -1;
}

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

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