前言
本文主要內容為nw.js官方文檔中沒有提到,而在實際入手開發過程中才碰到的問題以及經驗的匯總。
詳情請查看官方文檔:http://docs.nwjs.io/en/latest/References/Menu/
1. MenuStrip與ContextMenu
在聊nwjs中的Menu之前先說下在傳統window桌面端應用開發中的兩種常見的菜單。
windows中的MenuStrip
第一種:MenuStrip,菜單欄,通常在主窗體中的頂部,橫向展示。如圖:

windows中的ContextMenu
第二種:ContextMenu,上下文菜單,也就是右鍵菜單,關聯某個元素,再某個元素上點擊右鍵展現的菜單

nw.js中的nw.Menu
而在nw.js中,將windows系統的這兩種菜單結合成一個對象:nw.Menu。
而區分的方法在於構造的配置對象的 type屬性。
- type屬性設定為"menubar"則展現為MenuStrip行為,即窗體頂部菜單欄。
- type屬性設定為"contextmenu"則展現為右鍵菜單行為。默認為右鍵菜單
官方說明:
/**
* Object that contains options to use while creation of nw.Menu. example: new nw.Menu(MenuOption)
*/
interface MenuOption {
/**
* {string} (Optional) two types are accepted by this method: "menubar" or "contextmenu". The value is set to "contextmenu" by default.
*/
type: string;
}
2. nwjs中的多級菜單結構組成
在nwjs中,有關菜單的只有兩個對象,nw.Menu 和 nw.MenuItem。其中nw.Menu更多的功能與行為應該稱之為 菜單集合。而 nw.MenuItem才是真正的 菜單項。
用一張圖來描述Menu和MenuItem對應的實際結構如圖:

如上圖,紅框為Menu,橙色為MenuItem,所有MenuItem的集合均為Menu,而MenuItem的子集的類型為Menu。
接下來創建一個上圖中的頂部菜單欄的代碼示例:
//創建一個頂部菜單欄,類型為:menubar
var menuBar = new nw.Menu({
type: 'menubar'
});
//創建一個一級菜單項-文件
var fileMenu = new nw.MenuItem({
label: "文件",
});
//文件菜單的子菜單集合,類型為:contextmenu
var fileMenuColl = new nw.Menu({ type: "contextmenu" });
//設定一級菜單文件的子菜單
fileMenu.submenu = fileMenuColl;
// 創建一級菜單文件的子菜單:打開
var openMenu = new nw.MenuItem({
label: "打開",
click: function () {
console.log("打開");
},
});
//將打開菜單項 添加入文件子菜單集合中
fileMenuColl.append(openMenu);
//創建一級菜單文件的子菜單:資源管理器
var explorerMenu = new nw.MenuItem({
label: "資源管理器",
click: function () {
console.log("資源管理器");
},
});
fileMenuColl.append(explorerMenu);
//最后將一級菜單項文件 添加入菜單欄
menuBar.append(fileMenu);
//設定窗體菜單欄
nw.Window.get().menu = menuBar;
效果如下:

3. MenItem必須最后再被Menu.append添加
問題見代碼示例。
如將上述代碼化簡后:
var menuBar = new nw.Menu({ type: 'menubar' });
var fileMenu = new nw.MenuItem({ label: "一級菜單" });
var fileMenuColl = new nw.Menu();
fileMenu.submenu = fileMenuColl;
var openMenu = new nw.MenuItem({ label: "二級菜單" });
fileMenuColl.append(openMenu);
menuBar.append(fileMenu);//關鍵,放在最后沒問題,正常!
win.menu = menuBar;
效果如下:

