2019年10月19日土曜日

WM_NOTIFY について

WM_NOTIFY についてテクニカルノート[1]を読んだのでまとめた。

プロセス管理ツールを作りたい
→ データ指向の観点から配列でデータを渡したい
→ ListViewに列単位の一括でセット・ゲットできるラッパーを作りたい
→ WM_NOTIFYとはなんぞや(いまここ)

ポイント

  • Windows 3.x までの Windows API で通知メッセージの送信には WM_COMMAND が用いられてきたが、その方法にはいくつかの問題点があった
     
  • Win 32 から新しくコントロールが追加された際に、 WM_NOTIFY を用いた通知メッセージの送信機構が採用された
     
  • WM_NOTIFY メッセージの捌き方について例示する
     

WM_COMMAND について

  • Windows 3.x までの Windows API では、コントロールからのメッセージをウィンドウに通知する際に WM_COMMAND メッセージを用いた
     
  • WM_COMMAND では、wParam にコントロールの ID と通知コード、lParam にコントロールのハンドルが格納される
     
  • WM_COMMAND では wParam と lParam が埋まってしまい、新たに追加の通知コードを増設することが困難であった
     
  • 空きが無い点とは別に、WM_COMMAND には送れる情報が少ないという問題点があった
    (たとえば、マウスがクリックされた際に送信される BN_CLICKED では、マウスカーソルの場所などの情報を送ることができない。追加のデータを送るためには、別のメッセージを設ける必要がある。)

WM_NOTIFY について

  • Windows 3.1 までは WM_COMMAND で事足りていたが、Win32から複雑で洗練されたコントロールが追加されるこにとなった。Windows API の設計者は、新たにメッセージを追加するのではなく、WM_NOTIFY というたった一つのメッセージを追加することでコントロールの追加に対応した
     
  • WM_NOTIFY では wParam にメッセージを送信したコントロールの ID が格納される。一方で、lParam にはNMHDR 構造体か、コントロール依存のより大きな構造体(第一メンバがNMHDR 構造体でありキャストが可能)である
     
  • NMHDR 構造体のメンバはコントロールの ID とウィンドウハンドル、通知コードである
     

WM_NOTIFY メッセージの捌き方

ListView コントロールのメッセージ処理を例にとり、WM_NOTIFY の捌き方を述べる。

ListViewの例

  1. // In window procedure
  2. case WM_NOTIFY:
  3. return OnNofity(hwndDlg, (NMHDR*)lParam);
  4.  
  5. // Function called in WM_NOTIFY
  6. LRESULT OnNofity(HWND hwndDlg, NMHDR* nmhdr) {
  7. assert(nmhdr);
  8. if (nmhdr->hwndFrom == GetDlgItem(hwndDlg, IDC_LIST1)) {
  9. switch (nmhdr->code) {
  10. case LVN_COLUMNCLICK: {
  11. // Process for column click.
  12. LPNMLISTVIEW nmlistview = (LPNMLISTVIEW)nmhdr;
  13. } break;
  14. case NM_CLICK: {
  15. // Process for single click.
  16. } break;
  17. case NM_DBLCLK: {
  18. // Process for double click.
  19. } break;
  20. default:
  21. // none.
  22. break;
  23. }
  24. }
  25. return TRUE;
  26. }
  27.  

ウィンドウプロシージャ(もしくはダイアログプロシージャ)の switch 文の case ラベルに WM_NOTIFY を追加する。
WM_NOTIFY にはメッセージクラッカが用意されていないようなので、適当に関数を作る(好みの問題)。
GetDlgItem でダイアログ上 ListView コントロールのリソース ID からウィンドウハンドルを出力している。
NMHDR (lParam) 構造体には通知メッセージ送信元のウィンドウハンドルが格納されているので、これと ListView コントロールのウィンドウハンドルを比較し、一致するのであれば ポインタを LPNMLISTVIEW 構造体にキャストする。
code は通知の種類であり、ListView の場合は LVN_COLUMNCLICK (ListView に限定)や、NM_CLICK と NM_DBLCLK (コントロール間で共通) といったコードがある[2]。

まとめ

Win32 以降のコントロールの通知メッセージは WM_NOTIFY で捌く。
WM_NOTIFY にはメッセージクラッカが用意されていない。

感想

自前でラッパークラスを作ろうとすると、WM_NOTIFY の処理に悩む。
分かりやすくカプセル化できない?

へんちくりんなことをせず、素直にメッセージ処理はクラス外部のウィンドウ(ダイアログ)プロシージャで行い、一般化できるコントロールの管理とデータ処理はクラスで行うようにするなど、適度に分けるのが良いのかな?

作りたいアプリケーションのデータ設計の分析と、Windows API への十分な理解が大事なんやな。

付録

WM_NOTIFY の処理をしているコードの例はここに。

参考文献

  1. TN061: ON_NOTIFY and WM_NOTIFY Messages
  2. List View Notifications

0 件のコメント:

コメントを投稿

コメント表示は承認制に設定しています