←HSPハック トップへ

IE風メニューバーを作る 3

IE風メニューバーを作ります。キーボードナビゲーションというものを実装します。

最終更新:2017/12/05

初版:2017/12/05




はじめに


前回の続きです。
今回はキーボード(矢印キー、アクセラレーター)でメニューの操作ができるようにします。
※hscallbk.dllが必要です。

手順


  1. メインウィンドウを作成したら、キーボードフックをインストールする。
  2. ■キーボードフックプロシージャー内の処理
    codeの値がHC_ACTIONの時に以下の処理をする。
    • ツールバーにTB_MAPACCELERATORメッセージを送信して、押したキーに対応するボタンIDを取得する。(wParamにキーボードフック関数のwParam値を指定、lParamにボタンIDを代入する変数へのポインタを指定する。)
    • 返り値が0以外で、Altキーを押していたら、ツールバーにTB_SETHOTITEMメッセージを送信してメニューを表示させる。

  3. メニューを表示させる前に、WH_MSGFILTERタイプのメッセージフックをインストールする。(前回と共通)
  4. メニューを表示させる。
  5. ■メッセージフックプロシージャー内の処理
    codeの値がMSGF_MENUの時に以下の処理をする。
    lParamからMSG構造体を取り出しておく。
    • MSG構造体のmessageメンバの値がVK_LEFT/VK_RIGHTの時、ツールバーにTB_SETHOTITEMメッセージを送信して前後のメニューを表示させる。
    • (右のメニューに移動する際、サブメニューを持つメニューが選択状態の時はTB_SETHOTITEMメッセージを送信しない。)

  6. メニューが消えたら、メッセージフックを削除する。(前回と共通)
  7. アプリケーション終了時にキーボードフックを削除する。

ソースコード


#include "user32.as"
#include "kernel32.as"
#include "hscallbk.as"
#uselib ""
#func _MsgHookProc "" int,int,int
setcallbk MsgHookProc, _MsgHookProc,*OnMsgHookProc //メッセージフックプロシージャー登録
setcallbk KbdHookProc, _MsgHookProc,*OnKbdHookProc //キーボードフックプロシージャー登録

#define RBS_TOOLTIPS        0x00000100
#define RBS_VARHEIGHT       0x00000200
#define RBS_BANDBORDERS     0x00000400
#define RBS_FIXEDORDER      0x00000800
#define RBS_REGISTERDROP    0x00001000
#define RBS_AUTOSIZE        0x00002000
#define RBS_VERTICALGRIPPER 0x00004000 
#define RBS_DBLCLKTOGGLE    0x00008000

#define TBSTYLE_ALTDRAG               0x00000400
#define TBSTYLE_AUTOSIZE              0x00000010
#define TBSTYLE_BUTTON                0x00000000
#define TBSTYLE_CHECK                 0x00000002
#define TBSTYLE_CHECKGROUP            0x00000006
#define TBSTYLE_CUSTOMERASE           0x00002000
#define TBSTYLE_DROPDOWN              0x00000008
#define TBSTYLE_EX_DRAWDDARROWS       0x00000001
#define TBSTYLE_EX_HIDECLIPPEDBUTTONS 0x00000010
#define TBSTYLE_EX_MIXEDBUTTONS       0x00000008
#define TBSTYLE_FLAT                  0x00000800
#define TBSTYLE_GROUP                 0x00000004
#define TBSTYLE_LIST                  0x00001000
#define TBSTYLE_NOPREFIX              0x00000020
#define TBSTYLE_REGISTERDROP          0x00004000
#define TBSTYLE_SEP                   0x00000001
#define TBSTYLE_TOOLTIPS              0x00000100
#define TBSTYLE_TRANSPARENT           0x00008000
#define TBSTYLE_WRAPABLE              0x00000200

