S-JIS[2025-08-05] 変更履歴

Rust windows DialogBoxParamメモ

RustwindowsクレートのDialogBoxParamのメモ。


概要

Win32 APIのDialogBoxParamは、ダイアログを表示する関数。
具体的にはDialogBoxParamAとDialogBoxParamW関数。

ダイアログのレイアウトはリソースで定義する。
RustでWindowsリソースを扱う方法


windowsクレートのDialogBoxParamを使うには、Win32_UI_WindowsAndMessagingフィーチャーを指定する。

Cargo.toml:

〜

[dependencies]
windows = { version = "0.61.3", features = ["Win32_UI_WindowsAndMessaging"] }

DialogBoxParamWの例

ダイアログの定義を作り、DialogBoxParamW関数で表示する例。


ダイアログのrcファイルの作成

まず、ダイアログのレイアウトを拡張子rcのテキストファイルで定義する。

テキストファイルなので手で書くことも可能だろうが、ダイアログのレイアウトをテキストベースで修正したくないので、GUIのエディターを使いたい。
Visutal Studio Community 2022にはGUIのダイアログエディターがあるので、それを使うのが良さそう。


Visutal Studio Community 2022を起動し、新しいプロジェクトを作成する。
テンプレートは「空のプロジェクト(C++)」。

(このテンプレートを使うには、Visutal Studio Community 2022のインストール時に「C++によるデスクトップ開発」を選択しておく必要がある)


プロジェクトを作成したら、ダイアログを新設する。

  1. ソリューションエクスプローラーのツリーから「リソースファイル」を右クリックし、コンテキストメニューから「追加」→「リソース」を選択する。
  2. 「リソースの種類」から「Dialog」を選択し、「新規作成(N)」ボタンを押す。
  3. IDD_DIALOG1のレイアウトが表示される。(表示されるまでしばらく時間がかかる?)

リソースの初期状態は以下のような感じ(抜粋)。

Project1/Project1.rc:

〜
#include "resource.h"
〜
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_DIALOG1 DIALOGEX 0, 0, 308, 176
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,199,156,50,14
    PUSHBUTTON      "キャンセル",IDCANCEL,253,156,50,14
END
〜

Project1/resource.h

〜
#define IDD_DIALOG1                     101
〜

このダイアログ(IDD_DIALOG1)のIDは101として定義されている。
(DialogBoxParamW関数のダイアログIDにこの値を指定する)


リソースのリンク

ダイアログが定義されたrcファイルをコンパイルして拡張子resのリソースファイルを作成し、Rustアプリケーションのビルド時にリンクする。

手動でresファイルを作ってリンクする方法や、rcファイルをコンパイルしてリンクしてくれるクレートを使う方法がある。


ここではembed-resourceクレートを使ってみる。

Rustプロジェクト/Cargo.toml:

〜

[build-dependencies]
embed-resource = "3.0.5"

まず、rcファイルとresource.hをRustプロジェクトのresourceディレクトリーの下にコピーしておく。


それから、build.rsを定義する。

Rustプロジェクト/build.rs:

extern crate embed_resource;

fn main() {
    embed_resource::compile("resource/Project1.rc", embed_resource::NONE)
        .manifest_optional()
        .unwrap();
}

ダイアログの表示

ダイアログを表示するためのDialogBoxParamW関数を呼び出す。

Rustプロジェクト/src/main.rs:

use windows::{
    Win32::{
        Foundation::{GetLastError, HWND, LPARAM, WPARAM},
        UI::WindowsAndMessaging::*,
    },
    core::PCWSTR,
};
fn main() {
    let dialog_id = PCWSTR(101 as *const u16); // IDD_DIALOG1

    unsafe {
        let rc = DialogBoxParamW(None, dialog_id, None, Some(dialog_proc), LPARAM(0));
        if rc < 0 {
            println!("DialogBoxParamW failed: {:?}", GetLastError());
        } else {
            println!("DialogBoxParamW rc={}", rc);
        }
    }
}

ダイアログIDには、rcファイル(resource.h)で定義されているID(IDD_DIALOG1の値)を指定する。

dialog_procは、ダイアログのウィンドウメッセージを処理する(自分の)関数。

DialogBoxParamWの戻り値は、-1ならエラー。
それ以外は、dialog_procのEndDialogによってセットされた値。
OKボタンが押されたら1(IDOKの値)、キャンセルされたときは2(IDCANCELの値)が返るようにしてある。


