sun 将要在今年的晚些时候发布最新的Java平台(开发代号Mustang)作为正式的Java平台Standard Edition 这个版本关注了几个重要的主题例如兼容性和稳定性有关完整的主题列表参阅Java Specification Request JSE 的版本目录
Mustang预期拥有的新特性包括(除了别的以外) 一个编译器API 控制台输入/输出(I/O) 一个启动画面API 众多的Java D性能改进 XML数字签名 一个系统托盘API javaioFile类的分区空间方法 Java数据库连接(JDBC) 公共注释(Common annotations) 脚本支持(Scripting) 一个用于XML的流(streaming)API 排序过滤和加亮javaxswingJTable内容的能力 Javadoc标记的更新 可编程操作网络属性(例如广播地址和子网掩码) 方便地打印javaxswingtextJTextComponent的内容的能力
Mustang拥有远远超出一篇文章探讨范围的新特性因此本文只关注新特性的一小部份确切地说本文将讨论用于控制台输入/输出和分区空间的方法用于启动画面以及与系统托盘交互的API
警告由于Mustang目前没有最终发布一些特性还可能会被改变或者去掉所以当Sun公司最终发布Mustang的时候本文中的一些代码可能会需要改动或者变得完全不相干了
注意我使用Sun公司的Java SDK版本rc (build )创建和测试了本文的代码底层平台是Microsoft Windows ME
控制台输入/输出
在年月Sun收到了一个改进控制台输入/输出的增强请求(RFE)申请人特别要求一种可以提示用户输入密码并且允许用户输入密码(任意长度)而不会在控制台显示出密码字符的方法申请人指出抽象窗口工具包(AWT)的setEchoChar()方法并不合用因为它依赖于GUI的可用性然而很多基于服务器的操作系统根本不使用GUI
在年后期Sun回应了RFE #为Mustang (build )添加了javaioConsole类这个类所提供的方法可以访问与当前虚拟机相关联的基于字符的控制台设备但是在调用这些方法之前需要首先调用System的public static Console console()方法来获取一个Console对象该方法将返回一个用来与控制台设备交互的Console对象但是如果控制台设备不存在就会返回null例如当你重定向标准输入或标准输出(或二者皆有)的时候在调用nsole()来返回Console对象之后下面的一段代码将检查返回的Console实例是否为null来测定控制台设备是否存在
Console console = nsole ()if (console == null){ Systemerrprintln (Console not available) return}
假设控制台设备是存在的你可以从控制台输入流读取密码和整行的字符还可以向控制台输出流写入字符为了读取密码(而不会将密码字符显示到控制台输出流)你必须调用Console的两个readPassword()方法之一这两个方法不允许换行符作为密码的一部分如果达到了控制台输入流的字符数目限制他们将返回null
举例来说你可以调用public char [] readPassword(String fmt Object…… args)来提示用户输入密码其中提示是由javautilFormatter类型格式化字符串(fmt)和它的变量表(args)来描述的然后从一个字符数组中返回用户选择的密码下面的代码段反复调用readPassword(String fmt Object…… args)来提示用户输入密码直到用户输入了一个字符长度不小于MIN_PWD_LEN的密码为止
char [] pwddo{ pwd = consolereadPassword (Enter password (%d characters min) MIN_PWD_LEN)}while (pwdlength < MIN_PWD_LEN)
在密码被存储到pwd之后就可以按需使用了然而出于安全性考虑在不需要使用密码的时候应该将pwd清零
除了readPassword()方法之外Console提供了两个readLine()方法以便于从控制台读取一整行的字符并且将这些字符(不包括换行符)存储在一个String中如果达到了控制台输入流的字符数目限制这两个方法都将返回null举例来说你可以在不提示用户的情况下调用public String readLine()来返回一行字符下列代码段演示了这个方法
关于两个readPassword()方法和两个readLine()方法有一些有趣的事当这些方法遇到输入/输出错误时它们不抛出javaioIOException对象(例如会被Systeminread()抛出)而是抛出一个javaioIOError对象由于IOError是Error的子类你无需像捕捉IOException那样来捕捉这个对象
向控制台输出流输出字符的配套方法是public Console format(String fmt Object…… args)和作用相同的public Console printf(String fmt Object…… args)方法后者内部调用了format()下列代码段演示了printf()
consoleprintf (%s input)
输出字符之后format()——和printf()(按扩展名)——自动转储清除控制台输出流
注意控制台对象与唯一的一个javaioReader对象和唯一的一个javaioPrintWriter对象相关联调用控制台的public Reader reader()方法可以返回Reader例如你可以把Reader传递给javautilScanner的一个构造函数来对控制台输入流进行复杂的解析调用控制台的public PrintWriter writer()方法可以获取PrintWriter然后你可以调用各种各样有用的方法来输出不同类型的数据到控制台上为方便起见控制台提供了一个public void flush()方法来调用PrinterWriter的flush()方法
让我们用关于控制台输入/输出的知识来做一个实际应用——数据库访问最终我建立了一个Microsoft Access salesmdb销售数据库它随着本文的代码一同发布(可以在资源下载)该数据库包含一个单独的territories表它有两列Name代表售货员的名字Territory代表售货员可以合法地出售产品的区域我将下面的数据输入到了数据库中并且为它设置了密码保护——密码是mustang
| —— | | Name | Territory | | —— | | John Doe | North | | Jane Doe | South | | Paul Smith | East | | Kathy Smith | West | | —— |
销售数据库由一个Sales应用软件来访问在访问数据库之前程序请求用户输入一个用户名和密码然后它使用这些数据通过JDBCODBC bridge驱动来连接sales数据源(你可以用Windows控制面板的ODBC数据源小程序来创建它用于识别salesmdb的位置)如果连接成功程序将输出territories表的全部行和列的值列表显示了Sales程序的源代码
Listing Salesjava
// Salesjavaimport javaio*import javasql*import javautil*class Sales{ public static void main (String [] args) throws ClassNotFoundException SQLException { // Attempt to obtain a console // 尝试获取控制台 Console console = nsole () if (console == null) { Systemerrprintln (sales unable to obtain console) return } // Obtain username // 获取用户名 String username = consolereadLine (Enter username ) // Obtain password // 获取密码 String password = new String (consolereadPassword (Enter password )) // Create a Vector datastructure for holding table records // 建立一个向量数据结构来存储表记录 Vector<Object []> v = new Vector<Object []> () Connection con = null Statement stmt = null ResultSet rs try { // Attempt to connect to the sales datasource // 尝试连接sales数据源 con = DriverManagergetConnection (jdbcodbcsales username password) // Garbage collect the password—not a good idea to keep passwords // hanging around // 对密码进行垃圾收集——始终留着密码可不是个好主意 password = null // Attempt to create a statement // 尝试建立一个statement stmt = concreateStatement () // Establish the maximum number of rows that can be returned // 设置允许返回的最大行数 stmtsetMaxRows () // Attempt to fetch all rows from the territories table // 尝试读取territories表的所有行 String query = SELECT * FROM territories rs = stmtexecuteQuery (query) // Get number of columns // 获取列数 int nCols = rsgetMetaData ()getColumnCount () // Read all rows of all columns into storage // 读取所有列的每一行到向量存储 int i = while (rsnext ()) { Object [] buffer = new Object [nCols] for (int j = j < nCols j++) buffer [j] = rsgetObject (j + ) // NOTE getObject requires a based column index // 注意getObject要求列索引号从开始 vadd (buffer) } // Extract rows from the array // 从数组中取出全部行 Object [] rows = vtoArray () for (i = i < rowslength i++) { // Extract columns from the row // 取出一行中的每列值 Object [] cols = (Object []) rows [i] // Print out the values from each column // 显示输出每一列的值 for (int j = j < colslength j++) consoleprintf (%s cols [j]) consoleprintf (\n) } consoleprintf (Value at Row Col = %s\n getValue (v )) } catch (SQLException e) { consoleprintf (sales %s\n egetMessage ()) } finally { if (stmt != null) try { stmtclose () } catch (SQLException e) {} if (con != null) try { conclose () } catch (SQLException e) {} } } static Object getValue (Vector v int row int col) { // The following conversion should really not be done in this method // because its inefficient Placing the conversion here is a matter of // convenience // 由于效率低下下列的转换其实本不该在这个方法中进行把转换放在这里只是为了方便 Object [] rows = vtoArray () Object [] cols = (Object []) rows [row] return cols [col] }}
编译Salesjava然后执行程序首先呈现在你面的是Enter username的提示你可以随意输入任何用户名但至少要输入点什么接下来你将被提示输入密码确定这里要使用mustang假如连接成功建立了你将会看到我在上面已经展示的表值
列表使用了Console的printf()方法来输出表的内容然而这种方法有一个不十分明显的问题如果territories表有很多行的数据在不滚动的情况下并不是所有的数据都适合控制台窗口的显示为了获取这些行通常需要重定向标准输出到一个文件但是如果你这样做的话nsole()将返回null你也就无法使用控制台输入/输出了因此在你的应用中使用Console的printf()和format()方法之前要考虑清楚不要使用它们向屏幕输出大量的数据否则你可能就会无法看到全部数据了
注意仔细地分析列表的代码它并没有通过ClassforName()来尝试加载JDBCODBC bridge驱动由于Mustang支持JDBC ——它提供了一种内在机制来使DriverManager定位和加载驱动类——你不再需要通过ClassforName()来显式地加载驱动类了
尽管Console解决了最初的RFE问题但是很多开发者都对这个类存在异议主要的批评包括 nsole()方法应该被命名为SystemgetConsole()然而一些不赞成的人争辩说get前缀只应用在bean上面而System并不是bean此外console()是一个静态方法这意味着IDE将不会认为这个方法是用来获取property的getter方法了 Console类应该提供不使用缓沖来读取单个字符的能力换句话说程序不应该等到用户按下回车之后才能读取用户的输入一个类似C语言的kbhit()函数通过返回一个Boolean值来判断是否有按键被按下的方法将可以回应这种批评如果返回true程序就可以调用另一个方法来返回下一个等待在BIOS按键缓沖中的按键了 Console应该提供打开/关闭控制台字符显示的能力两个类似C语言中getch()函数(获得字符而不在控制台显示并且只当没有字符等待在BIOS按键缓沖中时才有效)和getche()函数(获得字符同时在控制台显示并且只当没有字符等待在BIOS按键缓沖中时才有效)的方法将可以回应这种批评 Console应该提供清除屏幕的能力和其他可以在基于Unix的curses库中找到的特性 Console应该提供一个detach()方法来将应用程序从控制台分离出来并且送入后台
除了第一条之外对Sun来说在Mustang的正式版本中回应上述批评的一部份(如果不是全部的话)也许还不是太晚
分区空间方法在收到改进控制台输入/输出的RFE #的一个月之前Sun收到了另外一个RFE要求提供一种方法来得到可用的磁盘空间Sun回应了这个RFE #为File类加入了个分区空间方法 public long getFreeSpace()返回以抽象路径名命名的分区的未分配空间字节数 如果抽象路径名不是磁盘分区的名字返回值为空 public long getTotalSpace()返回以抽象路径名命名的分区的总磁盘空间 如果抽象路径名不是磁盘分区的名字返回值为空public long getUsableSpace()返回以抽象路径名命名的虚拟机可用的空间字节数 如果抽象路径名不是磁盘分区的名字返回值为空
对于Windows版本的Mustang这些方法是按照Microsoft的GetDiskFreeSpaceEx函数实现的让我们基于这个函数的文档重新定义一下这些方法 getFreeSpace() 将返回当前根目录的磁盘的全部空闲字节数若当前根目录是一个CDROM驱动器则返回 如果有不可写CD在一个CDRW驱动器中会返回非值 getTotalSpace()将返回与调用线程相关的用户可用的磁盘字节总数如果用户配额被开启这个值可能会小于磁盘的空闲字节总数getUsableSpace()将返回与调用线程相关的用户可用的磁盘全部空闲字节数如果用户配额被开启这个值可能会小于磁盘的空闲字节总数如果当前根目录是一个CDROM驱动器则返回如果有不可写CD在一个CDRW驱动器中会返回非值
注意很多的现代操作系统允许限制每个用户可用的最大磁盘空间这个最大空间叫做用户的配额为用户留出的磁盘空间区域叫做该用户的分区在这种情况下getTotalSpace()返回的是与调用线程相关的用户的配额如果用户没有配额限制(只有一个用户)该方法返回磁盘字节总数同样getUsableSpace()返回的是与调用线程相关的用户的配额限制内的可用字节数如果用户没有配额限制该方法则返回磁盘的全部空闲字节数最后基于GetDiskFreeSpaceEx()的文档getFreeSpace()磁盘的空闲字节数——而不考虑用户的配额
列表展示了一个SpaceChecker程序的源代码它将显示文件系统的每一个根目录的空闲空间以及分配给当前用户的可用空间和全部空间
Listing SpaceCheckerjava
列表 SpaceCheckerjava
// SpaceCheckerjavaimport javaioFilepublic class SpaceChecker{ public static void main (String [] args) { File [] roots = FilelistRoots () for (int i = i < rootslength i++) { Systemoutprintln (roots [i]) Systemoutprintln (Free space = + roots [i]getFreeSpace ()) Systemoutprintln (Usable space = + roots [i]getUsableSpace ()) Systemoutprintln (Total space = + roots [i]getTotalSpace ()) Systemoutprintln () } }}
在我的Windows ME平台上运行该程序时我观察到了下列信息A\ Free space = Usable space = Total space =
C\ Free space = Usable space = Total space =
M\ Free space = Usable space = Total space =
N\ Free space = Usable space = Total space =
由于磁盘配额没有被应用到Windows ME操作系统上所以C的空闲空间与可用空间是相同的
启动画面 API
启动画面是基于GUI的现代应用软件的一个重要部分启动画面不仅可以在漫长的软件启动过程中占据用户的注意力(也可能用来通报用户版本信息或者其他细节)还可以使用户确定软件正在启动中这在Java环境中是尤其重要的因为JVM需要一段时间来加载和启动图列举了一个启动画面的例子
图 使用启动画面来显示版权和其他重要信息
Mustang对启动画面的实现是使用一个能够显示GIF(包括动画GIF)PNG或者JPEG图像的无修饰窗口Java应用程序加载器创建一个启动画面窗口然后响应命令行参数或者JAR manifest入口从而在窗口中显示指定的图片 splash命令行参数创建一个启动画面窗口并且显示一个指定的图片例如java splashmylogogif MyApp将创建启动画面窗口并且在这个窗口中显示mylogogif 确定的图像(见图)然后加载和启动JVM最后使JVM加载MyAppclass并且执行该类的public static void main(String [] args)方法 由于你最有可能把重要的应用打包进一个jar文件 Mustang提供了一个SplashScreenImage manifest项目用来从jar文件的manifest中访问启动画面的图像例如SplashScreenImagemylogogif把mylogogif识别为在启动画面窗口中显示的图像
假设下列信息被放置在了一个manifest文件中
ManifestVersion MainClass MyAppSplashScreenImagemylogogif
将这个manifest文件mylogogif和全部相关的类文件都打包进myAppjarjava jar myAppjar会在加载和启动JVM及运行应用程序之前创建启动画面并且显示mylogogif
如果你调用java splashyourlogogif jar myAppjar将会发生什么呢?在这种情况下yourlogogif将会在启动画面窗口中出现因为命令行参数splash优先替代了manifest设置SplashScreenImage
假如你希望在图像上加入视觉效果来定制启动画面例如为一个有着漫长初始化过程的软件的启动画面图像加入一个动态的进度条来告知用户剩余的时间(参考Mustang新的启动画面功能一文中的SplashTest程序)Mustang的javaawtSplashScreen类提供了对启动画面定制的支持
在没有启动画面窗口的情况下一个SplashScreen对象不具有任何意义所以你不能从SplashScreen类实例化对象而启动画面窗口只当你指定了splash命令行选项或者SplashScreenImage manifest项目时才会存在当你指定了此命令行选项或者manifest项目并且窗口被创建出来作为其启动过程的一部分Mustang会创建一个SplashScreen对象调用SplashScreen的public static SplashScreen getSplashScreen()方法可以返回这个对象的一个实例切记如果没有启动画面窗口的话将会返回null在调用了SplashScreengetSplashScreen()来返回SplashScreen对象之后下力代码段首先通过检查返回的SplashScreen实例是否为null来测定启动画面窗口是否存在
SplashScreen ss = SplashScreengetSplashScreen ()if (ss != null){ // Customize the splash screen}
假设启动画面窗口是存在的你可以调用下列五种SplashScreen方法来获取一个图形环境以便于在缓沖区绘制图象得到启动画面图象获取图象的尺寸用其他图象替换现有图象或者是使用缓沖区中的内容来更新启动画面窗口
public Graphics getGraphics()以javaawtimageBufferedImage的形式创建一个覆盖图象它具有与启动画面图象相同的尺寸还有一个窗口类型设置为BufferedImageTYPE_INT_ARGB(给予图象一个alpha通道来设置透明度)并且返回一个实例到BufferedImage的图形环境这幅图象的所有像素都被设为黑色并且是完全透明的你可以调用图形环境的方法在BufferedImage中绘制受到绘图操作影响的每个像素都会被分配一个不透明的alpha值如果在启动画面窗口关闭之后调用这个方法它将会抛出IllegalStateException public URL getImageURL()以URL返回当前的启动画面图象如果在启动画面窗口关闭之后调用这个方法它将会抛出IllegalStateException public Dimension getSize()返回启动画面窗口的尺寸同时也就是显示的图象的尺寸如果在启动画面窗口关闭之后调用这个方法它将会抛出IllegalStateException public void setImageURL(URL imageURL) 将启动画面图象改为imageURL确定的图象当图象被加载窗口被更新之后该方法将返回启动画面窗口将调整为图象的同样大小同时在屏幕上居中如果imageURL为null该方法将抛出NullPointerException如果在启动画面窗口关闭之后调用这个方法它将会抛出IllegalStateException public void update()更新启动画面窗口使得覆盖图内容与当前的启动画面窗口的像素相合成由于采用了Sourceover合成覆盖图象的透明像素可以使启动画面图象的像素显示出来而覆盖图象的不透明像素则遮挡了它下面启动画面图象的像素如果没有调用getGraphics()来创建覆盖图象或者在启动画面图象关闭之后它才被调用它将抛出IllegalStateException
列表通过一个PhotoAlbum软件的轮廓演示了getSplashScreen()getGraphics()getSize()与update()这四个方法这段程序定制了启动画面在四周加上了一个红色边框并且在水平中心显示一条信息Registering plugins……
Listing PhotoAlbumjava列表 PhotoAlbumjava
// PhotoAlbumjavaimport javaawt*public class PhotoAlbum{ public static void main (String [] args) { SplashScreen ss = SplashScreengetSplashScreen () if (ss != null) { Graphics g = ssgetGraphics () gsetColor (Colorred) // Colorwhite在我这个版本的Mustang中是默认颜色 Dimension size = ssgetSize () // 每个边框宽度都是图象的宽和高中较小值的% int borderSize if (sizewidth < sizeheight) borderSize = (int) (sizewidth * ) else borderSize = (int) (sizeheight * ) for (int i = i < borderSize i++) gdrawRect (i i sizewidthi* sizeheighti*) // 计算字符串在当前字体时的宽和高 FontMetrics fm = ggetFontMetrics () int strWidth = fmstringWidth (Registering plugins……) int strHeight = fmgetHeight () // 在字符串没有超出启动画面窗口范围时 if (strWidth < sizewidth && *strHeight < sizeheight) { gsetColor (Colorblue) gdrawString (Registering plugins…… (sizewidthstrWidth)/ sizeheight*strHeight) } // 拷贝覆盖图象到启动画面窗口 ssupdate () try { Threadsleep () // 暂停秒钟以便查看图象 } catch (InterruptedException e) { } } }}
为演示PhotoAlbum程序随本文的代码一起我加入了一张图片palogojpg当你执行java splashpalogojpg PhotoAlbum之后将首先在屏幕的中间看见palogojpg的图象在JVM完成载入并且开始运行main()你会看见如图所示的合成图象(也是居中的)
图 标识图片改变了自身的边框颜色同时提示用户正在注册插件点击缩略图以观看全尺寸图象
当第一个AWT或者Swing窗口变为可见的时候启动画面窗口会自动关闭然而也许你想要在软件窗口出现之前就将其关闭或者用你自己的窗口来替换它SplashScreen类提供了以下个方法来帮助你达到这个目的 public void close()隐藏并关闭启动画面窗口释放分配给该窗口的全部资源如果在启动画面窗口关闭之后调用这个方法它将会抛出IllegalStateException public Rectangle getBounds()返回启动画面窗口的边界如果你调用setImageURL()创建了一个不同尺寸的新启动画面图象这些边界将会改变如果在启动画面窗口关闭之后调用这个方法它将会抛出IllegalStateException public boolean isVisible()如果启动画面窗口是可见的将返回一个Boolea值true在窗口关闭之后调用则返回false
假如你用自己的窗口进行了替换你可以调用getBounds()来赋予它跟启动画面窗口同样的初始坐标和尺寸此外当你自己的窗口变为可见的时候启动画面窗口将自动关闭
系统托盘API
系统托盘是桌面上一个专门的区域它显示当前的时间和常驻内存的桌面应用的图标并且被桌面上当前运行的所有应用共享表展示了Windows ME的系统托盘它位于Windows任务栏的右侧
表 系统托盘上的显示属性应用程序图标相关联的菜单和工具提示
用户只需要适当地轻点鼠标就随时都可以与这些应用进行交互例如当鼠标位于应用软件的图标上时双击鼠标左键通常就可以打开应用的主窗口(在Windows平台上)同样地把鼠标移到应用的图标上面并且单击右键通常就可以显示特定应用的弹出菜单
Mustang引入了类javaawtSystemTray和javaawtTrayIcon来与系统托盘进行交互SystemTray代表桌面的系统托盘TrayIcon代表可以加入到系统托盘的一个图标
同SplashScreen一样你不能创建SystemTray对象而是必须调用SystemTray的public static SystemTray getSystemTray()方法来返回一个代表系统托盘区域的SystemTray实例由于在底层平台不支持系统托盘时该方法会抛出UnsupportedOperationException所以你必须首先调用public static boolean isSupported()如果系统托盘能得到最低限度的支持(除了显示图标之外最低限度的支持包括右键点击图标时显示的弹出式菜单或者是左键双击图标时响应的弹出事件)这个方法就将返回true否则返回false下列代码段演示了获取SystemTray实例的正确方式
if (SystemTrayisSupported ()){ SystemTray tray = SystemTraygetSystemTray () // Do stuff with tray}
假设系统托盘可以被支持你可以调用下列个方法来实现各种各样的功能 public void add(TrayIcon trayIcon) 为SystemTray加入一个TrayIcon在这之后trayIcon所描述的图标将在系统托盘显示出来图标加入到系统托盘的顺序取决于平台的实现同样当应用程序退出或者系统托盘变为不可用的时候图标将被自动移除如果trayIcon为null该方法将抛出NullPointerException如果你试图多次添加同样的TrayIcon实例将抛出IllegalArgumentException如果系统托盘不可用将抛出AWTException public void addPropertyChangeListener(String propertyName PropertyChangeListener listener) 为trayIcons属性加入一个javabeansPropertyChangeListener到listener表trayIcons属性必须为propertyName的值当程序从系统托盘添加或者删除一个TrayIcon或者图标被自动移除时listener将被调用该方法不抛出任何异常即使propertyName或者listener为null public PropertyChangeListener [] getPropertyChangeListeners(String propertyName) 返回一个与指定属性相关联的PropertyChangeListener(对于当前程序)的数组目前只支持trayIcons并且必须以propertyName值来指定如果你传递了null或者任何其它值该方法将返回一个空数组 public TrayIcon [] getTrayIcons() 返回含有被应用程序加入到SystemTray 的全部TrayIcon的一个数组所返回的数组是真实数组的一个拷贝可以随意修改而不会反映到系统托盘的图标上如果没有TrayIcon被加入该方法将返回一个空数组 public Dimension getTrayIconSize() 以javaawtDimension对象的形式返回图标出现在系统托盘时将占用的水平和垂直尺寸其单位是像素在创建图标之前调用该方法来决定图标的首选尺寸这是TrayIcon的public Dimension getSize()方法的一种方便的实现 public void remove(TrayIcon trayIcon) 从SystemTray移除指定的TrayIcon图标将从系统托盘移除同时将通报所有的属性改变listener该方法不抛出任何异常即使trayIcon为null public void removePropertyChangeListener(String propertyName PropertyChangeListener listener) 移除propertyName指定属性的listenerpropertyName必须是trayIcon(除此之外就没有任何地方需要调用这个方法了)该方法不抛出任何异常即使propertyName或者listener为null
表是一个用来演示上述方法的SystemTrayDemo程序这个程序先创建了一个内部为实心红色矩形的实心黄色圆形图标将这个图标添加到系统托盘暂停秒钟之后从系统托盘将其移除再暂停秒钟然后结束
表 SystemTrayDemojava
// SystemTrayDemojavaimport javaawt*import javaawtimage*import javabeans*public class SystemTrayDemo{ public static void main (String [] args) { if (SystemTrayisSupported ()) { // 获取系统托盘 SystemTray tray = SystemTraygetSystemTray () // 注册一个属性变化listener来通知系统托盘上图标的添加和移除 PropertyChangeListener pcl pcl = new PropertyChangeListener () { public void propertyChange (PropertyChangeEvent pce) { Systemoutprintln (Property changed = + pcegetPropertyName ()) Systemoutprintln () TrayIcon [] tia = (TrayIcon []) pcegetOldValue () if (tia != null) { Systemoutprintln (Old tray icon array contents ) for (int i = i < tialength i++) Systemoutprintln (tia [i]) Systemoutprintln () } tia = (TrayIcon []) pcegetNewValue () if (tia != null) { Systemoutprintln (New tray icon array contents ) for (int i = i < tialength i++) Systemoutprintln (tia [i]) Systemoutprintln () } } } trayaddPropertyChangeListener (trayIcons pcl) // 为图标创建一个图象 Dimension size = traygetTrayIconSize () BufferedImage bi = new BufferedImage (sizewidth sizeheight BufferedImageTYPE_INT_RGB) Graphics g = bigetGraphics () gsetColor (Colorred) gfillRect ( sizewidth sizeheight) gsetColor (Coloryellow) int ovalSize = (sizewidth < sizeheight) ? sizewidth sizeheight ovalSize /= gfillOval (sizewidth/ sizeheight/ ovalSize ovalSize) // 由该图像创建一个图标并且将其添加到系统托盘如果不能添加则结束程序 TrayIcon icon = null try { trayadd (icon = new TrayIcon (bi)) } catch (AWTException e) { Systemoutprintln (egetMessage ()) return } // 暂停以便查看系统托盘 try { Threadsleep () } catch (InterruptedException e) { } // 从系统托盘移除图标 trayremove (icon) // 暂停以查看没有了图标的系统托盘 try { Threadsleep () } catch (InterruptedException e) { } // 结束程序 Systemexit () } }}
在获取了系统托盘实例之后为这个实例注册一个属性改变listener并且为图标创建一个javaawtImage表基于这个图标创建了一个TrayIcon对象然后将该对象加入到系统托盘这个对象是通过调用TrayIcons public TrayIcon(Image image)构造器创建的这个构造器在TrayIcon对象中存储了image然后当trayadd (icon = new TrayIcon (bi))将TrayIcon加入到SystemTray的时候这个image被取出并且显示在系统托盘上如果image为null该构造器和TrayIcon的另外两个构造器都抛出IllegalArgumentException如果当前平台不支持系统托盘这些构造器将抛出UnsupportedOperationException
如果你在命令行运行SystemTrayDemo你将会在系统托盘发现发现一个新图标在控制台窗口将会获得类似如下的输出Property changed = trayIcons
Old tray icon array contents
New tray icon array contentsjavaawtTrayIcon@fc
Property changed = trayIcons
Old tray icon array contentsjavaawtTrayIcon@fc
几分钟之后图标就会消失SystemTrayDemo程序结束这对于持续运行的应用来说是不合适的与此相反你也许希望当用户右键点击程序图标以弹出菜单并且选择了适当的项目之后程序才会结束你可以用如下方式实现这个行为创建一个javaawtPopupMenu的实例创立一个带有动作 listener的菜单项目使用户选择该项即可结束程序然后将该菜单项加入到弹出菜单中最后调用public TrayIcon(Image image String tooltip PopupMenu popup)构造器使弹出菜单和图标相关联与图标的Image和相关联的PopupMenu一起你可以指定一个String来确定工具提示的文本内容当鼠标指针移动到图标上面时该文本就会出现把null传递给tooltip就可以使它不再显示下列代码段向系统托盘添加了一个带有工具提示和弹出菜单的图标
PopupMenu popup = new PopupMenu ()MenuItem miExit = new MenuItem (Exit)ActionListener alal = new ActionListener (){ public void actionPerformed (ActionEvent e) { Systemoutprintln (Goodbye) Systemexit () }}miExitaddActionListener (al)popupadd (miExit)TrayIcon ti = new TrayIcon (bi System Tray Demo # popup)trayadd (ti) // Assume that tray was previously created // 假定托盘在之前已经被创建了
当用户右键单击鼠标时弹出菜单就会出现然后用户可以选择菜单的Exit项目来执行菜单项的动作listener它就可以终止程序
除了鼠标右键动作之外你也许还希望当用户在图标上使用其它的鼠标动作例如双击为了使程序响应这些鼠标动作TrayIcon提供了下列注册listener的方法 public void addActionListener(ActionListener listener)为TrayIcon添加一个动作listener当用户双击图标时这个listener的public void actionPerformed(ActionEvent e)方法将被调用
你也许想要在多个TrayIcon之间共享一个动作listener那么决定哪个图标响应动作事件并且调用listener就变得很重要了你可以通过TrayIcon 的public void setActionCommand(String command)方法来为其分配一个唯一的指令然后你就可以在listener中调用javaawteventActionEvent的public String getActionCommand()方法来返回指令名称以及识别TrayIcon为方便起见TrayIcon也指定了一个public String getActionCommand()方法
调用TrayIcon的public void removeActionListener(ActionListener listener)方法从TrayIcon上移除listener你也可以通过调用public ActionListener [] getActionListeners()方法来获取一个含有全部注册的动作listener的数组 public void addMouseListener(MouseListener listener) 为TrayIcon添加一个鼠标listener当鼠标指针位于图标上方并且用户按下释放或者点击鼠标左键时这个listener的各种方法(除了public void mouseEntered(MouseEvent e)和public void mouseExited(MouseEvent e)不被支持)将会被调用
如果你调用javaawteventMouseEvent从父类继承的public Component getComponent()方法你将得到一个null值然而MouseEvent从父类继承的public Object getSource()方法将返回与事件相关联的TrayIcon调用MouseEvent的public int getX()和public int getY()方法可以得到鼠标坐标这些坐标是相对于屏幕的——而不是TrayIcon调用TrayIcons public void removeMouseListener(MouseListener listener)方法可以从TrayIcon中移除listener通过调用public MouseListener [] getMouseListeners()可以获取含有所有注册的鼠标listener的一个数组 public void addMouseMotionListener(MouseMotionListener listener) 为TrayIcon添加一个鼠标运动listener当用户在图标上移动鼠标指针时这个listener的public void mouseMoved(MouseEvent e)方法将被调用(public void mouseDragged(MouseEvent e)方法不支持)
再一次getComponent()返回nullgetSource()返回一个与事件相关联的TrayIcongetX()和getY()返回的坐标是相对于屏幕的——而不是TrayIcon
调用TrayIcon的public void removeMouseMotionListener(MouseMotionListener listener)方法来从TrayIcon移除listener你可以通过调用public MouseMotionListener [] getMouseMotionListeners()来得到一个含有全部注册的鼠标动作listener的数组
表展示了一个拥有自己的String工具提示和PopupMenu的程序SystemTrayDemo该程序也调用了之前介绍的listener注册方法来注册用于响应动作鼠标和鼠标运动事件的listener
表 SystemTrayDemojava
// SystemTrayDemojavaimport javaawt*import javaawtevent*import javaawtimage*import javabeans*public class SystemTrayDemo{ public static void main (String [] args) { if (SystemTrayisSupported ()) { // 获取系统托盘 SystemTray tray = SystemTraygetSystemTray () // 为图标创建图像 Dimension size = traygetTrayIconSize () BufferedImage bi = new BufferedImage (sizewidth sizeheight BufferedImageTYPE_INT_RGB) Graphics g = bigetGraphics () gsetColor (Colorred) gfillRect ( sizewidth sizeheight) gsetColor (Coloryellow) int ovalSize = (sizewidth < sizeheight) ? sizewidth sizeheight ovalSize /= gfillOval (sizewidth/ sizeheight/ ovalSize ovalSize) try { // 创建一个与程序的图标相关联的弹出菜单选择菜单唯一的一个菜单项就会结束程序 PopupMenu popup = new PopupMenu () MenuItem miExit = new MenuItem (Exit) ActionListener al al = new ActionListener () { public void actionPerformed (ActionEvent e) { Systemoutprintln (Goodbye) Systemexit () } } miExitaddActionListener (al) popupadd (miExit) // 从图像创建一个图标当鼠标位于图标上方式选择显示一个工具提示以及为图标分配弹出式菜单 TrayIcon ti = new TrayIcon (bi System Tray Demo # popup) // 创建并且关联一个listener到图标上当你采用了适当的动作例如在Windows下双击鼠标actionPerformed()方法将会被调用 al = new ActionListener () { public void actionPerformed (ActionEvent e) { Systemoutprintln (egetActionCommand ()) } } tisetActionCommand (My Icon) tiaddActionListener (al) // 创建并关联一个鼠标listener来记录于图标相关联的鼠标事件 MouseListener ml ml = new MouseListener () { public void mouseClicked (MouseEvent e) { Systemoutprintln (Tray icon Mouse clicked) } public void mouseEntered (MouseEvent e) { Systemoutprintln (Tray icon Mouse entered) } public void mouseExited (MouseEvent e) { Systemoutprintln (Tray icon Mouse exited) } public void mousePressed (MouseEvent e) { Systemoutprintln (Tray icon Mouse pressed) } public void mouseReleased (MouseEvent e) { Systemoutprintln (Tray icon Mouse released) } } tiaddMouseListener (ml) //创建并关联一个鼠标运动listener来记录于图标相关联的鼠标运动事件 MouseMotionListener mml mml = new MouseMotionListener () { public void mouseDragged (MouseEvent e) { Systemoutprintln (Tray icon Mouse dragged) } public void mouseMoved (MouseEvent e) { Systemoutprintln (Tray icon Mouse moved) } } tiaddMouseMotionListener (mml) // 将图标加入系统托盘 trayadd (ti) } catch (AWTException e) { Systemoutprintln (egetMessage ()) return } } }}
与SystemTrayDemo同样SystemTrayDemo在系统托盘显示了同样的图标移动鼠标到图标上除了在控制台窗口显示信息Mouse moved之外工具提示也将会出现当鼠标指针位于图标上方时试一下左键单击各种按下释放点击消息将会在控制台窗口出现如果你双击图标动作指令的名字将会在控制台窗口出现最后右键单击图标你将会看见一个带有Exit选项的弹出菜单表演示了SystemTrayDemo的图标的两个视图在左侧你能看见图标上的工具提示在右侧你可以看见图标的弹出菜单
表 托盘图标有自己的工具提示和弹出菜单
TrayIcon类提供了额外的一些方法——其中一些是通过构造器调用的——你也许会感兴趣这些方法包括 public void displayMessage(String caption String text TrayIconMessageType messageType) 在系统托盘上图标附近显示一条弹出消息这条消息持续的时间依赖于平台之后便会消失(我相信这个方法并不影响与TrayIcon相关联的工具提示)
caption显示在消息内容text的上方caption或text都可以为null但是如果均为null该方法将抛出NullPointerException最后messageType可以为下列消息类型之一TrayIconMessageTypeERROR (错误消息) TrayIconMessageTypeINFO (通知消息) TrayIconMessageTypeNONE (简单消息) or TrayIconMessageTypeWARNING (警告消息)当消息出现时平台可以使用messageType来决定什么动作——显示图形还是发出声音——需要被执行
这个方法并不是所有的平台都支持例如我在Windows ME平台上就无法显示消息 public void setImage(Image image) 创建一个image作为TrayIcon的Image这用于指示程序状态的改变是非常方便的前一个Image会丢弃而不调用Image的flush()方法——你必须明确的调用这个方法来转储所有被前一个Image对象使用的资源(包括为了屏幕绘制而缓存的像素数据)如果image为null该方法将抛出NullPointerException调用public Image getImage()方法来返回当前的Image public void setImageAutoSize(boolean autosize) 设置TrayIcon的自动调整尺寸属性这个属性决定了Image是否自动调整尺寸来适应分配给系统托盘图标的空间如果你传递true给autosizeImage将按照需要自动缩小或扩大否则Image将被裁减以适应分配的空间调用public boolean isImageAutoSize()方法可以返回自动调整大小属性的值 public void setPopupMenu(PopupMenu popup) 为TrayIcon设置弹出菜单如果popup为null没有PopupMenu与相关联TrayIcon如果你试图为不同的TrayIcon设置同样的弹出菜单该方法将抛出IllegalArgumentException一些平台也许不支持弹出菜单当用户右键单击图标时他们或者不显示菜单或者不显示本地版本的菜单调用public PopupMenu getPopupMenu()方法来返回当前的PopupMenu public void setToolTip(String tooltip) 为TrayIcon设置工具提示当用户把鼠标移动到系统托盘上的图标上方时工具提示会被显示出来传递null作为tooltip的值可以移除工具提示工具提示在一些平台上可能被简化了调用public String getToolTip()方法来返回当前的工具提示String
在文章的结尾我希望可以避开一个潜在的易混淆点 关于isSupported()方法的SystemTray文档建议将默认动作同时加入到动作listener和弹出菜单中以便保证托盘图标的默认动作始终可用这是什么意思呢?就是简单地向PopupMenu添加一个Default菜单项它拥有与你关联到TrayIcon的动作listener同样的动作listener像如下代码段所示
PopupMenu popup = new PopupMenu ()MenuItem miDefault = new MenuItem (Default)ActionListener alDefaultalDefault = new ActionListener () { public void actionPerformed (ActionEvent e) { Systemoutprintln (egetActionCommand ()) } }miDefaultaddActionListener (alDefault)popupadd (miDefault) MenuItem miExit = new MenuItem (Exit)ActionListener alExitalExit = new ActionListener () { public void actionPerformed (ActionEvent e) { Systemoutprintln (Goodbye) Systemexit () } }miExitaddActionListener (alExit)popupadd (miExit)TrayIcon ti = new TrayIcon (bi System Tray Demo # popup)tiaddActionListener (alDefault)
结论
Mustang就要到来了你一定已经忍不住想要在今年晚些时候正式版问世之前就体验一下这个最新的Java平台为了帮助你顺利起步本文展示了一些你也许可以在这个平台上找到的小示例本文关注了个重要的新特性控制台输入/输出分区空间方法启动画面API和系统托盘API
Jeff Friesen是一个自由软件开发者和教育者擅长CC++和Java技术