#define CCS_ADJUSTABLE      0x00000020
#define CCS_BOTTOM          0x00000003
#define CCS_LEFT            0x00000081
#define CCS_NODIVIDER       0x00000040
#define CCS_NOMOVEX         0x00000082
#define CCS_NOMOVEY         0x00000002
#define CCS_NOPARENTALIGN   0x00000008
#define CCS_NORESIZE        0x00000004
#define CCS_RIGHT           0x00000083
#define CCS_TOP             0x00000001
#define CCS_VERT            0x00000080

#define RBBIM_STYLE         0x00000001
#define RBBIM_COLORS        0x00000002
#define RBBIM_TEXT          0x00000004
#define RBBIM_IMAGE         0x00000008
#define RBBIM_CHILD         0x00000010
#define RBBIM_CHILDSIZE     0x00000020
#define RBBIM_SIZE          0x00000040
#define RBBIM_BACKGROUND    0x00000080
#define RBBIM_ID            0x00000100
#define RBBIM_IDEALSIZE     0x00000200
#define RBBIM_LPARAM        0x00000400
#define RBBIM_HEADERSIZE    0x00000800
#define RBBIM_CHEVRONLOCATION 0x00001000
#define RBBIM_CHEVRONSTATE  0x00002000

#define RBBS_BREAK          0x00000001
#define RBBS_FIXEDSIZE      0x00000002
#define RBBS_CHILDEDGE      0x00000004
#define RBBS_HIDDEN         0x00000008
#define RBBS_NOVERT         0x00000010
#define RBBS_FIXEDBMP       0x00000020
#define RBBS_VARIABLEHEIGHT 0x00000040
#define RBBS_GRIPPERALWAYS  0x00000080
#define RBBS_NOGRIPPER      0x00000100
#define RBBS_USECHEVRON     0x00000200
#define RBBS_HIDETITLE      0x00000400
#define RBBS_TOPALIGN       0x00000800

#define WS_CLIPCHILDREN     0x02000000
#define WS_CLIPSIBLINGS     0x04000000
#define WS_VISIBLE          0x10000000
#define WS_CHILD            0x40000000

#define RB_INSERTBAND       0x0401
#define TB_CHECKBUTTON	    0x0402
#define TB_ADDBUTTONS       0x0414
#define TB_BUTTONSTRUCTSIZE 0x041E
#define TB_GETRECT	        0x0433
#define TB_SETHOTITEM       0x0448
#define TB_MAPACCELERATOR   0x044E
#define TB_GETMAXSIZE       0x0453
#define TB_SETEXTENDEDSTYLE 0x0454

#define I_IMAGENONE         -2
#define TBSTATE_ENABLED     0x04
#define BTNS_DROPDOWN       0x08
#define BTNS_AUTOSIZE       0x10

#define WM_NOTIFY           0x004E
#define WM_KEYDOWN          0x0100
#define WM_MENUSELECT       0x011F
#define WM_MOUSEMOVE        0x0200
#define WM_MY_SHOWMENU      0x8000 + 1
#define VK_MENU             0x0012
#define VK_LEFT             0x0025
#define VK_RIGHT            0x0027
#define VK_DOWN             0x0028
#define TBN_DROPDOWN        -710
#define TBN_HOTITEMCHANGE   -713

#define KEYEVENTF_KEYUP     0x0002
#define TPM_NOANIMATION     0x4000

#define REBARBANDINFO_SIZE  22

#define MF_POPUP            0x00000010
#define MF_INSERT           0x00000000
#define MF_SEPARATOR        0x00000800

#define WH_MSGFILTER        -1
#define WH_KEYBOARD         2
#define HC_ACTION           0
#define MSGF_MENU           2

#define ctype LOWORD(%1) (%1&0xFFFF)
#define ctype HIWORD(%1) ((%1>>16)&0xFFFF)
#define ctype MAKELPARAM(%1,%2) (%1&0xFFFF)|(%2<<16)

#enum IDM_NEW = 100
#enum IDM_OPEN
#enum IDM_EXIT
#enum IDM_UNDO
#enum IDM_OPTION
#enum IDM_ABOUT
#enum IDM_SUBMENUITEM

