从近期开发的Compose DeskTop项目中收获的四个知识点

3,679 阅读14分钟

Compose可以跨平台相信已经不是什么新鲜事情了,相信只要会点Compose的小伙伴都已经多多少少开始上手Compose的跨平台开发,在Compose能跨的平台里面,除了Android之外,“跨”的最完善的应该就是DeskTop桌面平台了,可就算是DeskTop平台,我们在开发过程中也不能完全将Android代码都复制过来使用,一些平台上的差异性决定了有些代码只能在DeskTop平台上运行,或者说在做DeskTop开发时候必须得接触到的一些特性,那么接下来你就会知道

  1. 如何给应用添加菜单项
  2. 如何选择文件
  3. 如何调用系统弹框
  4. 如何去掉应用窗口的边框

添加菜单项

基本每一个桌面应用都有若干个菜单选项,这些选项中的功能可能在应用当中也存在,但是放在菜单中能够更加方便,快捷的让用户找到并使用,通常我们新建一个DeskTop项目并运行它的时候,在电脑屏幕的菜单栏上只有一个Mainkt的选项,里面是一些系统提供的一些功能,比如最小化,退出应用之类的

image.png

这个时候比如我们想要添加一些属于我们应用的一些菜单选项,应该怎么做呢?答案就在Window.desktop.kt文件里面

image.png

有一个MenuBar函数,它是FrameWindowScope的扩展函数,在函数里面是直接生成了一个JMenuBar,这个类在swing包里面,功能可以理解为获取我们桌面顶部那个菜单栏实例,然后通过setContent方法,将content塞到菜单栏里面,而content就是MenuBar函数的入参,也就是我们需要设置进去的菜单组件,至于FrameWindowScope我们都很熟悉的,这个就是Window的作用域,所以我们除了在Window里面绘制我们的主页面之外,也可以直接使用MenuBar来定义菜单项的内容

image.png

接下来就是如何定义菜单项了,直接放个Text组件进去吗?当然不是,我们继续到MenuBarScope里面看下,里面有五个组件,分别是MenuSeparatorItemCheckboxItemRadioButtonItem,也就是在MenuBarScope里面可以使用这五个组件,除了Separator很好理解,是个分割线,其余四个我们看下如何使用

Menu

image.png

通过英文注释我们知道Menu组件的四个参数的作用,text就是菜单的文案,mnemonic是设置单个字符来触发菜单,enable表示这个菜单能否使用,content就是这个菜单点击后弹出来的下拉框内容,它也是个MenuScope,表示可以给菜单设置子菜单,现在我们来写个Menu看看效果

image.png

代码中给应用加了个名字就叫"菜单"的菜单,然后在菜单里面又建了一个子菜单,名字叫“菜单aa”,以此类推连续套了三个,一个简单的菜单里面打开子菜单的功能就做好了,效果如下

0530aa1.gif

所以Menu的功能就是一个可以打开子菜单的菜单项,那么真正的选项如何设置呢,我们用Item组件

Item

image.png

itemMenu的个别参数是一样的,不多做介绍,多出来三个不同的参数,分别是

  • icon:给Item设置图标
  • shortcut:快捷键,组合键
  • onClick:菜单选项的响应事件

我们先给上面的例子增加一个Item选项,并且点击的响应事件就是退出程序

image.png

很简单,同Menu一样,直接给Item设置一个文案就可以了,效果如下

0530aa2.gif

我们现在给选项添加一个icon,直接使用painterResource函数将图片路径传进去就好了,跟我们使用Image组件一样

image.png image.png

接下去我们给选项设置个快捷键,一般性一个菜单的选项都有快捷键,比如在AS里面运行一个程序的快捷键通常都是command+R,而在代码中,菜单选项的快捷键使用shortcut参数设置,它接收一个KeyShortcut对象,我们看下KeyShortcut的构造函数就知道如何使用它了

image.png

除了第一个key,其余的都是布尔值,分别表示是否可以通过ctrl键,command键,Alt键或者shift键来触发事件,而Key里面就是其他按键,我们进去看下

image.png

太多了只截取里一小部分,我们看到每一个变量相当于键盘上的一个按键比如Key.A就相当于监听键盘上A键的点击事件,现在我们给刚才的菜单上添加一个shortcut

image.png

这里给选项设置了一个shortcut,当点下command键和D键后,选项可以触发响应事件,我们看下效果

0531aa1.gif

我们在虚拟键盘上能够看到,当按下command+D的时候,我们就算没有将菜单打开点击选项,我们的应用也退出了

CheckboxItem

这个一看名字就知道是在菜单里面放一个复选组件,它的参数基本跟MenuItem差不多,唯一区别就是多了一个checked,这个表示当菜单打开时候这个复选框默认是不是勾选上的,它的响应事件onCheckedChange当复选框的状态发生变化的时候,就会回调一次

image.png