但是如果將menuBar.append(fileMenu)放在剛剛創建完fileMenu之后的話:
var menuBar = new nw.Menu({ type: 'menubar' });
var fileMenu = new nw.MenuItem({ label: "一級菜單" });
//關鍵,創建fileMenu后立刻添加入菜單欄,會發生無法顯示二級菜單的問題!!!
menuBar.append(fileMenu);
var fileMenuColl = new nw.Menu();
fileMenu.submenu = fileMenuColl;
var openMenu = new nw.MenuItem({ label: "二級菜單" });
fileMenuColl.append(openMenu);
win.menu = menuBar;
問題:會出現二級菜單無法打開,只能看到一級菜單的問題。
另外,如果再創建fileMenu時直接在構造函數的配置對象中制定了submenu的話,也可以避規此問題。
私以為這個應該屬於nwjs的一個bug或是一個缺陷。理論上來講都屬性引用類型,先append再新增,或先新增再append應該都是可以的。至少在C#的WinForm開發菜單時是這樣的。
原因不明。
解決方案:只能在開發過程中嚴格遵守: 所有級別的菜單項,必須全部創建完成后最后再被父級append。
4. nwjs頂部菜單欄不支持純一級菜單
在windows應用程序中:
頂部菜單欄可以只有一級菜單,而沒有其下屬二級菜單。沒有二級菜單也可實現相應各個點擊事件等等,不強制要求必須有二級菜單。
而在nwjs中:
頂部菜單欄的純一級菜單,即沒有二級菜單項的一級菜單無法響應點擊事件,其設定的click事件也是無效的,必須在二級菜單及更高級別的菜單中才可以響應點擊事件。
這就帶來一個問題:
在把純windows應用程序使用nwjs重寫時,那些純一級菜單就必須折疊到二級菜單中,才能使用。
另外:
nwjs中只有頂部菜單受此影響,右鍵菜單不受此影響。
這一點官方也有說明:
To create a menubar, usually you have to create a 2-level menu and assign it to win.menu
要創建一個窗體頂部菜單欄,必須使用二級菜單,並賦值給win.menu
個人猜測:
nwjs只所以這樣做是因為在mac OS系統中,似乎不支持純一級菜單項。所以nwjs為了要兼容三方平台,統一行為,所以屏蔽了菜單欄的純一級菜單項的點擊功能,讓其不具有實際功能,只能打開二級菜單欄。
5. 關於MenuItem
關於type屬性
MenuItem菜單項有三種類型normal,checkbox,separator。
normal:標准模式,也是默認模式,純文本菜單項。
checkbox:可選中的菜單項,前面會有一個對勾,重復點擊狀態來回切換。
separator:分割菜單項,展現為一條直線分隔符。
示例代碼:
var reloadMenu = new nw.MenuItem({
label: "刷新",
type: "normal",
});
var separatorMenu = new nw.MenuItem({
type: "separator"
});
var checkMenu = new nw.MenuItem({
type: "checkbox",
label: "是否展現縮略圖"
});
var exitMenu = new nw.MenuItem({
label: "退出",
});
效果如下:

另外:MenuItem的type屬性只能在創建時設定,不能在運行時動態更改。
key屬性不支持菜單欄形式下的一級菜單
在windows系統中:
菜單欄中的文字后面的括號下划線英文,是展開此菜單欄的快捷鍵,方式是 ALT+對應英文字母。
如下圖,展開文件菜單的快捷鍵是 ALT+F,執行文件菜單中的關閉菜單項的快捷鍵是 ALT+C。

在nwjs中:
也提供了類似的功能,是MenuItem的key屬性和modifiers屬性。
key屬性用來設定觸發的快捷鍵。
modifiers屬性用來設定跟快捷鍵相關聯的修飾鍵。
如我們要設定菜單項刷新的快捷鍵為F5則可以:
var reloadMenu = new nw.MenuItem({
label: "刷新",
key: "F5",
click: function () {
pageWindow.location.reload();
},
});
如要設定為快捷鍵為ALT+R則可以:
var reloadMenu = new nw.MenuItem({
label: "刷新",
key: "r",
modifiers:"alt",
click: function () {
pageWindow.location.reload();
},
});
展現效果為:

nwjs的問題在於:
nwjs中不支持設定菜單欄的一級菜單的快捷鍵。
也就是無論如何設定,一級菜單都不會相應快捷鍵自動彈出顯示。無法實現windows系統中的效果。只有二級菜單及以下才生效。
另外:
如果給一個含有三級菜單的二級菜單設定快捷鍵,也就是給一個包含子集展開項設定了快捷鍵,其行為也不會像windows系統中那樣展開他的下屬菜單集合,而是直接執行當前它本身的click事件。
//刷新菜單的下屬子集
var reloadMenuColl = new nw.Menu();
reloadMenuColl.append(new nw.MenuItem({ label: "刷新二級" }));
/** 操作-刷新 */
var reloadMenu = new nw.MenuItem({
label: "刷新",
key: "r",
modifiers: "alt",
click: function () {
pageWindow.location.reload();
},
submenu: reloadMenuColl
});
如圖:

按下快捷鍵ALT+R,直接刷新了頁面,而沒有展開二級菜單。但如果用鼠標點擊那個刷新菜單的話,是無論如何也不會觸發刷新操作的。但通過快捷鍵反倒可以...
有關這一點nwjs的官方文檔中並沒有特殊說明,我姑且認定為沒有太大影響的缺陷吧。詳見:http://docs.nwjs.io/en/latest/References/MenuItem/
源代碼下載
本文上述源代碼已托管至Github:
https://github.com/xxcanghai/nwjs-demo/tree/master/menu
歡迎Start & Follow~
后記
以上為nw.js入坑兩周來的有關菜單開發的小記。因為我們要兼容XP系統,所以沒得選,只能用nw.js。其github上的issue區也是紅紅火火,只能說感覺nw在很多功能細節上還需打磨和完善。