onexit *exit
oncmd gosub *l_OnNotify, WM_NOTIFY
oncmd gosub *l_OnMenuSelect, WM_MENUSELECT
oncmd gosub *l_OnMyShowMenu, WM_MY_SHOWMENU

//メニュー項目を作成
hMenu(4) = CreatePopupMenu()
AppendMenu hMenu(4), MF_INSERT, IDM_SUBMENUITEM, "サブメニュー項目(&S)"

hMenu(3) = CreatePopupMenu()
AppendMenu hMenu(3), MF_INSERT, IDM_ABOUT, "バージョン情報(&A)"

hMenu(2) = CreatePopupMenu()
AppendMenu hMenu(2), MF_POPUP, hMenu(4), "サブメニュー(&S)" //サブメニューを持つ項目を追加してみた
AppendMenu hMenu(2), MF_INSERT, IDM_OPTION, "オプション(&O)"

hMenu(1) = CreatePopupMenu()
AppendMenu hMenu(1), MF_INSERT, IDM_UNDO, "元に戻す(&U)\tCtrl+Z"

hMenu(0) = CreatePopupMenu()
AppendMenu hMenu(0), MF_INSERT, IDM_NEW, "新規作成(&N)\tCtrl+N"
AppendMenu hMenu(0), MF_INSERT, IDM_OPEN, "開く(&O)\tCtrl+O"
AppendMenu hMenu(0), MF_SEPARATOR, 0, 0
AppendMenu hMenu(0), MF_INSERT, IDM_EXIT, "終了(&X)\tAlt+F4"

hMainMenu = CreateMenu() //トップレベルメニュー
AppendMenu hMainMenu, MF_POPUP, hMenu(0), "file"
AppendMenu hMainMenu, MF_POPUP, hMenu(1), "edit"
AppendMenu hMainMenu, MF_POPUP, hMenu(2), "tool"
AppendMenu hMainMenu, MF_POPUP, hMenu(3), "help"

//IE風メニューバーを作成
winobj "ToolbarWindow32", "", 0, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | TBSTYLE_TRANSPARENT | TBSTYLE_REGISTERDROP | TBSTYLE_CUSTOMERASE | TBSTYLE_LIST | TBSTYLE_FLAT | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE | CCS_TOP,200, 20, 0, 0
hToolBar = objinfo_hwnd(stat)
sendmsg hToolBar, TB_BUTTONSTRUCTSIZE, 20, 0
sendmsg hToolBar, TB_SETEXTENDEDSTYLE, 0, 0
MenuName = "ファイル(&F)", "編集(&E)", "ツール(&T)", "ヘルプ(&H)" //メニューの名前
MenuHnd  = hMenu(0), hMenu(1), hMenu(2), hMenu(3) //メニューのハンドル
//ボタン追加
foreach MenuName
	TBB = I_IMAGENONE, cnt, ((BTNS_DROPDOWN|BTNS_AUTOSIZE) << 8) | TBSTATE_ENABLED, 0, varptr(MenuName(cnt))
	sendmsg hToolBar, TB_ADDBUTTONS, 1, varptr(TBB)
loop

//レバーコントロールを作成
winobj "ReBarWindow32", "", 0, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | RBS_BANDBORDERS | RBS_VARHEIGHT, 0, 20, 0, 0
hReBar = objinfo_hwnd(stat)

dim RBBI, REBARBANDINFO_SIZE
RBBI(0)  = REBARBANDINFO_SIZE*4 //cbSize
RBBI(1)  = RBBIM_STYLE | RBBIM_SIZE | RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_ID //fMask
RBBI(2)  = RBBS_BREAK | RBBS_GRIPPERALWAYS //fStyle
RBBI(8)  = hToolBar //hwndChild
RBBI(10) = 26 //cyMinChild
sendmsg hReBar, RB_INSERTBAND, -1, varptr(RBBI) //バンド追加

