前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用NW.js构建跨平台桌面应用(2)-原生界面API

用NW.js构建跨平台桌面应用(2)-原生界面API

作者头像
江米小枣
发布2020-06-15 14:56:10
6.4K0
发布2020-06-15 14:56:10
举报
文章被收录于专栏:云前端云前端云前端

[I] 概述 - NW.js原生界面(Native UI)APIs

  • 要构建一个像样的桌面应用,除了由NodeJS处理底层功能,以及由Webkit来应付窗口GUI外,还需要诸如操作窗口、访问剪贴板或隐藏到系统托盘区等和系统图形界面交互的能力
  • 而前面提到的两者,要么无法访问GUI,要么受限于API边界,均无法提供
  • NW.js Native UI APIs 则在其他JS层的顶部提供了这些完整的功能

1.1 获取nw实例

  • 旧版本中可以用 var nw = require('nw.gui') 获取
  • 新版本中直接访问全局成员 nw 即可
//获取当前窗口
var currentWindow = nw.Window.get();//基本上所有的原生界面对象都继承自NodeJS中的EventEmitter
currentWindow.on('minimize', e=>alert('窗口已被最小化')); 

1.2 一些最佳实践

和web应用类似,如果引起某些错误,应用可能崩溃(并且可能没有异常会被抛出),所以一些好的习惯是:

  • 删除元素后,将其引用赋予null,以免不当的重用
  • 尽量重用元素,而不是重复创建
  • 不要重复指定元素
  • 不要更改UI对象的原型

1.3 几个主要的API

APIs

描述

App

设置应用基础功能,包括打开已绑定类型的本地文件、访问manifest文件、注册全局快捷键或退出应用等

Window

操作一个或多个窗口,响应窗口事件等

Screen

用一个单例对象,取得屏幕信息,并响应屏幕分辨率更改、增加屏幕等事件

Menu

用来创建窗口菜单、托盘菜单或右键菜单

File对话框

用文件对话框来打开文件或保存文件等

Tray

管理托盘状态图标

Clipboard

访问系统剪贴板

Shell

调用系统默认应用打开文件等

[II]. App API - 应用的核心

2.1 打开关联类型的文件

NW.js应用有多种办法打开文件,此处谈论的是打开关联的文件类型;也就是说如果我们开发一个文本编辑器,那么我们希望在系统中右键单击一个txt文件出现的“open with...”菜单中,能用我们的应用直接打开它

  • 事实上,当我们进行上述操作时,实际发生的是 nw path/to/app path/to/file.txt
  • 也可以同时打开多个文件 nw path/to/app path/to/a.txt path/to/b.txt
  • 为了访问到这些文件路径,需要使用App.argv属性,其返回一个参数数组
//如果要实际运行例子,需要打包,否则无法获取参数
var args = nw.App.argv;
if (args.length) {
   args.forEach(filepath=>{
       //检查文件是否合法并进行某些操作
   });
}
  • 以上的做法只在程序启动时运行一次
  • 在运行过程中,比如把文件拖放到应用图标上,会以同样的形式传递参数
  • 此时为了拦截到每个打开的文件,需要侦听open事件
//此时的参数是文件路径的字符串
nw.App.on('open', filepath=>{
   //操作文件
});

2.2 访问application data目录路径

所有操作系统都会提供一个默认的文件夹,用来关联每个用户及每个程序,以保存个人设置、应用支持文件,以及某些特定数据;为了避免在程序中硬编码每个平台的对应文件夹,可以用App.dataPath属性统一取得其路径

实际取得的值( 表示manifest文件中配置的应用名 ): - Win: $LOCALAPPDATA%/<name> - Linux: ~/.config/<name> - Mac: ~/Library/Application Support/<name>

//保存配置文件到`application data`目录的例子
var
   fs = require('fs')
   ,path = require('path')
   ,settings = {
       'show_sidebar': true,
       'show_icons': false
   }
   ,settingsFile = path.join(nw.App.dataPath, 'mySettings.json')
;
fs.writeFileSync(settingsFile, JSON.stringify(settings));
fs.readFile(settingsFile, 'utf8', (err, data)=>{
   var saved = JSON.parse(data);
   console.log(saved);
});

2.3 访问manifest文件

正如上一篇中介绍的,应用使用package.json作为主配置文件,尽管可以藉由Node.js取得其引用,但更方便的方法是使用App中的属性

var manifestData = nw.App.manifest;
alert(manifestData.name);

2.4 关闭应用

如果以NW.js应用正常的生命周期来理解,应用打开的所有窗口都依次关闭后,整个应用才能退出;不过有两种方法可以干预这一进程:

App.closeAllWindows()

该方法会触发各子窗口的close事件,从而提供了执行清理动作的机会:

//index.html(第一个窗口,主窗口)
window.open('settings.html');
var currWin = nw.Window.get();
currWin.on('close', function() {
  nw.App.closeAllWindows();
  this.close(true);
});//settings.html(第二个窗口,设置窗口)
var currWin = nw.Window.get();
currWin.on('close', function() {
  //退出之前,在这里保存设置数据
  this.close(true);
});
App.quit()

与上一种方法不同,该方法不发出任何关闭信号,程序直接退出

2.5 注册系统层级的快捷键

利用App.registerGlobalHotKey()方法,并结合 Shortcut API(http://docs.nwjs.io/en/latest/References/Shortcut/),可以注册系统层级的组合快捷键,即便是在应用失去焦点、最小化或缩至托盘后,这些快捷键仍能生效

nw.App.registerGlobalHotKey(
   new nw.Shortcut({
       key: 'Ctrl+Alt+A',
       active: function() { //组合键被正确按下时的回调
           nw.Window.get().show(); //显示当前窗口
           console.log('按下了 ', this.ke);
       }
   })
);
  • 注意尽量不要和其他快捷键冲突,比如'Ctrl+C'
  • 除了用active回调,也可以用同名事件 shortcut.on('active', function() {...})
  • 可以用 App.unregisterGlobalHotKey(shortcut)解除注册

2.6 完整的App文档

[文档地址](http://docs.nwjs.io/en/latest/References/App/)

[III]. Window API - 操作NW.js窗口

在NW.js中,Window API 只不过是对DOM中window对象的一层包装,很多(并非所有)方法和属性继承了后者的用法,同时window对象也是 Node.js EventEmitter 的实例,可以对move或resize等实现事件监听

3.1 实例化

//取得当前窗口
var currWin = nw.Window.get();//向get()方法中传递一个DOM引用,取得其他窗口
var win = nw.Window.get( window.open('other.html') );//也可以用 nw.Window.open(url[,options][,callback]) 方法
var win = nw.Window.open('other.html', {
   // options中的选项和manifest中 [相关参数](http://docs.nwjs.io/en/latest/References/Manifest%20Format/#window-subfields) 一致
   position: 'center',
   width: 600,
   height: 500,
   focus: true,
   
   //也有几个额外定义的选项
   'new-instance': true, //在新的Webkit进程中打开窗口
   'inject-js-start': 'path/to/js', //在文档loaded前注入的脚本
   'inject-js-end': 'path/to/js' //在文档unloaded前注入的脚本
});
优化窗口显示时机

NW.js窗口显示后,代码执行等后台工作还需要一段时间,为了更好等用户体验,可以有意先隐藏窗口

{
   "window": {
       "show": false
   }
}
//html
window.onload = function() {
   nw.Window.get().show();
}
原始的window对象

开头提过:“在NW.js中,Window API 只不过是对DOM中window对象的一层包装”,但很多功能受限无法访问,为了获得原始的引用,可以使用Window.window

var currWin = nw.Window.get();
var nav = currWin.window.navigator;
var lang = nav.language;
console.log(lang);  //zh-CN

3.2 位置和尺寸

在nw.Window实例中,最常见的属性包括 width, height, x, y 等,前两者不言自明,x,y则表示窗口之于屏幕的绝对位置

var currWin = nw.Window.get();
console.log({
  x: currWin.x,
  y: currWin.y,
  width: currWin.width,
  height: currWin.height
});
setTimeout(function() {
  currWin.x = 100;
  currWin.width = 600;
});

可以用以下方法限制窗口大小;但 max/min 不能和 setResizable(false) 方法同时使用,否则会无效

win.setResizable(bool);
win.setMaximumSize(maxW, maxH);
win.setMinimumSize(minW, minY);

可以用如下方法设定窗口位置,其中to设定绝对值,by设定相对偏移量

win.setPosition(posiStr); //有效参数为 null | 'center' | 'mouse'
win.moveTo(x, y);
win.moveBy(x, y);
win.resizeTo(w, h);
win.resizeBy(w, h);

窗口位置或尺寸变化时,触发以下事件

win.on('move', (x,y)=>console.log(x, y));
win.on('resize', (w,h)=>console.log(w, h));

3.3 改变窗口状态

每个桌面窗口都有几种不同的状态:minimized,maximized,hidden,focused,blurclosed;可以用以下方法设置:

var other = nw.Window.get().open('other.html');other.minimize();
other.restore(); //从最小化恢复other.show();
other.hide();other.maximize();
other.unmaximize();other.focus();
  • 应成对使用以上方法并仔细测试,否则会在不同平台引起差异;比如在Mac上用win.open()也可以恢复一个最小化的窗口,但在Win8.1上则只会打开一个黑色的窗口
  • 可以监听'minimize','restore','maximize','unmaximize','focus','blur'几个事件

3.4 全屏

可以简单的设置isFullScreen属性:

win.isFullScreen = true;

也可以调用方法并监听事件:

win.enterFullScreen();
win.leaveFullScreen();
win.toggleFullScreen();
win.on('enter-fullscreen', function(){...});
win.on('leave-fullscreen', function(){...});

3.5 Kiosk模式

一种特殊的全屏模式,也有人称之为展台模式,就是类似网吧或取号机等场合那种不能轻易退出的定制模式

  • 在Linux或Windows系统中,如果有键盘,还可以用Alt+F4,Ctrl+Alt+Del等组合键退出
  • 在Mac系统基本相当于完全锁定了
  • 对应的方法为 win.enterKioskMode(), win.leaveKioskMode(), win.toggleKioskMode()
  • 对应的事件仍是 'enter-fullscreen' 和 'leave-fullscreen'
  • 也可以在在manifest配置(http://docs.nwjs.io/en/latest/References/Manifest%20Format/#kiosk)中启用Kiosk模式

3.6 无边框窗口和可拖动区域

可以将窗口的边框禁用,从而更大程度的自定义窗口

//package.json
{
   "name": "My App",
   "main": "index.html",
   "window": {
       "frame": false
       /* "fullscreen": true */ //应避免同时这样设置,否则将造成屏幕边缘无法捕获鼠标
   }
}

同时,一旦设置为无边框,就无法拖动窗口了,除非自己设置一个可拖动区域

<div class="draggable">
   可拖动区域
   <a href="javascript:;">不被拖动干扰的链接</a>
</div>
<style>
.draggable {
   -webkit-app-region: drag;
   -webkit-user-select: none;
}
.draggable>a {
   -webkit-app-region: no-drag;
}
</style>

3.7 任务栏图标

当窗口失去焦点或最小化时,任务栏或Dock图标是吸引用户注意的重要途径,相关的API包括:

win.setShowInTaskbar(bool); //显示或隐藏图标
win.setBadgeLabel(label); //设置图标相关的文本标签,在Linux下一般无效
win.setProgressBar(num); //0到1//Mac上,参数为-1就跳一次,为1就一直跳直到用户点击
//Windows上,图标和窗口同时闪动参数指定的次数
//Linux上,在非激活状态下,非0的参数才会生效
win.requestAttention(number|bool); 

3.8 关闭窗口

前面用到过的 win.close([fouce]) 方法及相关的事件,可以用来在窗口关闭前方便的做收尾工作;需要注意的是这个过程也会减慢窗口的关闭,可以先隐藏窗口以提供比较好的用户体验:

win.on('close', function(type) {
   this.hide(); //先隐藏
   swtich (type) {
       case 'quit': //从菜单、任务栏图标或快捷键关闭
           break;
       case undefined: //点击窗口关闭按钮
           break;
   }
   //save data ...
   this.close(true); //
});
//调用close()并不会立即关闭窗口,直到回调内的close(true),这样就很好的提供了很好的关闭流程
win.close();

当有多个窗口时,closed事件也可以用来清理窗口的实例引用等

var otherWin = nw.Window.get().open('other.html');
otherWin.on('closed', function() {
  otherWin = null;
});

3.9 完整的Window文档

[文档地址](http://docs.nwjs.io/en/latest/References/Window/)

[IV]. Screen API

nw.Screen.Init(); //实例化Screen的单例对象,只需一次
var screens = nw.Screen.screens; //获得屏幕数组,保护一个或多个screen对象//每个屏幕对象包含这些信息:
/*
screen {
   id: int,   // 物理屏幕分辨率
   bounds: {
       x: int,
       y: int,
       width: int,
       height: int
   },   // 可用区域
   work_area: {
       x: int,
       y: int,
       width: int,
       height: int
   },   scaleFactor: float,
   isBuiltIn: bool,
   rotation: int,
   touchSupport: int
}
*///可以方便的利用这些信息改善用户体验
var currWin = nw.Window.get();
var myScreen = screens[0];
var x = myScreen.work_area.width - currWin.width;
var y = myScreen.work_area.y;
currWin.moveTo(x, y); //窗口贴到了右上角//当连接投影仪时,分辨率有可能发生改变
nw.Screen.on('displayBoundsChanged', function(newScreen) {
  //参数 newScreen 包含了新尺寸屏幕的所有信息
  x = newScreen.work_area.width - currWin.width;
  currWin.moveTo(x, y);
});

[完整的文档地址](http://docs.nwjs.io/en/latest/References/Screen/)

[V]. Menu API - 菜单栏和右键中的菜单

NW.js中,共有三种类型的菜单:

  • 上下文菜单:右键单击应用内的元素时
  • 窗口菜单:在Windows或Linux中,每个窗口上方都可以有自己的菜单栏;==在Mac中,同一应用的所有窗口在系统的任务栏中共享一套菜单==
  • 托盘菜单:在系统任务栏的右侧,一般都有托盘区域,点击其中图标出现的就是托盘菜单

4.1 上下文菜单

var menu = new nw.Menu(); //实例化一个菜单
menu.append(new nw.MenuItem({ //添加若干菜单项
 label: 'Item A',
 icon: 'xxx.png',
 tooltip: 'hello world!',
 enabled: true,
 key: 'A', //快捷键的主键,仅在菜单打开时有效;如果同时为document监听了keyup,会同时生效
 modifiers: 'ctrl-shift', //快捷键的修饰键
 click: function() { //点击事件回调,也可以用 menuitem.on('click', callback) 的方式
   alert('点击了 "Item A"');
 }
}));
menu.append(new nw.MenuItem({
   label: 'Item B',
   checked: false,
   type: 'checkbox' //类型2:点击后菜单项前面有对勾效果
}));
menu.append(new nw.MenuItem({
   type: 'separator' //类型3:分割线
}));
menu.append(new nw.MenuItem({
   label: 'Item C',
   type: 'normal', //类型1: 普通
   iconIsTemplate: true, //仅对Mac生效,系统自动根据 dark/light 等状态改变其样式
   submenu: new nw.Menu(...) //子菜单
}));
document.querySelector('#area').addEventListener('contextmenu', function(ev) {
 ev.preventDefault();
 menu.popup(ev.x, ev.y); //右键时弹出菜单
 return false;
}, false);

menu实例中一些其他的方法:

  • menu.items: 一个由 MenuItem 组成的数组,包含了其持有的所有菜单项
  • menu.insert(item, i)
  • menu.remove(item)
  • menu.removeAt(i)

4.2 窗口菜单

窗口菜单的绝大多数用法和上下文菜单相同,几个不同点在于:

  1. 必须指定type为menubar
var winMenu = new nw.Menu({type: 'menubar'});
  1. 如果应用要部署到Mac系统,还需要激活系统内建菜单(应用、编辑和窗口)
var os = require('os');
if (os.platform() === 'darwin') {
    winMenu.createMacBuiltin("配置中的应用名称", {
        hideEdit: true,
        hideWindow: false
    });
}
  1. 所有 windowMenu 下的一级菜单都必须有子菜单,不能为空
var mitem1 = new nw.MenuItem({
    label: 'm1',
    submenu: new nw.Menu
});
mitem1.submenu.append(new nw.MenuItem({label: 'aaa1'}));
mitem1.submenu.append(new nw.MenuItem({label: 'bbb1'}));
winMenu.append(mitem1);

var mitem2 = new nw.MenuItem({
    label: 'm2',
    submenu: new nw.Menu
});
mitem2.submenu.append(new nw.MenuItem({label: 'aaa2'}));
mitem2.submenu.append(new nw.MenuItem({label: 'bbb2'}));
mitem2.submenu.append(new nw.MenuItem({
    label: '退出这个程序', 
    click: e=>nw.App.quit()
}));
winMenu.append(mitem2);
  1. 将菜单赋值给窗口的menu属性
var currWin = nw.Window.get();
currWin.menu = winMenu;

[完整的文档地址](http://docs.nwjs.io/en/latest/References/Menu/)

[V]. Tray API - 管理托盘状态图标

托盘区一般处在系统状态栏的右侧,一些长时间运行的应用或服务的图标被安置在此处,以免都挤在任务栏中过于拥挤。(这些图标在不同平台叫法不同,Mac中叫做 Status Item, 一些Linux中叫做 Status Icon, Windows 中叫做 System Tray Icon)

// 创建一个托盘图标
var tray = new nw.Tray({ title: 'Tray', icon: 'img/icon.png' });// 添加菜单
var menu = new nw.Menu();
menu.append(new nw.MenuItem({ type: 'checkbox', label: 'box1' }));
tray.menu = menu;// 移除图标
tray.remove();
tray = null;
  • 把实例放在全局作用域,以防被gc后图标消失
  • Mac中的托盘图标没有右键点击的行为,如果在menu上绑定了click行为,将被默认的显示菜单行为覆盖
  • Mac中的高分屏,可判断 window.devicePixelRatio>1后动态指定,或将2x图标和原始图标文件打包,[参考这里](http://www.cnblogs.com/lovelylife/p/6226314.html)

[完整的文档地址](http://docs.nwjs.io/en/latest/References/Tray/)

[VI]. 文件对话框 - 打开或保存文件

在浏览器里,文件对话框可以上传下载文件。在NW.js里,同样的操作只是传递文件路径字符串而已,而非拷贝其内容;同时一些浏览器中的安全限制被解除,并赋予其一些增强的能力,从而使用户体验更接近原生应用

<input type="file" />
<a href="javascript:;" id="file-trigger">打开文件对话框</a><script>
var fipt = document.querySelector('[type=file]');
fipt.addEventListener('change', function(e) {
   var path = this.value;
   alert(path);
   fipt.value = '';
});
document.getElementById('file-trigger').addEventListener('click', function() {
   fipt.click(); //如果要自定义样式或自动触发,也可以直接调用
});
</script>

6.1 各种文件对话框

  • 允许一次选择多个文件 <input type="file" multiple />
  • 过滤文件类型 <input type="file" accept=".doc,.xml,application/msword" />
  • 选择一个目录 <input type="file" nwdirectory />
  • 保存文件 <input type="file" nwsaveas[=默认路径] />
  • 默认路径,必须写成目标平台的格式 <input type="file" nwworkingdir="C:\Documents" />

6.2 用拖动打开文件

<div id="drag_file_area" style="width: 300px;height: 300px;background-color: gray;">拖动文件到这里</div><script>
window.ondragover = window.ondrop = function(e) {
   e.preventDefault()
};
var dragfile = document.getElementById('drag_file_area');
dragfile.ondragenter = function() {this.style.opacity = .5}
dragfile.ondragleave = function() {this.style.opacity = 1}
dragfile.ondrop = function(e) {
   e.preventDefault();
   this.style.opacity = 1;
   let {files} = e.dataTransfer;
   let paths = [].map.call(files, file=>file.path);
   alert(paths);
};
</script>

[VII]. Clipboard API - 访问系统剪贴板

// 获取单例
var clipboard = nw.Clipboard.get();// 从剪贴板读取
var text = clipboard.get('text');
console.log(text);// 写入剪贴板
clipboard.set('I love NW.js :)', 'text');// 清空
clipboard.clear();

[完整的文档地址](http://docs.nwjs.io/en/latest/References/Clipboard/)

[VIII]. Shell API - 调用系统默认应用

  • Shell.openExternal(URI): 用系统默认的浏览器或邮件程序打开URI
  • Shell.openItem(file_path): 用系统默认的关联程序打开一个文件,如果没有指定,则打开"Open with"对话框
  • Shell.showItemInFolder(path): 用资源管理器或finder打开指定的目录

[完整的文档地址](http://docs.nwjs.io/en/latest/References/Shell/)

* 原创文章转载请注明出处

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-02-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云前端 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • [I] 概述 - NW.js原生界面(Native UI)APIs
    • 1.1 获取nw实例
      • 1.2 一些最佳实践
        • 1.3 几个主要的API
        • [II]. App API - 应用的核心
          • 2.1 打开关联类型的文件
            • 2.2 访问application data目录路径
              • 2.3 访问manifest文件
                • 2.4 关闭应用
                  • App.closeAllWindows()
                  • App.quit()
                • 2.5 注册系统层级的快捷键
                  • 2.6 完整的App文档
                  • [III]. Window API - 操作NW.js窗口
                    • 3.1 实例化
                      • 优化窗口显示时机
                      • 原始的window对象
                    • 3.2 位置和尺寸
                      • 3.3 改变窗口状态
                        • 3.4 全屏
                          • 3.5 Kiosk模式
                            • 3.6 无边框窗口和可拖动区域
                              • 3.7 任务栏图标
                                • 3.8 关闭窗口
                                  • 3.9 完整的Window文档
                                  • [IV]. Screen API
                                  • [V]. Menu API - 菜单栏和右键中的菜单
                                    • 4.1 上下文菜单
                                      • 4.2 窗口菜单
                                      • [V]. Tray API - 管理托盘状态图标
                                      • [VI]. 文件对话框 - 打开或保存文件
                                        • 6.1 各种文件对话框
                                          • 6.2 用拖动打开文件
                                          • [VII]. Clipboard API - 访问系统剪贴板
                                          • [VIII]. Shell API - 调用系统默认应用
                                          领券
                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档