8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rust で Windows プログラミング - Shell_NotifyIcon編

Last updated at Posted at 2019-02-03

##環境
toolchain: stable-i686-pc-windows-msvc
rustc 1.32.0 (9fda7c223 2019-01-16)
winapi 0.3.6

##最小化でタスクトレイに収納
前回ウィンドウを表示したので、そのウィンドウを最小化するとタスクトレイにアイコンとして格納されるようにしてみます。タスクトレイのアイコンを左クリックすると、ウィンドウが再表示されます。

Cargo.toml の features に"shellapi"を追加します。

Cargo.toml
[dependencies.winapi]
version = "0.3"
features = ["winuser", "shellapi"]

main.rs に shellapi module を追加。

main.rs
use winapi::{
    um::{
        winuser::{RegisterClassW, WNDCLASSW, CS_HREDRAW, CS_VREDRAW,
                  LoadIconW, IDI_APPLICATION, LoadCursorW, IDC_ARROW,
                  CreateWindowExW, ShowWindow, SW_NORMAL, SW_HIDE, UpdateWindow,
                  GetMessageW, TranslateMessage, DispatchMessageW, MSG,
                  WM_DESTROY, PostQuitMessage, DefWindowProcW, WS_OVERLAPPEDWINDOW,
                  WM_USER, WM_SYSCOMMAND, SC_MINIMIZE, WM_LBUTTONDOWN},
        wingdi::{GetStockObject, WHITE_BRUSH},
        shellapi::{Shell_NotifyIconW, NOTIFYICONDATAW, NIM_ADD, NIM_DELETE,
                   NIF_ICON, NIF_MESSAGE, NIF_TIP},
    },
    shared::{
        windef::{HWND, HBRUSH},
        minwindef::{UINT, WPARAM, LPARAM, LRESULT},
    },
};
use std::ptr;
use std::mem;

タスクトレイアイコンのメッセージ識別子(?)と識別用のIDを定義し、NOTIFYICONDATA構造体へのポインタをグローバル変数として持っておきます。

main.rs
const MYMSG_TRAY: UINT = WM_USER + 1;
const ID_MYTRAY: UINT = 58091;

static mut P_NID: *mut NOTIFYICONDATAW = ptr::null_mut();

main関数にNOTIFYICONDATA構造体を初期化するコードを追加します。

main.rs
fn main() {
    unsafe {
        let class_name = encode("my_window_class_name");
        if !register_wndclass(&class_name) {
            return;
        }

        let hwnd = create_window(&class_name);
        if hwnd.is_null() {
            return;
        }

        let mut nid = create_nid(hwnd); //追加
        P_NID = &mut nid; //追加

        ShowWindow(hwnd, SW_NORMAL);
        UpdateWindow(hwnd);

        let mut msg = mem::uninitialized::<MSG>();
        loop {
            if GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
                return;
            }
            TranslateMessage(&mut msg);
            DispatchMessageW(&mut msg);
        }
    }
}

create_nid関数の中身です。例によって、std::mem::zeroed()で構造体を初期化して、必要なメンバだけに値を設定してきます。szTip[u16; 128]の配列にNULL終端文字列を入れる必要がありますが、Rust ではどうやると良いのでしょう?

とりあえずサンプルでは、[0u16; 128]で 0 で初期化された配列を用意しておいて、std::ptr::copy()で UTF-16 にエンコードした文字列を配列にコピーするというやり方で対応しました。

main.rs
unsafe fn create_nid(hwnd: HWND) -> NOTIFYICONDATAW {
    let mut nid = mem::zeroed::<NOTIFYICONDATAW>();
    nid.cbSize = mem::size_of::<NOTIFYICONDATAW>() as u32;
    nid.uID = ID_MYTRAY;
    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    nid.hWnd = hwnd;
    nid.uCallbackMessage = MYMSG_TRAY;
    nid.hIcon = LoadIconW(ptr::null_mut(), IDI_APPLICATION);
    let mut buf = [0u16; 128];
    let tip = "Hello, World!";
    ptr::copy(encode(tip).as_ptr(), &mut buf[0], tip.len());
    nid.szTip = buf;
    nid
}

最後にウィンドウプロシージャです。最小化したらアイコンをタスクトレイに表示してウィンドウを非表示に、タスクトレイのアイコンを左クリックしたらアイコンを削除してウィンドウを再表示するという処理です。終了時にタスクトレイにアイコンが残らないように、WM_DESTROYのところでアイコンを消すようにしています。

NOTIFYICONDATA構造体のポインタをグローバル変数として準備しておいたのは、下記のようにウィンドウプロシージャ内でShell_NotifyICon関数に引数として渡す必要があったからです。

main.rs
unsafe extern "system" fn win_proc(hwnd: HWND, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    match msg {
        WM_SYSCOMMAND => {
            match w_param {
                SC_MINIMIZE => {
                    Shell_NotifyIconW(NIM_ADD, P_NID);
                    ShowWindow(hwnd, SW_HIDE);
                },
                _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
            };
        },
        MYMSG_TRAY => {
            match (w_param as u32, l_param as u32) {
                (ID_MYTRAY, WM_LBUTTONDOWN) => {
                    Shell_NotifyIconW(NIM_DELETE, P_NID);
                    ShowWindow(hwnd, SW_NORMAL);
                },
                _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
            }
        },
        WM_DESTROY => {
            Shell_NotifyIconW(NIM_DELETE, P_NID);
            PostQuitMessage(0);
        },
        _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
    };
    0
}