我们下面来做个例子来体验下,比如界面上有段文案,文案的内容跟随着复选框的变化而变化,代码如下

image.png

itemChecked是个布尔值,当CheckboxItem的状态改变时候,会回调一个布尔类型的状态值,我们在回调里面将状态值赋值给itemChecked,每一次赋值触发视图重组,来显示不同的文案,效果如下

0531aa2.gif

RadioButtonItem

这个组件同CheckboxItem在用法上相差不大,唯一的区别在于回调变成了监听点击事件,我们可以用RadioButtonItem来实现同上面一样的功能,代码如下

image.png

最终效果是一样的,这里就不展示了

选择文件

我们在做移动端开发的时候,经常会和手机设备里面的文件系统打交道,比如读取个文件,往文件里面写入点东西,但是当我们在做桌面开发的时候,又如何从电脑里面选择文件呢?答案是JFileChooser,下面一个简单的例子来看下如何使用

image.png

界面上有一个按钮,通过点击这个按钮我们生成一个JFileChooser的实例,我们所有选择文件相关的操作都在JFileChooser里面,比如调用showOpenDialog方法就是打开选择文件的弹框,这个方法会返回一个Int值

  • JFileChooser.CANCEL_OPTION:表示点击来取消按钮关闭了弹框
  • JFileChooser.APPROVE_OPTION:表示选中了某个文件点击了打开按钮,这个时候可以通过调用getSelectedFile方法获取选中的文件路径
  • JFileChooser.ERROR_OPTION:出现异常

最终将选中的路径显示在Text上,效果如下

0531aa3.gif

过滤文件类型

目前是所有文件都可以选,JFileChooser还可以设置选择的文件类型,使用setFilter方法,里面传入一个FileFilter类型的参数,我们这里使用它的一个子类FileNameExtensionFilter,它的构造方法里面有两个参数

image.png

第一个参数description是显示在文件格式后面的描述,第二个参数是一个String类型的可变参数,可以传入需要过滤的文件扩展名,比如“jpg”,"png"之类,我们在上面的例子中加入文件类型过滤的功能,代码如下

image.png

这里的意思是只能选择jpg,png或者gif类型的文件,我们看下效果

0531aa4.gif

打开指定目录

除此之外我们还发现一个细节,每次都要从用户目录开始选择,我们能不能快速定位到目标目录呢,可以的,我们可以先看下JFileChooser的构造方法

image.png

这里总共有四个构造方法,除了默认方法什么都不传之外,还可以传入一个String类型的参数,或者File,这个就表示打开指定目录,当这个指定目录为空的时候,默认是Windows上打开的是我的文档,而Mac上就是用户目录,而获取默认目录路径的方式就是通过下面这段代码

FileSystemView.getFileSystemView().defaultDirectory.absolutePath

现在比如我们想要直接打开DeskTop目录,那么我们代码应该这样写

image.png

直接在默认路径上追加想要打开的目录就好了,效果如下

0531aa5.gif

现在就可以直接打开想要的目录了,JFileChooser里面还有很多设置的方法,比如设置弹框标题的,按钮文案的,是否可以多选文件的,这些有兴趣的可以自己去尝试下

系统弹框

在Android开发中,如果想要弹一个系统弹框,我们都知道使用AlertDialog,而且现在随着Android版本的升级,手机里面的系统弹框的UI样式已经没有以前那样子难看了,但是在DeskTop开发里面,如果想要弹一个系统弹框该怎么做呢?虽然也有一个AlertDialog,但是这个是一个Composable组件,需要使用一个状态值来控制它是否插入到LayoutNode树里面去,下面是一个展示AlertDialog的例子

0531aa6.gif

我们看到在不做任何的样式调整下,这弹出来的东西已经不能算是个框了,如果想要让它颜值好看些,那么需要加上一定的代码量才行,那么有没有一个简单点的方式去弹一个框呢,毕竟有时候我们只想使用弹框去展示点提示信息之类的文案,那么我们就要去使用swing里面的另一个组件JOptionPane

提示类弹框

比如有一段提示语文案需要用弹框的形式展示出来,我们用JOptionPane可以这样做

0531aa8.gif

一个很桌面化的弹框就出来了,其中第四个参数messageType表示弹窗的类型,比如现在我们弹的是info级别的,我们可以改一下改成warning级别的试试看

0531aa9.gif

我们看到将第四个参数改成JOptionPane.WARNING_MESSAGE之后,弹框里的icon变了,变成一个带警告标志的icon,我们还可以将messageType改成JOptionPane.ERROR_MESSAGE,那么弹出来的框就变成异常级别的了

0531aa10.gif

当然如果觉得边上这个icon有点占地方,碍眼,那么就可以使用另一个messageTypeJOptionPane.PLAIN_MESSAGE来将这个icon去掉

0531aa11.gif

多个按钮的弹框

