山本ワールド
Windowsプログラミング
アルゴリズム Vitual C++ 2008/2013によるWin32/Win64 APIレベルのプログラム 基礎 Vitual C++ 2008/2013によるAPIレベルのプログラム(32/64bit) Wix3でインストーラーを作る Visual C++ 2008 Standard Editonによるフォームアプリケーションのプログラム(32/64bit) Vitual C++ 2008 Standard EditonによるAPIレベルのプログラム(32/64bit) Windows 7対応 Visual C++ 2008 ExpressによるAPIレベルのプログラム Visual C++ 2005 ExpressによるAPIレベルのプログラム Visual C++ Versiosn 5 BORLAND C++ Windowsプログラム全般 Excel VBA その他大きな文字を表示
概要
ウィンドウの大きさに合わせて文字を表示します。ウィンドウの大きさの変更に合わせて文字の大きさが変わります。
テスト環境
コンパイラ
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を呼び出し必要な大きさを再度求めます。
TextOut APIにより先ほどのSIZE構造体に格納されている描画範囲からクライアント領域の中央に文字を表示します。
描画が終了したら、SelectObject APIでフォントを元に戻し作成したフォントをDeleteObject APIにより削除します。
再描画が終了したことをWindowsに知らせるためにEndPaint APIを使用してます。このAPIを呼び出さないと何度もWM_PAINTメッセージが発生し暴走します。
case WM_DESTROY:
ウィンドウが閉じるときに呼び出されます。PostQuitMessage APIにより終了コードを指定して、ウィンドウプロシージャーを終了させます。
プログラムソース
// 巨大文字の描画(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("BigFont");
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst,TCHAR* lpszCmdLine, int nCmdShow){
HWND hWnd;
MSG lpMsg;
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:{
hdc = BeginPaint(hWnd, &ps);
RECT rect;
GetClientRect(hWnd, &rect);
double scr = double(rect.right) / double(rect.bottom);
int h = rect.bottom;
TCHAR* str=_TEXT("MS明朝");
TCHAR* fontName = _TEXT("MS 明朝");
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, str, _tcslen(str), &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, str, _tcslen(str), &size);
}
TextOut(hdc, (rect.right-size.cx)/2, (rect.bottom-size.cy)/2, str, (int)_tcslen(str));
TCHAR buf[32];
_stprintf_s(buf, sizeof(buf) / sizeof(buf[0]), _TEXT("bigfont[%i*%iピクセル]"), size.cx,size.cy);
SetWindowText(hWnd, buf);
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;
}
ソースファイルと実行ファイルのダウンロード
Copyright (C) 2012 山本ワールド All Rights Reserved.