C++ 極小レベルのWinFormアプリを.NET系無しwin32で作成
概要とか
元々 C# で WindowsFormsApplication とか WPF やらのウィンドウアプリ触ってたんですが、
C# がデフォで使う .NET Framework
って中間言語という扱いらしく、
高速起動とかメモリ消費量とかにこだわる場合は .NET系に頼らない選択肢もあるっぽいので、C++ に手を出した時のメモです
- 当環境
- Windows10 x64
- C++ で Win32API を利用
- Visual Studio Community 2022
プロジェクト作成など
まず Visual Studio Installer から C++ によるデスクトップ開発 を有効化(インストール)しておく。 ( 画像右上 )
ちなみに、本末転倒な話だけど .NET
.NET Framework
を使うなら、
インストールの詳細/オプション内から v143 ビルド ツール用 C++/CLI サポート (最新) にチェックを入れると良さそう?
新規プロジェクト作成 ~ .cpp 追加
Windows デスクトップ ウィザード を選択して次へ。
( 画像は [ C++ & Windows & デスクトップ ] で条件を絞り込んだ状態 )
「アプリケーションの種類」を デスクトップアプリケーション (.exe) に変更し、
☑空のプロジェクト にチェックを入れて作成すればOK
こうすると、マジで何もないプロジェクトが出来上がるので、手作業でファイルを追加。
プロジェクト メニュー >> 新しい項目の追加 >> C++ ファイル(.cpp)
本例ではファイル名を main.cpp
とした。
あとは .cpp
に次項のソースを貼り付けて実行すれば、
以下のようなウィンドウが表示される
.cpp 用ソース
ちょいちょいコメントメモ付き
#include <windows.h>
#include <stdlib.h>
#include <tchar.h>
// 関数の事前宣言
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine,_In_ int nCmdShow)
{
// ウィンドウクラス名。
static TCHAR szWindowClass[] = _T("DesktopApp");
// ▼ ウィンドウクラス登録用の情報を詰め込んでいく
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
// ▼ 上で仕込んだクラス情報をここで登録
if (!RegisterClassEx(&wcex))
{
MessageBox(NULL, _T("ウィンドウ登録失敗"), L"エラー" , NULL);
return 1;
}
// ▼ ウィンドウ作成
HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, L"タイトル" , WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
300, 100,
NULL, NULL, hInstance, NULL );
if (hWnd == NULL)
{
MessageBox(NULL, _T("ウィンドウ作成失敗"), L"エラー" , NULL);
return 1;
}
// ▼ ウィンドウ表示
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) // メッセージループ
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// メインウィンドウのメッセージ処理用
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
TCHAR szMessage[] = _T("表示したい文字列");
switch (message)
{
// メインウィンドウ描画
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// テキスト描画のレイアウト処理
TextOut(hdc, 5, 5, szMessage, _tcslen(szMessage));
// レイアウト処理終了
EndPaint(hWnd, &ps);
break;
// 終了時、メッセージを投げて戻る
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
旧スタイルでウィンドウ作成
uxtheme.lib
周りを使えるようにしつつ、 SetWindowTheme()
を仕込むだけでスタイルが変わった。
( ※ メニューとか細部のスタイルまでは未検証 )
- learn.microsoft.com - SetWindowTheme 関数 (uxtheme.h)
セクションエントリと一致しない空の文字列 (L" ") を指定することで、
指定されたウィンドウに表示スタイルが適用されないようにすることができます。
#pragma comment (lib, "uxtheme")
#include <windows.h>
#include <Uxtheme.h>
// ★★ ↑ Uxtheme 関連のおぜんだて ★★
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
const WCHAR* szClassName = L"myWindowClass";
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szClassName;
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wcex))
{
MessageBox(NULL, L"ウィンドウ登録失敗" , L"エラー", NULL);
return 1;
}
HWND hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, szClassName, L"タイトル", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 250, 100,
NULL, NULL, hInstance, NULL);
if (hWnd == NULL)
{
MessageBox(NULL, L"ウィンドウ作成失敗" , L"エラー", NULL);
return 1;
}
SetWindowTheme(hWnd, L" ", L" "); // ★★ このタイミングで仕込む ★★
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
- 余談
「Uxtheme を使うには プロジェクトプロパティから "リンカー > 入力 > 追加の依存ファイル" でuxtheme.lib
突っ込んでくれ」
…みたいな古めの情報があったんだけど、自環境ではうまくいかず。
代わりに、#pragma comment (lib, "uxtheme")
を冒頭に載せるだけで自分は解決しました。
https://learn.microsoft.com/en-us/answers/questions/302560/x64-and-comctl32-lib
リソースファイル(.rc)でメニューを追加表示
下記リンクを参考に、プロジェクトにリソースファイル(.rc)を追加 ⇒ メニュー項目を作成
シフトシステム株式会社 - 一週間で身につくWIN32プログラミングの基本
特に捻らなければ画像左側末端の通り IDR_MENU1
というリソースシンボル名でメニューが作られるはずなので、
あとはコード内で MAKEINTRESOURCE(IDR_MENU1)
として得たのち WNDCLASSEX.lpszMenuName
へ割り当てればOK
microsoft.com - MAKEINTRESOURCEW マクロ (winuser.h)
( ※ メニュー内項目が押された時の挙動は未設定です )
const WCHAR* szClassName = L"myWindowClass";
WNDCLASSEX wcex;
// …中略…
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
- wcex.lpszMenuName = NULL;
+ wcex.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
wcex.lpszClassName = szClassName;
// …以下略…
Classicスタイルにならない問題
ウィンドウ・メニューバーと違い、展開された子メニュースタイルはOS標準(?)のほうだった。
解決方法がわからなかったので保留というかスルー
もっと深い部分をこねくりまわしながら、動的メニュー作成をすれば上手くいくかもしれない。