昨日打ち込んだ「猫でも」のサンプルは、SDIウィンドウにサイズ固定の矩形と文字列を描画するものでした。
しかしこのままでは面白くないので、ウィンドウサイズを変えると矩形と文字列(フォントサイズ)もつられて変化するように改造しようと考えました。
この場合、C++プログラマーはWM_SIZEメッセエージを捕まえてlParamのLOWORD(幅)とHIWORD(高さ)を取得して、それをもとに描画サイズを変更しようとします。ところが高級言語C#ではメッセージループ等なく(注)、(描画の場合のOnPaintイベントのように)WM_SIZEのイベントを発生させるのではないかと、OnSizeとかOnResizeとかのイベントを探ってみましたが見当たりません。途方に暮れていたら「あっ、C#ではこういうものはプロパティで取得するのかな?」ということで(フォーム.)Sizeプロパティを調べたらそれはWindowサイズで、更に調べて(フォーム.)ClientRectangle.Sizeで随時クライアントエリアのサイズを取得することができることがわかりました。(結構な探索時間がかかりましたよ。)
注:調べてゆく過程で、↓のvoid WndProcメソッドがあることがわかりました。これを使うとC++的プログラミングができますが、あえてC#を下級化することはないのよほどの変態的処理でなければ使わないのでしょうね。以下はこのメソッドをオーバーライドしてWM_CREATEを拾って処理をする場合のコードです。
protected override void WndProc(ref Message message)
{
base.WndProc(ref message);
if (message.Msg == WM_CREATE) {
(処理);
}
}
以下が改造されたサンプルプログラムのコードです。赤字が改造部分で、たったこれだけでウィンドウサイズ変更処理ができました!
// drawstring02.cs
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;
class drawstring02 : Form
{
public static void Main()
{
drawstring02 d2 = new drawstring02();
Application.Run(d2);
}
public drawstring02() //コンストラクター
{
Text = "猫でもわかるプログラミング";
BackColor = Color.White;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Size size = ClientRectangle.Size;
Graphics g = e.Graphics;
string str = "今日はよい天気です。\r\n" +
"しかし明日もよい天気かどうかはわかりません。" +
"明日は、明日の風が吹きます。";
Font ft = new Font("MS ゴシック", size.Width / 20);
RectangleF rf = new RectangleF(10F, 10F, (size.Width - 20), (size.Height - 20));
g.DrawRectangle(new Pen(Color.Blue), 10, 10, (size.Width - 20), (size.Height - 20));
g.DrawString(str, ft, Brushes.Black, rf);
}
}
ここで「では、コード量を減らすためのBCCSkeltonで同様のものを書いたらどうなるのか?」という疑問が生じ、早速やってみました。(注)
注:最短のコードはBCCSkeltonのCANVASクラスを使う必要がありますが、C#では仮想ウィンドウを使っていないので、一応BCCSkeltonもOnPaint関数(WM_PAINTメッセージ処理)でWin32 API のみのコードもコメントで書いておきました。
//////////////////////////////////////////
// TestCat.h
// Copyright (c) 11/05/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include "BCCSkelton.h"
//リソースIDのヘッダー
#include "ResTestCat.h"
/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCSDIクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CSDI
{
public: //以下はコールバック関数マクロと関連している
//2重起動防止用のMutex用ID名称
CMyWnd(char* UName) : CSDI(UName) {}
//メンバー変数
int m_Width; //クライアントエリア幅
int m_Height; //クライアントエリア高さ
//メニュー項目、ダイアログコントロール関連
//ウィンドウメッセージ関連
bool OnSize(WPARAM, LPARAM);
bool OnPaint(WPARAM, LPARAM);
bool OnClose(WPARAM, LPARAM);
};
////////////////////////////////////////////////////////////////////////
//派生させたCMyWndクラスのインスタンスとコールバック関数(マクロ)の作成
//主ウィンドウはダイアログと違い、コールバック関数は一つしか作れない
////////////////////////////////////////////////////////////////////////
CMyWnd TestCat("TestCat"); //ウィンドウクラスインスタンスの生成
BEGIN_SDIMSG(TestCat) //ダイアログと違い、コールバック関数名を特定しない
//メニュー項目、ダイアログコントロール関連
//ウィンドウメッセージ関連
ON_SIZE(TestCat)
ON_PAINT(TestCat)
ON_CLOSE(TestCat)
END_WNDMSG
//--- CANVASを使用 ここから ---
///////////////////////
//仮想ウィンドウの作成
///////////////////////
CANVAS cvs;
//--- CANVASを使用 ここまで ---
//////////////////////////////////////////
// TestCatProc.h
// Copyright (c) 11/05/2022 by BCCSkelton
//////////////////////////////////////////
/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////
bool CMyWnd::OnSize(WPARAM wParam, LPARAM lParam) {
m_Width = LOWORD(lParam);
m_Height = HIWORD(lParam);
//--- CANVASを使用 ここから ---
//仮想ウィンドウの初期化(一回だけ実行)
static bool flag = TRUE;
if(flag) {
cvs.SetCanvas(m_hWnd);
flag = FALSE;
}
//矩形描画
cvs.Clear();
cvs.Color(9);
cvs.Box(10, 10, m_Width - 20, m_Height - 20, 0);
cvs.Color(0);
//メッセージ描画
char* str = "今日はよい天気です。\r\nしかし明日もよい天気かどうかはわかりません。明日は、明日の風が吹きます。";
RECT rec = {12, 12, m_Width - 24, m_Height - 24};
cvs.PrintTextEx(str, &rec, RGB(0, 0, 0), "MS ゴシック", m_Width / 20, DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOCLIP);
//--- CANVASを使用 ここまで ---
return TRUE;
}
/* --- CANVASを不使用 ここから ---
bool CMyWnd::OnPaint(WPARAM wParam, LPARAM lParam) {
//描画のための構造体、ハンドル変数の宣言と初期化
PAINTSTRUCT paint;
HDC hDC = BeginPaint(m_hWnd, &paint);
HPEN hPen = (HPEN)CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
HPEN hOldPen = (HPEN)SelectObject(hDC, hPen);
HBRUSH hBrush = (HBRUSH)CreateSolidBrush(RGB(255, 255, 255));
HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
//矩形描画
Rectangle(hDC, 10, 10, m_Width - 20, m_Height - 20);
//メッセージ描画
char* str = "今日はよい天気です。\r\nしかし明日もよい天気かどうかはわかりません。明日は、明日の風が吹きます。";
HFONT hFont = CreateFont(m_Width / 20, 0,
0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH, "MS ゴシック");
HFONT hOldFont = (HFONT)SelectObject(hDC, hFont);
SetTextColor(hDC, RGB(0, 0, 0));
RECT rec = {12, 12, m_Width - 24, m_Height - 24};
DrawText(hDC, str, -1, &rec, DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOCLIP);
SelectObject(hDC, hOldFont);
DeleteObject(hFont);
//宣言したペンとブラシのメモリーを開放し、元に戻す
SelectObject(paint.hdc, hOldBrush);
DeleteObject(hBrush);
SelectObject(paint.hdc, hOldPen);
DeleteObject(hPen);
EndPaint(m_hWnd, &paint);
return TRUE;
}
--- CANVASを不使用 ここまで --- */
//--- CANVASを使用 ここから ---
bool CMyWnd::OnPaint(WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT paint;
cvs.OnPaint(BeginPaint(m_hWnd, &paint));
EndPaint(m_hWnd, &paint);
return TRUE;
}
//--- CANVASを使用 ここまで ---
bool CMyWnd::OnClose(WPARAM wParam, LPARAM lParam) {
if(MessageBox(m_hWnd, "終了しますか", "終了確認",
MB_YESNO | MB_ICONINFORMATION) == IDYES)
//処理をするとDestroyWindow、PostQuitMessageが呼ばれる
return TRUE;
else
//そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
return FALSE;
}
//////////////////////////////////////////
// TestCat.cpp
//Copyright (c) 11/05/2022 by BCCSkelton
//////////////////////////////////////////
#include "TestCat.h"
#include "TestCatProc.h"
////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
//2重起動防止
if(!TestCat.IsOnlyOne()) {
HWND hWnd = FindWindow("MainWnd", "猫でもわかるプログラミング");
if(IsIconic(hWnd))
ShowWindow(hWnd, SW_RESTORE);
SetForegroundWindow(hWnd);
return 0L;
}
//ウィンドウ登録 - Init(ClassName, hInstance, WndProc, "IDM_MAIN",
// (以下省略可)MAKEINTRESOURCE(IDI_ICON), IDC_ARROW, Brush)
TestCat.Init("MainWnd", hInstance, SDIPROC, "", MAKEINTRESOURCE(IDI_ICON));
//ウィンドウ作成と表示-Create(WindowTitle, (以下省略可)Style,
// ExStyle, hParent, hMenu, x, y, w, h)
if(!TestCat.Create("猫でもわかるプログラミング"))
return 0L;
//メッセージループに入る
return TestCat.Loop();
}

いやはや、如何にC#が高級言語か、ということを見せつける差ですね。一応BCCSkeltonの実行画面も載せておきます。(システムアイコンが違いますね。)
一方、段々とC#に慣れてくるとC++のプログラミングが細部まで、細かくプログラマーが気を払わないとならないので大変だなと感じ始めました。実際今回の↑のサンプルを書く時にも結構忘れていることが多く、時間がかかりました。最後はC++でプログラムを書けなくなっちゃうのじゃないか、と心配です。
しかし、一番印象的であったのはBCCSkeltonの exe ファイルは約78KB(Win32 AP!-77.5KB、CANVAS-78KB)であるのに対し、C#の exe ファイルは約5KBと極めてコンパクトなことです。C#は自分のランタイムを持たず、OSの機能に依拠していることがわかります。


