我们平常对java程序进行问题排查、性能调优时,如果没有合适的工具,很多时候会事倍功半,甚至无法继续进行下去。其实,jdk自身已经提供了很多强大的工具供我们使用。本文就对这些工具做一个概览性的描述。
笔者的开发环境是:OS X EI Captian 10.11.6
JDK版本:
JAVA_HOME/bin下的工具截图如下:
昨天发布《JavaFX学习入门篇,想用Java写窗口应用的同学可以看看》的基础上,今天继续介绍JavaFX控件绑定及交互事件处理。昨天的文章中已经对新建立的demo.fxml指定了控制器类DemoController,这个控制器类就是今天学习中要用到的主类。在JavaFX中通过注解@FXML来绑定fxml文件中的控件或控件的交互事件。前面已经在场景中添加了菜单组件,菜单组件中默认已经添加了三个菜单,且都有一个子菜单,目前点击任何一个菜单都不会有响应。
下面介绍如何响应菜单的点击事件,首先在Netbeans中双击demo.fxml,然后在JavaFX Scene Builder中找到场景中的菜单组件。在左侧Document节点下Hierarchy中展开菜单,点击选中要添加事件监听的MenuItem,然后在右侧Inspector中展开Code节点,在On Action中输入该菜单点击时回调函数的名称,这里函数名称为closeAction,输入完成按回车并Ctrl+S保存修改。具体操作如下图所示:
为MenuItem设置Action回调函数
完成以上操作之后回到Netbeans中,在DemoController中添加刚才的函数,注意需要添加@FXML注解,函数参数可以为空也可以是javafx.event.ActionEvent,参数可根据需要添加,但不是什么参数都可以。本例中函数如下:
@FXML
private void closeAction(ActionEvent event){//函数名必需与fxml中的一致
System.out.println("invoke close menu action");
}
到此菜单事件的绑定已经完成了,同样的道理其他任何交互控件的事件绑定都类似。每个控件可监听的事件都不太一样,例如输入框可以监听鼠标事件、键盘事件、触屏事件等等,交互事件监听都可以参考此操作。
接下来继续介绍控件的绑定,我们开发一个桌面应用表单元素是必不可少的。我们需要知道用户输入了什么内容,或者选择了哪个值。这些就需要用到控件的绑定,控件绑定操作也很简单,细心的读者或许已经发现前面的截图中On Action之前有个fx:id,没错这里就是通过这个id进行控件绑定。我们将一个输入框(TextField)拖放到场景中,并指定fx:id为input,为了触发显示我们输入的值,再监听这个输入框的键盘按键释放事件On Key Released,函数名为keyRelease,具体设置如下图:
添加输入框并设置ID和key release事件监听
编辑完场景之后保存,回到Netbeans中在DemoController中添加一个TextField变量及一个方法keyRelease,注意变量及方法都需要添加@FXML注解,此处keyRelease的参数可以为空或javafx.scene.input.KeyEvent,其他参数会抛出IllegalArgumentException: argument type mismatch异常。添加代码如下:
@FXML
private TextField input;//变量名必需与fxml文件中的fx:id值一致
@FXML
private void keyRelease(KeyEvent event){//函数名必需与fxml中的一致
System.out.println(input.getText());
}
到些控件的绑定也介绍完了,此时可以将项目运行起来看看效果。好奇的小伙伴或许对场景中的输入框文字“这是输入框”存在疑问,是默认值吗?这个是JavaFX的Prompt Text等同于HTML中的placeholder,即输入提示文字,当该控件得到光标时提示文字会消失。具体设置在右侧Inspector中的Properties页面中,本例源码已上传到作者github:https://github.com/ajtdnyy/JavaFXDemo。Prompt Text如下图所示:
设置输入框的Prompt Text
ebView2 是越来越香了。
WebView2 不但是 Win11 自带的系统组件,Win10 也已经自动推送安装。即使是少量没有安装 WebView2 的系统 —— 使用 aardio 中的 web.view 也会自动安装( 不需要写任何代码 )。
我用 WebView2 开发了很多项目,不得不说 WebView2 稳定可靠、性能强悍,接口简洁,是真的让人省心。
这里介绍一个适合用于 WebView2 的极简前端组件 htmx.js ,这个组件最大的特色就是简单,一学就会,也很容易理解。
我们正常浏览一个网页的过程是在浏览器里输入网址,向 HTTP 服务器发送请求。然后服务器返回 HTML 代码,浏览器显示页面。
但是 htmx.js 脑洞大开,让网页上的每一个 HTML 节点都可以向服务器发送请求并获取 HTML,并实时更新页面上指定的节点。而且不需要写任何 JavaScript 代码。
首先我们打开 aardio ,创建 WebView2 工程并选择 htmx.js 模板:
生成的工程如下:
点『运行』可直接测试效果,点『发布』可生成独立 EXE 文件 。
在工程管理器中右键点『网页』弹出菜单,然后点『用外部编辑器打开』,如果安装了 VS Code 会使用 VS Code 打开网页目录。
在 VS Code 中点击并打开 index.html 源码:
打开 index.html ,先看最简单的 htmx.js 示例:
<button hx-get="/api/index.aardio"
hx-swap="innerHTML"
hx-trigger="click"
hx-target="#info-div" >
点这里发送 GET 请求
</button><br>
<div id="info-div"></div>
注意看凡是 "hx-" 前缀的属性都是用于 htmx.js 。
hx-trigger 用于指定在什么事件发生时触发 HTTP 请求,例如:
hx-trigger="click"
表示在 click 单击事件发生时触发请求。
hx-trigger 可使用标准网页事件名,常用事件如下:
事件名后面还可以添加修饰器,例如修饰器 once 表示只允许触发一次 :
hx-trigger="click once"
其他事件修饰器:
下面的 HTML 使用了多个事件修饰器:
<input type="text"
hx-trigger="keyup changed delay:500ms"
hx-post="/api/index.aardio" >
这表示在按键放开( keyup ),文本框的内容发生改变( changed )时触发,并且延时 500 毫秒再发送请求。
hx-get 则指定要请求的是哪个后端页面,例如:
hx-get="/api/index.aardio"
表示事件触发时,请求 "/api/index.aardio" 这个页面。因为 aardio 在启动 SPA 应用时自动指定了后端根目录为 "/web",所以实际请求的是 "/web/api/aardio" 。
而 hx-swap 则指定要将返回的 HTML 写入到哪里,"innerHTML" 指定是更新网页节点内部 HTML,"outerHTML" 指定替换目标网页节点的全部 HTML ,其他还有 "afterbegin" , "beforebegin" , "beforeend" , "afterend" , "none" 。这些看名字就知道是什么作用,就不解释了。
hx-target 属性用 CSS 选择器指定要写入的网页节点,例如:
hx-target="#info-div"
指定服务器返回的 HTML 写入 id 为 "info-div" 的节点。如果省略 hx-target 属性表示写入目标是当前节点自身。
hxmx.js 在更新 HTML 时,如果发现新旧 html 中有 id 相同的元素会进行优化并平滑显示。
看到这里,htmx.js 您已经会用了。
虽然 htmx.js 文档里有更多花式用法,但一般可能用不上。有些事搞太复杂了也不一定是好事。
aardio 提供了嵌入式 HTTP 服务器,可以直接使用 aardio 代码写网页,支持与 PHP 类似的模板语法。
aardio 的模板语法很简单,aardio 代码写在 <? ?> 内部,而 HTML 代码写在 <? ?> 外部就可以了。实际上 <? ?>外部的代码被转换为了 aardio 中 print 函数的参数。
例如服务端有下面的 aardio 代码:
<span>abc</span>
<?
response.write("123")
?>
运行后会自动转换为纯 aardio 代码如下:
print("<span>abc</span>");
response.write("123");
在 HTTP 后端中,print 函数实际上就是指向用于向 HTTP 客户端输出数据的 response.write() 函数。
在 HTTP 后端有两个最常用的对象,request 对象包含了所有 HTTP 请求信息,而 response 对象为 HTTP 响应对象,用于向客户端发送数据。
打开 aardio 自带「工具 > 库函数文档」,点击 fastcgi.client 的文档可以查看 request, response 对象的所有属性与方法。aardio 中的所有 HTTP 服务端实现都统一兼容 fastcgi.client 文档规定的 request, response 用法。
也可以参考 aardio 开始页的 《 aardio 网站开发、FastCGI开发入门教程 》。至于 aardio 模板语法,请参考 《 aardio 语法与使用手册 > aardio 语言 > 模板语法 》
aardio 的模板语法不仅仅可以用于写 HTTP 后端,也不仅仅是可以用于输出 HTML,实际上可以用于生成任何字符串。aardio 中的很多功能都支持这种模板语法,例如运行时编译 C# 代码就支持用 aardio 模板语法生成 C# 代码。另外 aardio 提供 string.loadcode() 函数可以直接解析 aardio 模板并返回字符串。
这里要注意,上面范例工程默认导入的 HTTP 服务器是:
wsock.tcp.simpleHttpServer;
这是一个多线程的 HTTP 服务端,每次被请求执行的 aardio 代码都是在后台线程中运行。aardio 多线程开发要注意的是每个线程都运行在独立的环境,全局变量是相互隔离的,这个限制实际上让 aardio 的多线程开发更简洁,坑更少,具体请参考 aardio 自带「范例程序 > aardio 语言 > 多线程」。
如果改为 wsock.tcp.asynHttpServer 则是单线程异步的 HTTP 服务器。
下面我们仍然使用默认的 simpleHttpServer 。多线程的好处是耗时操作不会卡界面。后端在进行耗时操作时,网页前端通常需要显示一个动画,htmx.js 做这事就很简单。
我们只要简单的修改一下前面讲过的网页代码如下:
<button hx-get="/api/index.aardio"
hx-indicator="#indicator" >
点这里发送 GET 请求
</button><br>
<img id="indicator"
class="htmx-indicator"
src="/images/loading.gif"/>
主要是增加了 hx-indicator 属性,该属性的值用一个 CSS 选择器指定了发送 HTTP 请求时要显示的 HTML 元素,这里指定的是 id 为 "indicator" 的元素。
实际上我们可以自定义这个请求动画的样式,我们打开样式文件 index.css 添加下面的样式:
.htmx-indicator{
display:none;
}
.htmx-request.htmx-indicator{
display:inline;
}
在发送请求时,网页上被设定的指示元素会自动添加 CSS 类 "htmx-request",HTTP 请求结束会移除该类。
然后我们打开对应的 aardio 后端代码 /web/api/index.aardio ,输入以下代码:
<span>
<?
if( request.method == "GET"){
/*
这是多线程后端,
这里等 2 秒,网页会显示加载动画
*/
sleep(2000);
response.write( time() )
}
?></span>
上面的代码的作用是:如果收到 GET 请求,线程就休眠 2 秒以模拟耗时操作。然后输出当前时间。
我们运行一下看看效果:
htmx.js 提交请求的节点如果是一个表单控件,只要指定 name 属性 —— 就会自动以该名字发送请求参数,参数值就是控件的值。
如果提交请求的节点是表单,则 HTTP 请求参数为表单内所有控件的值。
也可以在节点的 hx-vals 属性中用一个 JSON 对象指定请求参数,例如网页这样写:
<button hx-get="/api/index.aardio"
hx-vals='{"myVal": "值"}'>
点这里发送 GET 请求
</button><br>
aardio 后端就可以使用:
request.get["myval"]
取到 HTTP 请求参数 myval 的值。
如果使用 POST 发送请求,例如:
<button hx-post="/api/index.aardio"
hx-vals='{"myVal": "值"}'>
点这里发送 GET 请求
</button><br>
那么 aardio 后端可以使用
request.post["myval"]
取到 HTTP 请求参数 myval 的值。
在 aardio 后端使用:
request.query("myval")
可以取到 GET 或 POST 发送的 myval 参数值。
hx-vals 还可以通过加上 javascript: 或者 js: 前缀后使用 JS 对象返回请求参数,例如:
<button hx-get="/api/index.aardio"
hx-vals='javascript:{myVal: "值"}' >
点这里发送 GET 请求
</button>
有趣的是 web.form 也可以支持 htmx.js 。
web.form 是基于系统自带的 IE 内核控件,注意系统虽然删除了 IE 浏览器,但 IE 控件属于系统组件,Windows 有说明该控件不会被移除。IE 控件的好处是从 XP 到 Win11 所有操作系统都自带。
而且 IE 控件很轻量,与本地程序交互的接口也非常方便。Win10 ,Win11 自带的是 IE11 内核,写写一般的网页还是很好用的。至于 Win7 —— 因为只有极低的份额,一般软件不用考虑。
htmx.js 最后一个支持 IE 11 的版本是 1.6.1 ,这个足够用了。前面说过我们需要的只是局部请求刷新的功能,其他功能我们用不上,所以追新无意义。
首先我们打开 aardio 工程向导,选择「 Web 界面 > Web Form 」然后创建工程即可,新版工程模板默认就是使用 htmx.js 。
其他 HTML 代码写法与前面介绍的 WebView2 基本一样。不过 web.form 本就支持 EXE 内嵌资源文件,所以默认并不会启动 HTTP 服务器,需要多写几句代码。
打开工程的 webPage.aardio 源码:
可以看到源码中是如下启动 HTTP 服务器的:
import web.form;
var wb = web.form(winform);
//多线程后端
import wsock.tcp.simpleHttpServer;
wsock.tcp.simpleHttpServer.documentBase = "\web"
var indexUrl = wsock.tcp.simpleHttpServer.startUrl("\index.html")
wb.go(indexUrl);
我并没有把这几句代码封装到 wb.go() 函数中。
有些新手总以为代码越少越好,其实并非如此,有时候多写几句更容易看清楚代码的思路,更容易理解我们正在使用的技术。
下面我们看下 web.form + htmx.js 范例的运行效果:
上面示例程序的主窗口是使用 win.ui.tabs 做的,只有其中一个标签页用到了网页。
其实一般桌面软件的界面并不是一定要全部使用网页实现。有时候我们将界面中适合用网页呈现的部分用网页做,可能会更好。
我们在使用任何技术时,都要考虑一下适不适合。没有一样技术能适合做所有的事,多个选择总是好事。
*请认真填写需求信息,我们会在24小时内与您取得联系。