这种框也是我们比较常见的,通常有若干个按钮,需要用户去选择点击哪一个,然后执行相应逻辑,我们使用showConfirmDialog方法去展示这样的弹框,这个方法的参数与上面那个showMessageDialog几乎相同,唯一多了一个optionType,它是一个int值,不同的值表示展示的按钮数量也不同,一般可供选择的有这三个值YES_NO_OPTION, YES_NO_CANCEL_OPTION还有 OK_CANCEL_OPTION,至于点击了哪一个,我们通过showConfirmDialog的返回值来判断,点击了yes或者ok,返回0,点击no或者cancel返回1,如果有三个按钮,那么第三个按钮点击返回2,我们看下面的例子

0531aa12.gif

这个例子里面就是通过返回值result的不同来展示不同的按钮文案,比起Android里面的AlertDialog方便了不少,不用去给每个按钮添加点击事件

带有输入框的弹框

也有某些场景里面需要让弹框里面有个输入框,那么我们可以用showInputDialog方法,这个方法里面提供了如下几个参数

  • parentComponent:弹框的父容器
  • message:相当于是输入框的label
  • title:弹框的标题
  • messageType:弹框的类型
  • icon:设置边上的图标
  • selectionValues:选项列表
  • initialSelectionValue:初始值

其中如果同时设置了selectionValuesinitialSelectionValue,那么输入框里面只会展示selectionValues数组的第一个值,initialSelectionValue会无效,输入框里面的内容也会从showInputDialog方法返回,下面我们来简单展示一个带输入框的弹框吧

0531aa13.gif

我们看到输入框里面输入一段内容以后,点了确定,内容就展示在了按钮上,我们再给输入框加上一段初始值

0531aa14.gif

我们看到什么也不做,直接点确定以后,按钮上就显示了输入框里面的初始值,另外由于JOptionPane里面的代码都是用java写的,所以就算我们暂时用不到iconselectionValues,也必须显式的将这俩参数写出来,我们现在给输入框加上几个可选项,看看初始值是不是真的不显示出来了

0531aa15.gif

我们看到初始值的确是不出来了,取而代之的是输入框展示的是selectionValues数组的第一个值,点击确定之后,选择好的值就显示在了按钮上

去除Window的边框

正常情况下,一个桌面应用都会有个边框,在Mac上表现为左上角带有三个按钮,分别是退出应用,最小化以及最大化

image.png

但是如果突然有个需求,需要隐藏这个边框,让你去定制一排这样的按钮并实现同样的功能,那么该如何做呢?其实蛮容易的,我们看下面的例子

image.png

这是很简单的一段代码,仅仅只是展示一个带有标题的窗口,如果我们想要隐藏最上面的边框,我们可以用Window组件的其中一个属性transparent,这是一个布尔值,默认为false,我们只要将它显示为true就能将整个窗口透明化,然后在将主体内容设置个背景颜色,那么看起来的效果就像去掉边框一样了

image.png

不过要注意,上面这个代码如果运行起来是会报错的,会有下面这段日志出来

image.png

关键信息就是第一行的那段话“Transparent window should be undecorated!”,咋弄呢?也容易,Window组件里面还有一个属性undecorated,默认也会false,把它设置为true就可以了,所以如果想要将一个窗口设置为透明,必须同时将transparentundecorated两个属性都设置为true才可以,我们加上这个属性在看下效果

image.png image.png

我们现在就得到了一个无边框的背景为白色的页面,那么接下来我们就在页面里面加上三个按钮来分别实现退出应用,最小化以及最大化的功能吧,首先添加上三个按钮

image.png image.png

最简单的就是退出应用,因为我们在WindowonCloseRequest属性上就已经实现了,调用exitApplication函数就可以实现退出应用的功能

image.png 0601aa3.gif

至于最小化以及全屏显示的功能,我们需要用到WindowState这个类,使用rememberWindowState()函数去创建它,并将它赋值给Window组件的state属性

image.png

其中最小化对应着WindowState里面的属性isMinimized,我们将它设置为true就能实现最小化功能了,代码如下

image.png 0601aa4.gif

剩下的最大化就需要用到WindowStateplacement属性,这个属性需要给它设置一个WindowPlacement枚举对象,它一共有三个值,分别为

image.png

这些值都表示着窗口以哪种形式展示在屏幕上,其中Maximized就表示窗口最大化,Floating表示以最初的形式展示在屏幕上,所以我们可以在最大化的按钮上实现点击一次最大化,再点击一次恢复至原来的形态

image.png 0601aa7.gif

总结

写这些东西主要是最近刚好在写一个DeskTop的小项目,刚好要用到这些东西,就顺便总结一下,个人觉得对于一个经常开发移动端的开发者来讲,如果要开发一个完整的DeskTop项目的话,除了掌握一些必要的Compose知识点之外,也一定要具备一些Window组件的特性,还有一些系统api以及swing的知识点,还是很有帮助的