概要

 ウィンドウの大きさに合わせて文字を表示します。ウィンドウの大きさの変更に合わせて文字も大きさが変わります。
 文字をパスとして描画しています。文字の塗りつぶしにはリソースファイルで定義したBMP画像を使用しています。

テスト環境

コンパイラ

Visual C++ 2013 Express 32/64bit

プログラムソースの概要

_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を取得します。
パスを

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が標準的な処理を行ってくれます。

WM_NCCREATE

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

case WM_DPICHANGED:

 解像度が異なるモニターにウィンドウが移動したとき、又はモニターの解像度が変更となった時に発生するイベントです。Windows 8.1以降でサポートされています。
 変更後の解像度をnowDPIx,nowDPIy,nowScaleに保存します。
 lParamに解像度変更後のウィンドウサイズが提案されているのでSetWindowPos APIによりウィンドウの大きさを変更します

case WM_PAINT:

 ウィンドウを再描画する必要があるときに呼び出されます。
 他のウィンドウに隠れ再びフォアグラウンドになった場合などウィンドウの再描画は Windowsが面倒を見ないのでプログラマの仕事となっています。
 BeginPaint APIを呼び出して、描画に必要なデバイスコンテキストのハンドルを取得します。
 BeginPaint APIの第2引数のポインタにはPAINTSTRUCT構造体のポインタを渡します。
 BeginPaint API終了後、PAINTSTRUCT構造体には再描画が必要な領域の座標等の値が格納されています。
 ウィンドウのクライアントサイズをGetClientRect APIにより取得します。
 ウィンドウの高さと同一の大きさのフォントをCreateFont APIにより作成します。
 SelectObjectにより新しいフォントを選択するとともに旧フォントハンドルをhOldFontに格納します。
 GetTextExtentPoint32 APIにより新しいフォントで描画した場合の縦横のピクセル数をSIZE構造体に格納します。
 クライアントの縦横比と描画した場合の縦横比を比較しクライアント幅を描画するフォントの幅が上回る場合には比率により新しいフォントを作成しGetTextExtentPoint32 APIを呼び出し必要な大きさを再度求めます。
 BeginPath APIにより以降の描画はパス扱いにすることを指定します。
 TextOut APIにより先ほどのSIZE構造体に格納されている描画範囲からクライアント領域の中央にパスを作成します。
 EndPathによりパス扱いを終了を指定します。
 SetBkMode APIにより背景モードを透過処理に変更します。
 次にBMP画像のブラシの作成を行います。ブラシの作成方法の詳細は以下を参照してください。
GDIのブラシの作成方法
 ブラシを作成したらそのブラシで描画させるためにSelectObject APIによりブラシを選択します。 戻り値は、前回のペンのハンドルですので保存しておき、デバイスコンテキストの解放前に ブラシの設定をもとに戻すときに使用します。本プログラムで使用するブラシの画像()はリソースファイルでファイル名を定義しています。
 StrokeAndFillPath APIにより先ほど作成されたパスをクライアント領域に描画します。
 描画が終了したら、SelectObject APIでブラシを元に戻します。
 再描画が終了したことをWindowsに知らせるためにEndPaint APIを使用してます。このAPIを呼び出さないと何度もWM_PAINTメッセージが発生し暴走します。

case WM_DESTROY:

 ウィンドウが閉じるときに呼び出されます。
 PostQuitMessage APIにより終了コードを指定して、ウィンドウプロシージャーを終了させます。

プログラムソース

bigfont2.cpp

//	巨大文字の描画(GDI)
//	Visual C++ 2013 32/64bit

#include <windows.h>
#include <tchar.h>
#include <math.h>
#if _MSC_VER>1500
#include <shellscalingapi.h>
#endif
#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);
int win10ver2(void);
int WIN10VER;

//	ウィンドウプロシージャー
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

TCHAR szClassName[] = TEXT("BigFont2");
HINSTANCE hInst;

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst,TCHAR* lpszCmdLine, int nCmdShow){
	HWND hWnd;
	MSG lpMsg;
	hInst = hInstance;
	WNDCLASS myProg;
	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) {
		myProg.style =CS_HREDRAW | CS_VREDRAW;
		myProg.lpfnWndProc =WndProc;
		myProg.cbClsExtra =0;
		myProg.cbWndExtra =0;
		myProg.hInstance =hInstance;
		myProg.hIcon =NULL;
		myProg.hCursor =LoadCursor(NULL, IDC_ARROW);
		myProg.hbrBackground =(HBRUSH)GetStockObject(WHITE_BRUSH);
		myProg.lpszMenuName =NULL;
		myProg.lpszClassName =szClassName;
		if (!RegisterClass(&myProg))
			return FALSE;
	}
	hWnd = CreateWindow(szClassName,
		szClassName,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		200,
		150,
		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;
	static int nowDPIx = 96;
	static int nowDPIy = 96;
	static UINT nowScale = 100;

	switch (msg) {
		case WM_DESTROY:
			PostQuitMessage(0);
			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);
			int cx = rect.right - rect.left;
			int cy = rect.bottom - rect.top;
			SetWindowPos(hWnd, NULL, rect.left, rect.top, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE);
			break;
		}
		case WM_PAINT:{
			TCHAR* buf=_TEXT("謹賀新年");
			TCHAR* fontName = _TEXT("HGS行書体");
			hdc = BeginPaint(hWnd, &ps);
			RECT rect;
			GetClientRect(hWnd, &rect);
			double scr = double(rect.right) / double(rect.bottom);
			int h = rect.bottom;
			HFONT hFont = CreateFont(
				h, 0, 0, 0, 0, FALSE, FALSE, FALSE,
				SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS,
				CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
				VARIABLE_PITCH, fontName
				);
			SIZE size;
			HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
			GetTextExtentPoint32(hdc, buf, _tcslen(buf), &size);
			double font = double(size.cx) / double(size.cy);
			if (scr < font){	//	横がはみ出ている
				h =h*rect.right/size.cx ;
				SelectObject(hdc, hOldFont);
				DeleteObject(hFont);
				hFont = CreateFont(
					h, 0, 0, 0, 0, FALSE, FALSE, FALSE,
					SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS,
					CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
					VARIABLE_PITCH, fontName
					);
				SelectObject(hdc, hFont);
				GetTextExtentPoint32(hdc, buf, _tcslen(buf), &size);
			}
			SetBkMode(hdc, TRANSPARENT);
			BeginPath(hdc);
			TextOut(hdc, (rect.right - size.cx) / 2, (rect.bottom - size.cy) / 2, buf, (int)_tcslen(buf));
			EndPath(hdc);

			HBITMAP hbitmap = LoadBitmap(hInst, TEXT("BMP_RC"));
			HBRUSH hBrush = CreatePatternBrush(hbitmap);
			HBRUSH hOldBrush=(HBRUSH)SelectObject(hdc, hBrush);
			StrokeAndFillPath(hdc);
			SelectObject(hdc, hOldBrush);
			DeleteObject(hBrush);
			SelectObject(hdc, hOldFont);
			DeleteObject(hFont);
			EndPaint(hWnd, &ps);
			break;
		}
		default:
			return(DefWindowProc(hWnd, msg, wParam, lParam));
	}
	return (0L);
}

//	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;
}

resource.rc

#include <windows.h>

BMP_RC  BITMAP  "brush.bmp"

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

ダウンロード bigfont2.zip(39.8kByte)
ZIPファイルに含まれるファイル
bigfont2.cpp
bigfont2.exe
resouce.rc
brush.bmp