ラベルの表示 - Rustで作るWindowsアプリ
- 作成日: 2023-05-08
- 更新日: 2023-12-24
- カテゴリ: windows-rs
ラベルを表示する
Windows APIではウィンドウ内に表示されるボタンやテキストなどの部品を「コントロール」と呼びます。ウィンドウ内にコントロールを配置し、ユーザーがそのコントロールに対して入力を行い、プログラムの振る舞いを変えるというのがWindows APIに限らず一般的なGUIアプリの動作です。
ここではウィンドウ内にラベルを配置してテキストを表示するということをやってみたいと思います。ラベルとはテキストを表示するコントロールで、ラベル内のテキストは変更したり取得したりできます。
ラベルは汎用性のある部品でたとえばテキストボックスの上部にラベルを配置してテキストボックスが何のためにあるのか表示するなどの使われ方をします。
ラベルの作成と配置方法を通じてWindows APIの基本的なコントロールの配置方法を知っておきましょう。
ウィンドウプロシージャの改造
ウィンドウプロシージャ関数を以下のように改造します。
extern "system" fn window_proc(
window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM
) -> LRESULT {
unsafe {
static mut HWND_LABEL: HWND = HWND(0);
static mut COUNT: i32 = 0;
// メッセージ内容によって処理を分岐する
match message {
// ウィンドウの作成時に呼ばれる
WM_CREATE => {
// ラベルの作成
HWND_LABEL = CreateWindowExW(
WINDOW_EX_STYLE::default(),
w!("STATIC"), // ラベルのウィンドウ クラス名
w!("0回描画されました。"), // ラベルに表示するテキスト
WS_VISIBLE | WS_CHILD, // スタイル
50, 50, // XおよびY座標
200, 20, // 幅および高さ
window, // 親ウィンドウのハンドル
None, // メニューハンドル(使用しない)
None, // インスタンスハンドル
None // 追加のアプリケーションデータ
);
LRESULT(0)
}
// ウィンドウの背景を削除するときに呼ばれる
WM_ERASEBKGND => {
let hdc = HDC(wparam.0 as isize);
let mut rect = RECT::default();
// ウィンドウのサイズを取得
GetClientRect(window, &mut rect);
// 白色のブラシで四角形を描画
let h_white_brush: HBRUSH = CreateSolidBrush(COLORREF(0x00ffffff));
FillRect(hdc, &mut rect, h_white_brush);
LRESULT(1) // 背景を消去したことを示すために1を返す
}
// ウィンドウの変更によりクライアント領域の内容が変わった
WM_PAINT => {
let mut ps: PAINTSTRUCT = PAINTSTRUCT::default();
BeginPaint(window, &mut ps);
// ラベル内のテキストを更新
let formatted: String = format!("{}回描画されました。", COUNT);
COUNT += 1;
let mut u16_ary: Vec<u16> = formatted.encode_utf16().collect();
u16_ary.push(0); // 重要: ナル文字をプッシュ
let text = PCWSTR::from_raw(&u16_ary[0]);
SetWindowTextW(HWND_LABEL, text);
// ラベル内のテキストを取得
let mut u16_ary: [u16; 100] = [0; 100];
GetWindowTextW(HWND_LABEL, &mut u16_ary);
let text = String::from_utf16(&u16_ary).unwrap();
println!("{}", text);
// ラベルを再描画
RedrawWindow(HWND_LABEL, None, None, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE);
EndPaint(window, &ps);
LRESULT(0)
}
// ウィンドウが破棄される
WM_DESTROY => {
println!("ウィンドウを破棄します。");
// メッセージキューにWM_QUITメッセージをポストする
PostQuitMessage(0);
LRESULT(0)
}
_ => DefWindowProcW(window, message, wparam, lparam),
}
}
}
他のコードはウィンドウを表示した時と同じです。このコードでは以下の修正を加えています。
WM_CREATE
でラベルの作成WM_ERASEBKGND
で背景色のクリアWM_PAINT
でラベルの再描画
修正を加えたコードを実行すると以下のようなウィンドウが表示されます。
WM_CREATEでラベルを作成する
// ウィンドウの作成時に呼ばれる
WM_CREATE => {
// ラベルの作成
HWND_LABEL = CreateWindowExW(
WINDOW_EX_STYLE::default(),
w!("STATIC"), // ラベルのウィンドウ クラス名
w!("0回描画されました。"), // ラベルに表示するテキスト
WS_VISIBLE | WS_CHILD, // スタイル
50, 50, // XおよびY座標
200, 20, // 幅および高さ
window, // 親ウィンドウのハンドル
None, // メニューハンドル(使用しない)
None, // インスタンスハンドル
None // 追加のアプリケーションデータ
);
LRESULT(0)
}
WM_CREATE
メッセージはウィンドウの作成時に発行されます。ここでHWND_LABEL
を初期化します。HWND_LABEL
はウィンドウプロシージャ内で定義されているstatic
変数です。
static mut HWND_LABEL: HWND = HWND(0);
static mut COUNT: i32 = 0;
これはラベルのウィンドウ ハンドルです。ラベルと言ってもその実体はウィンドウになっています。このラベルの設定で大事なのは以下の点です。
- ウィンドウ クラス名に
STATIC
を設定する - スタイルに
WS_CHILD
を設定する - 親ウィンドウのハンドルを設定する
ウィンドウ クラス名にSTATIC
を設定するとそのウィンドウは静的コントロールになります。静的コントロールは選択不可、キーボードによるフォーカス不可なコントロールです。ラベルの特性から言ってラベルにはこの静的コントロールが適していると言えます。
スタイルにWS_CHILD
を設定するとそのウィンドウは子ウィンドウになります。子ウィンドウにはメニューバーを含めることができません。WS_CHILD
はWS_POPUP
と一緒に使うことはできません。
子ウィンドウの場合は親ウィンドウを設定します。ここではwindow
を指定しています。
WM_ERASEBKGNDで背景色をクリアする
// ウィンドウの背景を削除するときに呼ばれる
WM_ERASEBKGND => {
let hdc = HDC(wparam.0 as isize);
let mut rect = RECT::default();
// ウィンドウのサイズを取得
GetClientRect(window, &mut rect);
// 白色のブラシで四角形を描画
let h_white_brush: HBRUSH = CreateSolidBrush(COLORREF(0x00ffffff));
FillRect(hdc, &mut rect, h_white_brush);
LRESULT(1) // 背景を消去したことを示すために1を返す
}
WM_ERASEBKGND
はウィンドウの背景を削除するときに発行されます。たとえばウィンドウのサイズを変更すると背景色を削除する必要があるためこのメッセージが発行されます。
ここでは背景色を白色のブラシでクリアしています。こうすると背景色が真っ白になります。
WM_PAINTでラベルを再描画する
// ウィンドウの変更によりクライアント領域の内容が変わった
WM_PAINT => {
let mut ps: PAINTSTRUCT = PAINTSTRUCT::default();
BeginPaint(window, &mut ps);
// ラベル内のテキストを更新
let formatted: String = format!("{}回描画されました。", COUNT);
COUNT += 1;
let mut u16_ary: Vec<u16> = formatted.encode_utf16().collect();
u16_ary.push(0); // 重要: ナル文字をプッシュ
let text = PCWSTR::from_raw(&u16_ary[0]);
SetWindowTextW(HWND_LABEL, text);
// ラベル内のテキストを取得
let mut u16_ary: [u16; 100] = [0; 100];
GetWindowTextW(HWND_LABEL, &mut u16_ary);
let text = String::from_utf16(&u16_ary).unwrap();
println!("{}", text);
// ラベルを再描画
RedrawWindow(HWND_LABEL, None, None, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE);
EndPaint(window, &ps);
LRESULT(0)
}
WM_PAINT
内ではラベルを再描画します。ラベルの再描画はRedrawWindow
関数で行います。この関数にHWND_LABEL
を指定して再描画します。
RedrawWindow関数
Function windows::Win32::Graphics::Gdi::RedrawWindow
pub unsafe fn RedrawWindow<P0, P1>(
hwnd: P0, // 再描画するウィンドウのハンドル
lprcupdate: Option<*const RECT>, // 更新する四角形の範囲
hrgnupdate: P1, // 更新リージョンのハンドル。Noneの場合はクライアント領域全体が更新
flags: REDRAW_WINDOW_FLAGS // 再描画フラグ
) -> BOOL
where
P0: IntoParam<HWND>,
P1: IntoParam<HRGN>,
RedrawWindow
関数はウィンドウのクライアント領域を更新します。四角形でも指定可能です。再描画フラグがありこのフラグを設定しておく必要があります。再描画フラグの一部は以下の通りです。
- windows::Win32::Graphics::Gdi
- RDW_ERASE ... ウィンドウが再描画されるときに
WM_ERASEBKGND
を受信 - RDW_INVALIDATE ...
lprcupdate
またはhrgnupdate
の無効化 - RDW_ERASENOW ... 影響を受けるウィンドウが必要に応じて
WM_ERASEBKGND
などのメッセージを受信 - RDW_UPDATENOW ... 影響を受けるウィンドウが必要に応じて
WM_PAINT
などのメッセージを受信
RDW_ERASE
は無効化フラグでこれを指定する場合はRDW_INVALIDATE
も指定する必要があります。
RDW_INVALIDATE
はウィンドウへの無効化フラグで、lprcupdate
とhrgnupdate
が両方None
だった場合はウィンドウ全体が無効になります。
この関数で再描画を行う場合はRDW_UPDATENOW
かRDW_ERASENOW
のいずれかを指定する必要があります。
REDRAW_WINDOW_FLAGS構造体
Struct windows::Win32::Graphics::Gdi::REDRAW_WINDOW_FLAGS
#[repr(transparent)]
pub struct REDRAW_WINDOW_FLAGS(pub u32);
REDRAW_WINDOW_FLAGS
は再描画フラグ用のu32
のラッパーでタプル構造体です。トレイトでビット演算子を実装しておりOR演算子などで複数の定数を合成可能です。
ラベルのテキストの設定と取得
// ラベル内のテキストを更新
let formatted: String = format!("{}回描画されました。", COUNT);
COUNT += 1;
let mut u16_ary: Vec<u16> = formatted.encode_utf16().collect();
u16_ary.push(0); // 重要: ナル文字をプッシュ
let text = PCWSTR::from_raw(&u16_ary[0]);
SetWindowTextW(HWND_LABEL, text);
// ラベル内のテキストを取得
let mut u16_ary: [u16; 100] = [0; 100];
GetWindowTextW(HWND_LABEL, &mut u16_ary);
let text = String::from_utf16(&u16_ary).unwrap();
println!("{}", text);
ラベルにテキストを設定するにはSetWindowTextW
関数を使います。この関数に渡すテキストはPCWSTR
にする必要がありますが、色々コードを書いてformat!
関数で整形した文字列をPCWSTR
に変換しています。format!
関数はRustの組み込みマクロで引数などを指定して文字列を作成できます。ここではCOUNT
というstatic
変数を文字列に格納しています。COUNT
はWM_PAINT
が実行されるたびにインクリメントされます。String
はencode_utf16
メソッドでUTF-16なベクタに変換できます。
このベクタにナル文字をプッシュしている点に注意してください。以前にも書きましたがwindows-rs
の使っているWindows APIはC言語由来のものなので文字列にナル文字を入れておく必要があります。ナル文字が入ってないとC言語の文字列関数は文字列の終着点を検知することができずそのままメモリ空間の彼方へ突っ走ってしまう場合があります。このナル文字を入れる処理をコメントアウトしてプログラムを実行するとアプリがクラッシュする場合があります。
PCWSTR
などのfrom_raw
メソッド等を使う場合は引数のポインタにナル文字が入ってるかどうか注意してください。
ラベルからテキストを取得するにはGetWindowTextW
関数を使います。この関数にはミュータブルなu16
の配列を渡します。DLLの関数を呼び出すときにwindows-rs
側で配列の長さも関数に渡してくれてます。取得したu16
の配列はString
のfrom_utf16
メソッドでRustの文字列にすることができます。取得した文字列をprintln!
関数で出力しています。
SetWindowTextW関数
Function windows::Win32::UI::WindowsAndMessaging::SetWindowTextW
pub unsafe fn SetWindowTextW<P0, P1>(
hwnd: P0, // 変更対象のウィンドウのハンドル
lpstring: P1 // 新しいテキスト
) -> BOOL
where
P0: IntoParam<HWND>,
P1: IntoParam<PCWSTR>,
この関数は指定したウィンドウにタイトルバーが存在する場合、タイトルバーのテキストを変更します。ウィンドウがコントロールならコントロールのテキストを変更します。別のアプリケーションのコントロールのテキストは変更できません。
返り値は成功の場合は0
以外、失敗した場合は0
です。
GetWindowTextW関数
Function windows::Win32::UI::WindowsAndMessaging::GetWindowTextW
pub unsafe fn GetWindowTextW<P0>(
hwnd: P0, // 取得対象のウィンドウのハンドル
lpstring: &mut [u16] // 保存先のu16の配列
) -> i32
where
P0: IntoParam<HWND>,
この関数は指定したウィンドウにタイトルバーが存在する場合、タイトルバーのテキストをlpstring
にコピーします。ウィンドウがコントロールの場合はコントロールのテキストをコピーします。この関数は別のアプリケーションのテキストは取得できません。
この関数は同名のWin32APIの関数が存在しますがwindows-rs
ではこの関数をラップして配列の要素数の引数を省略しています。
返り値は関数が成功した場合はコピーされた文字列の長さ(ナル文字を除く)です。ウィンドウにタイトルやテキストが無かったり空だった場合、ハンドルが無効な場合は戻り値は0
です。