main.rs をまとめてどうぞ。

main.rs
use winapi::{
    um::{
        winuser::{RegisterClassW, WNDCLASSW, CS_HREDRAW, CS_VREDRAW,
                  LoadIconW, IDI_APPLICATION, LoadCursorW, IDC_ARROW,
                  CreateWindowExW, ShowWindow, SW_NORMAL, SW_HIDE, UpdateWindow,
                  GetMessageW, TranslateMessage, DispatchMessageW, MSG,
                  WM_DESTROY, PostQuitMessage, DefWindowProcW, WS_OVERLAPPEDWINDOW,
                  WM_USER, WM_SYSCOMMAND, SC_MINIMIZE, WM_LBUTTONDOWN},
        wingdi::{GetStockObject, WHITE_BRUSH},
        shellapi::{Shell_NotifyIconW, NOTIFYICONDATAW, NIM_ADD, NIM_DELETE,
                   NIF_ICON, NIF_MESSAGE, NIF_TIP},
    },
    shared::{
        windef::{HWND, HBRUSH},
        minwindef::{UINT, WPARAM, LPARAM, LRESULT},
    },
};
use std::ptr;
use std::mem;

const MYMSG_TRAY: UINT = WM_USER + 1;
const ID_MYTRAY: UINT = 58091;

static mut P_NID: *mut NOTIFYICONDATAW = ptr::null_mut();

fn main() {
    unsafe {
        let class_name = encode("my_window_class_name");
        if !register_wndclass(&class_name) {
            return;
        }

        let hwnd = create_window(&class_name);
        if hwnd.is_null() {
            return;
        }

        let mut nid = create_nid(hwnd);
        P_NID = &mut nid;

        ShowWindow(hwnd, SW_NORMAL);
        UpdateWindow(hwnd);

        let mut msg = mem::uninitialized::<MSG>();
        loop {
            if GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
                return;
            }
            TranslateMessage(&mut msg);
            DispatchMessageW(&mut msg);
        }
    }
}

fn encode(source: &str) -> Vec<u16> {
    source.encode_utf16().chain(Some(0)).collect()
}

unsafe fn register_wndclass(class_name: &[u16]) -> bool {
    let mut winc = mem::zeroed::<WNDCLASSW>();
    winc.style = CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc = Some(win_proc);
    winc.hIcon = LoadIconW(ptr::null_mut(), IDI_APPLICATION);
    winc.hCursor = LoadCursorW(ptr::null_mut(), IDC_ARROW);
    winc.hbrBackground = GetStockObject(WHITE_BRUSH as i32) as HBRUSH;
    winc.lpszClassName = class_name.as_ptr();

    RegisterClassW(&winc) > 0
}

unsafe fn create_window(class_name: &[u16]) -> HWND {
    CreateWindowExW(
        0,
        class_name.as_ptr(),
        encode("Hello, World!").as_ptr(),
        WS_OVERLAPPEDWINDOW,
        0,0, 200, 200,
        ptr::null_mut(),
        ptr::null_mut(),
        ptr::null_mut(),
        ptr::null_mut(),
    )
}

unsafe fn create_nid(hwnd: HWND) -> NOTIFYICONDATAW {
    let mut nid = mem::zeroed::<NOTIFYICONDATAW>();
    nid.cbSize = mem::size_of::<NOTIFYICONDATAW>() as u32;
    nid.uID = ID_MYTRAY;
    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    nid.hWnd = hwnd;
    nid.uCallbackMessage = MYMSG_TRAY;
    nid.hIcon = LoadIconW(ptr::null_mut(), IDI_APPLICATION);
    let mut buf = [0u16; 128];
    let tip = "Hello, World!";
    ptr::copy(encode(tip).as_ptr(), &mut buf[0], tip.len());
    nid.szTip = buf;
    nid
}

unsafe extern "system" fn win_proc(hwnd: HWND, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    match msg {
        WM_SYSCOMMAND => {
            match w_param {
                SC_MINIMIZE => {
                    Shell_NotifyIconW(NIM_ADD, P_NID);
                    ShowWindow(hwnd, SW_HIDE);
                },
                _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
            };
        },
        MYMSG_TRAY => {
            match (w_param as u32, l_param as u32) {
                (ID_MYTRAY, WM_LBUTTONDOWN) => {
                    Shell_NotifyIconW(NIM_DELETE, P_NID);
                    ShowWindow(hwnd, SW_NORMAL);
                },
                _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
            }
        },
        WM_DESTROY => {
            Shell_NotifyIconW(NIM_DELETE, P_NID);
            PostQuitMessage(0);
        },
        _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
    };
    0
}

また何かネタを思いついたら投稿します。

今までのネタ
Rust で Windows プログラミング - MessageBox編
Rust で Windows プログラミング - CreateWindow編

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?