unsafe extern "system" fn dialog_proc(
    hwnd: HWND,
    message: u32,
    wparam: WPARAM,
    _lparam: LPARAM,
) -> isize {
    const TRUE: isize = 1;
    const FALSE: isize = 0;

    match message {
        // WM_INITDIALOG => {
        //     println!("Dialog initialized");
        //     TRUE
        // }
        WM_COMMAND => {
            let id = wparam.0 as i32;
            if id == IDOK.0 || id == IDCANCEL.0 {
                unsafe {
                    let _ = EndDialog(hwnd, id as isize);
                }
                TRUE
            } else {
                FALSE
            }
        }
        _ => FALSE,
    }
}

中央に表示する例

上記の例では、ダイアログは画面の左上(座標(0, 0))に表示される。
これは、rcファイルで「DIALOGEX 0, 0」と定義されているため。

画面の中央に表示するには、WM_INITDIALOGメッセージ内でダイアログの位置を設定する。

Rustプロジェクト/src/main.rs:

unsafe extern "system" fn dialog_proc(〜) -> isize {
〜
    match message {
        WM_INITDIALOG => match init_dialog(hwnd) {
            Ok(_) => TRUE,
            Err(e) => {
                println!("init_dialog failed. {:?}", e);
                FALSE
            }
        },
〜
    }
〜
}
fn init_dialog(hwnd: HWND) -> Result<(), windows::core::Error> {
    use windows::Win32::Foundation::RECT;
    unsafe {
        let parent_hwnd = match GetParent(hwnd) {
            Ok(value) => value,
            Err(_) => GetDesktopWindow(),
        };

        let mut rect = RECT::default();
        GetWindowRect(hwnd, &mut rect)?;
        let mut parent_rect = RECT::default();
        GetWindowRect(parent_hwnd, &mut parent_rect)?;

        let width = rect.right - rect.left;
        let height = rect.bottom - rect.top;
        let parent_width = parent_rect.right - parent_rect.left;
        let parent_height = parent_rect.bottom - parent_rect.top;

        let x = parent_rect.left + (parent_width - width) / 2;
        let y = parent_rect.top + (parent_height - height) / 2;
        SetWindowPos(hwnd, None, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE)?;
    }
    Ok(())
}

親ウィンドウの位置とダイアログのサイズを元に、座標を地道に計算する^^;


テキスト入力の例

ダイアログにテキストの入力フィールドを追加し、Rustアプリケーションでその値を取得する例。


ダイアログのレイアウト編集

  1. Visual Studio Community 2022のメニューバーから「表示(V)」→「ツールボックス(X)」で、ツールボックスを開く。
  2. ツールボックスの「ダイアログエディター」欄からコントールをドラッグ&ドロップする。
  3. ダイアログ上のEdit Controlを右クリックしてポップアップメニューを開き、「プロパティ(R)」を選択する。
  4. IDを変更する。

ちなみに、rcファイルは以下のような感じになった。

Project1/Project1.rc:

〜
IDD_DIALOG1 DIALOGEX 0, 0, 307, 176
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Example"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,199,156,50,14
    PUSHBUTTON      "キャンセル",IDCANCEL,253,156,50,14
    LTEXT           "ユーザー",IDC_STATIC,27,23,28,8
    EDITTEXT        IDC_USER,71,21,117,14,ES_AUTOHSCROLL
    LTEXT           "パスワード",IDC_STATIC,27,44,34,8
    EDITTEXT        IDC_PASSWORD,71,42,117,14,ES_PASSWORD | ES_AUTOHSCROLL
END
〜

Project1/resource.h:

〜
#define IDD_DIALOG1                     101
#define IDC_USER                        1001
#define IDC_PASSWORD                    1002

DialogBoxParamの変更

DialogBoxParamW関数の引数LPARAMで、入力フィールドの値を受け取る構造体を渡す。
で、dialog_procのWM_COMMANDで、OKボタンが押されたときにその構造体に値をセットする。

Rustプロジェクト/src/main.rs:

#[derive(Debug)]
struct MyDialogValue {
    user: Option<String>,
    password: Option<String>,
}