hKbdHook = SetWindowsHookEx(WH_KEYBOARD, varptr(KbdHookProc), 0, GetCurrentThreadId()) //キーボードフックインストール

nm_mousemoveCount = 0
nm_menuid         = -1
stop

*exit
	EndMenu //メニューを閉じる
	UnhookWindowsHookEx hKbdHook //フック削除
	DestroyMenu hMainMenu //トップレベルメニューを削除
end

*OnMsgHookProc
	if bm_show{ //メニューが表示されている時のみ処理
		 if (callbkarg(0)==MSGF_MENU){
			 dupptr mhp_pMsg, callbkarg(2), 7*4 //MSG構造体を取り出す。
			 MenuHookProc mhp_pMsg(0), mhp_pMsg(1), mhp_pMsg(2), mhp_pMsg(3) //メニューフックプロシージャーに処理を渡す。
		}
	}
return CallNextHookEx( hm_MsgHook, callbkarg(0), callbkarg(1), callbkarg(2) )
*OnKbdHookProc
	if (callbkarg(0)==HC_ACTION){
		nIndex=0
		//アクセラレーター文字に対応したボタンIDを取得する。
		SendMessage hToolbar(0), TB_MAPACCELERATOR, callbkarg(1), varptr(nIndex)
		if ((stat) && (nm_menuid!=nIndex)){
			getkey k,18
			if k{ //Altキーが一緒に押されてたら処理
				bm_ac    = 1
				bm_ckey  = 1
				bm_show  = 1
				nm_menuid=nIndex
				SendMessage hToolbar(0), TB_SETHOTITEM, nIndex, 0
				return 1
			}
		}
	}
return CallNextHookEx( hKbdHook, callbkarg(0), callbkarg(1), callbkarg(2) )
*l_OnNotify
return OnNotify(ginfo(24), iparam, wparam , lparam)
*l_OnMenuSelect
return OnMenuSelect(ginfo(24), iparam, wparam , lparam)
*l_OnMyShowMenu
return OnMyShowMenu(ginfo(24), iparam, wparam , lparam)

#defcfunc OnNotify int msg_wndid, int msg_msg, int msg_wp, int msg_lp //OnNotify 処理用関数
	dupptr Nmhdr, msg_lp, 4*3
	if ((Nmhdr(0) == hToolBar) && (Nmhdr(2) == TBN_DROPDOWN)){ //ツールバーボタンが押された
		dupptr nmtoolbar, msg_lp, 44
		PostMessage hwnd, WM_MY_SHOWMENU, nmtoolbar(3), 0 //WM_MY_SHOWMENUメッセージを発行してメニューを表示。
	}
	if ((Nmhdr(0) == hToolBar) && (Nmhdr(2) == TBN_HOTITEMCHANGE)){ //ツールバーのホットアイテムが変更された
		dupptr nmtbhotitem, msg_lp, 24
		if ((bm_show) && (nmtbhotitem(4)>=0) && (((nm_menuid!=nmtbhotitem(4)) && (bm_chg)) || (bm_ac))){
			EndMenu //メニューを閉じる
			PostMessage hwnd, WM_MY_SHOWMENU, nmtbhotitem(4), TPM_NOANIMATION //WM_MY_SHOWMENUメッセージを発行してメニューを表示。
		}
		return 1
	}
return 0

#defcfunc OnMenuSelect int msg_wndid, int msg_msg, int msg_wp, int msg_lp //OnMenuSelect 処理用関数
	if bm_show{
		bm_haveSubmenu = HIWORD(msg_wp)&MF_POPUP //サブメニューを持つメニューアイテムか判定。
	}
return 0

