Windows エクスプローラーが表示するようなコンテキストメニューを自分のアプリケーションでも表示したいことがあります。
このときに難しいと感じた部分や罠があったので記録として残しておこうと思います。
意外にもまとめてあるページはなかったようなので、誰かの役に立ってくれれば幸いです。
現在のシステムにおいて登録済みのコンテキストメニューを表示するまでの話で、コンテキストメニューの拡張の話では無いのでご注意ください。
コンテキストメニューを取得するまで
目的のコンテキストメニューの取得のゴールは IContextMenu2 を取得することです。
IContextMenu では一部の機能が不足するためです。
流れとしては IContextMenu を取得、その後 QueryInterface で IContextMenu2 を取得します。
IContextMenu は GetUIObjectOf で取得が可能なのですが、このメソッドを正しく呼び出して結果を得るまでが少々大変でした。
色々と試行錯誤しましたが、結果としては以下の点になります。
- 対象ファイル/ディレクトリの親フォルダを示す IShellFolder から GetUIObjectOf を呼び出すこと
- GetUIObjectOf に渡す PIDL は相対PIDL であること
- 親フォルダを示すIShellFolder は絶対PIDLを元に取得する
PIDL について正しく把握している場合には、今回の自分のように悩まないのではと思いました。
まず親ディレクトリの IShellFolder を取得します。
そして対象ファイル/ディレクトリの親からの相対PIDLを取得します。
この相対PIDL を引数にセットして、親のIShellFolder::GetUIObjectOf を呼び出します。
これで IContextMenu を取得することができます。
あとは先だって説明したように IContextMenu2 を取得します。
この後は QueryContextMenu, TrackPopupMenu らで処理が可能になります。
表示されない系の色々
COM を扱うので CoInitialize 系が必要なのは予想がつきましたが、これだけでは不十分でした。OleInitialize を先だって呼び出しておくことが必要でした。そうしないと、一部のメニューが表示されませんでした。
さらに ウィンドウプロシージャで、 WM_INITMENUPOPUP のメッセージを処理する必要があります。このメッセージを受信したら IContextMenu2 の HandleMenuMsg に繋いであげることが必要でした。これを実装しないと、「共有」の項目が空っぽで表示されました。
その他
部分的に間違ったコードを実装しても、制限された最低限のコンテキストメニューが表示できてしまったりするので、バグの発見が遅れます。
また 64bit, 32bit の挙動違いも意識していなければ、一部のコンテキストメニューが表示されない!と悩むことにもなります。
あと取得したコンテキストメニューの情報をチェックしていたときにですが、「ライブラリを取得中…」とメニュー名が取れることがあるようでした。
デバッグ中のステップ実行では見た記憶が無いので、タイミング・経過時間などに依存するのかもと思われます。この項目は、ライブラリや送るのメニューの中身で見かけました。
参考
ITEMID とか ITEMIDLIST だとかの説明はここが参考になりました。
http://www.kab-studio.biz/Programing/Codian/ShellExtension/06.html
これを理解した上で、自分で PIDL操作はせずに ILCombine, ILFindLastID だとかの API を使っていくのがよさそうです。
コメント