impl Default for MyDialogValue {
    fn default() -> Self {
        Self {
            user: None,
            password: None,
        }
    }
}
fn main() {
    let dialog_id = PCWSTR(101 as *const u16); // IDD_DIALOG1
    let dialog_value = Box::new(MyDialogValue::default());
    let dialog_value_ptr = Box::into_raw(dialog_value);

    unsafe {
        let rc = DialogBoxParamW(
            None,
            dialog_id,
            None,
            Some(dialog_proc),
            LPARAM(dialog_value_ptr as isize),
        );

        let dialog_value = Box::from_raw(dialog_value_ptr);

        if rc < 0 {
            println!("DialogBoxParamW failed: {:?}", GetLastError());
        } else {
            println!("DialogBoxParamW rc={}, dialog_value={:?}", rc, dialog_value);
        }
    }
}

ダイアログの値を受け取る構造体MyDialogValue(のポインター)を、DialogBoxParamW関数のLPARAMで渡す。


DialogBoxParamW関数で渡したLPARAMは、WM_INITDIALOGイベントで受け取り、SetWindowLongPtrW関数でGWLP_USERDATAに保存しておく。
(WM_COMMANDイベントで渡ってくるLPARAMは、最初に渡したLPARAMとは異なる値なので)

unsafe extern "system" fn dialog_proc(〜) -> isize {
〜
    match message {
        WM_INITDIALOG => {
            unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, lparam.0) };
〜
        }
        WM_COMMAND => {
            let id = wparam.0 as i32;
            if id == IDOK.0 || id == IDCANCEL.0 {
                if id == IDOK.0 {
                    dialog_result(hwnd);
                }
                unsafe {
                    let _ = EndDialog(hwnd, id as isize);
                }
                TRUE
            } else {
                FALSE
            }
        }
〜
    }
〜
}

OKボタンが押されたらdialog_resultを呼ぶようにする。


dialog_resultでは、ダイアログの入力フィールドの値を取得してMyDialogValueにセットする。

fn dialog_result(hwnd: HWND) {
    let dialog_value = unsafe {
        let ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut MyDialogValue;
        &mut *ptr
    };

    const IDC_USER: i32 = 1001;
    let user = text_value(hwnd, IDC_USER);
    dialog_value.user = Some(user);

    const IDC_PASSWORD: i32 = 1002;
    let password = text_value(hwnd, IDC_PASSWORD);
    dialog_value.password = Some(password);
}

GetWindowLongPtrW関数で、GWLP_USERDATAからMyDialogValue(のポインター)を取得する。


fn text_value(hwnd: HWND, id: i32) -> String {
    use std::{ffi::OsString, os::windows::ffi::OsStringExt};

    let mut buf = [0u16; 1024];
    let len = unsafe { GetDlgItemTextW(hwnd, id, &mut buf) };
    let widestring = OsString::from_wide(&buf[..len as usize]);
    let s = widestring.to_string_lossy();
    s.into()
}

GetDlgItemTextW関数は、ダイアログからテキストを取得する。

GetDlgItemTextW関数の返り値は取得した文字数。
フィールドのテキストの長さがバッファーサイズ以上だった場合は「バッファーサイズ - 1(NUL終端の分)」が返る。つまり、バッファーに入りきらない分はカットされる。


テキストフィールドを初期化する例

ダイアログのテキスト入力フィールドに、Rustアプリケーションから初期値をセットする例。

WM_INITDIALOGイベントで、SetDlgItemTextW関数を使って初期値をセットする。

Rustプロジェクト/src/main.rs:

fn main() {
〜
    let dialog_value = Box::new(MyDialogValue { // ダイアログの初期値
        user: Some("hishidama".into()),
        password: Some("password".into()),
    });
〜
}
fn init_dialog(hwnd: HWND, lparam: LPARAM) -> Result<(), windows::core::Error> {
    let dialog_value = unsafe {
        let ptr = lparam.0 as *mut MyDialogValue;
        &*ptr
    };

    if let Some(s) = &dialog_value.user {
        const IDC_USER: i32 = 1001;
        set_text_value(hwnd, IDC_USER, s)?;
    }
    if let Some(s) = &dialog_value.password {
        const IDC_PASSWORD: i32 = 1002;
        set_text_value(hwnd, IDC_PASSWORD, s)?;
    }
〜
}
fn set_text_value(hwnd: HWND, id: i32, value: &str) -> Result<(), windows::core::Error> {
    let mut v: Vec<u16> = value.encode_utf16().collect();
    v.push(0); // NUL終端

    unsafe { SetDlgItemTextW(hwnd, id, PCWSTR(v.as_ptr()))? };
    Ok(())
}

windowsへ戻る / Rustへ戻る / 技術メモへ戻る
メールの送信先:ひしだま