#defcfunc OnMyShowMenu int msg_wndid, int msg_msg, int msg_wp, int msg_lp //OnMyShowMenu 処理用関数
	//wParam に表示するメニューIDが入ります。
	//lParam にTrackPopupMenuExに渡すフラグを指定します。
	
	dim rc,4
	sendmsg hToolBar ,TB_GETRECT, msg_wp, varptr(rc) //ツールバーボタンの座標を取得
	pt = rc(0),rc(3)
	ClientToScreen hToolBar, varptr(pt)
	sendmsg hToolBar, TB_CHECKBUTTON, msg_wp, 1 //ボタンを押した状態にする

	if bm_ckey{ //通常のメニューと仕様を合わせるため、キーによる操作のときは一番最初の項目をホットアイテムにする。
		keybd_event VK_DOWN, 0, 0, 0
		keybd_event VK_DOWN, 0, KEYEVENTF_KEYUP, 0
	}
	
	bm_show        = 1 //メニュー表示フラグ
	nm_menuid      = msg_wp //表示中メニューID
	bm_chg         = 0 //メニューが変更されたかのフラグ
	bm_haveSubmenu = 0 //サブメニューがあるかのフラグ
	bm_ckey        = 0 //キーで操作したかのフラグ
	bm_ac          = 0 //アクセラレータ文字による操作かのフラグ
	hm_MsgHook     = SetWindowsHookEx(WH_MSGFILTER, varptr(MsgHookProc), 0, GetCurrentThreadId()) //フックインストール
	TrackPopupMenuEx MenuHnd(msg_wp), flag, pt(0), pt(1), hwnd, 0 //メニュー表示
	UnhookWindowsHookEx hm_MsgHook //フック削除
	bm_show        = 0
	nm_menuid      = -1
	bm_chg         = 0
	bm_haveSubmenu = 0
	bm_ac          = 0
	hm_MsgHook     = 0
	
	sendmsg hToolBar, TB_CHECKBUTTON, msg_wp, 0 //ボタンを離した状態にする
return 0

#deffunc MenuHookProc int mhp_hwnd, int mhp_msg, int mhp_wp, int mhp_lp
	switch mhp_msg
		case WM_MOUSEMOVE
			nm_mousemoveCount--
			if nm_mousemoveCount<0:nm_mousemoveCount=0
			if nm_mousemoveCount>0:return
			pt=LOWORD(mhp_lp), HIWORD(mhp_lp)
			
			//マウスカーソルがツールバーの中にあるときだけ処理する。
			Rect=0,0,0,0:size=0,0
			GetWindowRect hToolbar, varptr(Rect)
			SendMessage hToolbar, TB_GETMAXSIZE, 0, varptr(size) //ツールバーのサイズ取得
			Rect(2)=Rect(0)+size(0),Rect(1)+size(1)
			PtInRect varptr(Rect), pt(0), pt(1) //マウスカーソルがツールバーのボタンの上にあるかを判定
			if stat==0:return
			
			ScreenToClient hToolbar, varptr(pt)
			bm_chg=1
			SendMessage hToolbar, mhp_msg, mhp_wp, MAKELPARAM(pt(0), pt(1)) //転送
			bm_chg=0
		swbreak
		case WM_KEYDOWN
			switch mhp_wp
			case VK_LEFT
				if (nm_menuid)<=0:nm_menuid=length(MenuHnd)
				nm_mousemoveCount = 4
				bm_chg  =1
				bm_ckey =1
				SendMessage hToolbar(0), TB_SETHOTITEM, nm_menuid-1, 0
			swbreak
			case VK_RIGHT
				if bm_haveSubmenu:return
				if nm_menuid>=(length(MenuHnd)-1):nm_menuid=-1
				nm_mousemoveCount = 4
				bm_chg  =1
				bm_ckey =1
				SendMessage hToolbar(0), TB_SETHOTITEM, nm_menuid+1, 0
			swbreak
			swend
		swbreak
	swend
return 0

nm_mousemoveCountは、正常に矢印キーでメニューを移動させるために使います。 メニューを表示するときにWM_MOUSEMOVEメッセージが3回ほど来るので、それを無効化させます。

カスタムドローなどを使うとよりIE風メニューバーらしくなります。




[前へ] [戻る] [次へ(作成中...)]

(C)2019 inonote / 無断転載禁止