﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-study-随笔分类-AWT&amp;&amp;Swing</title><link>http://www.blogjava.net/xixidabao/category/15386.html</link><description>GROW WITH JAVA</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 04:18:16 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 04:18:16 GMT</pubDate><ttl>60</ttl><item><title>Java SE 6.0 桌面API编程探讨</title><link>http://www.blogjava.net/xixidabao/archive/2006/05/26/48366.html</link><dc:creator>JAVA之路</dc:creator><author>JAVA之路</author><pubDate>Fri, 26 May 2006 08:37:00 GMT</pubDate><guid>http://www.blogjava.net/xixidabao/archive/2006/05/26/48366.html</guid><description><![CDATA[
		<br />　　在默认GUI外观、打印和运行性能方面，Java平台一直在努力缩小本机应用程序和Java应用程序程序是之间的差距。随着Java SE 6（代码名为Mustang）的问世，一些新的功能又被加入，包括新的系统托盘功能，更好的打印支持和桌面API（java.awt.Desktop API），从而进一步缩小以上差距。本文中描述的这些新型桌面API允许Java应用程序与主机平台上的特定文件类型的默认应用程序进行交互。为了更有效地描述这些API，本文还将向你展示一个简单的示例应用程序DesktopDemo。<br /><br />　　<b>一、 桌面概述</b><br /><br />　　这种新功能是由java.awt.Desktop类所提供的。这种API来源于JDesktop集成组件（JDIC）工程。该工程的目的是，使得基于Java技术的应用程序成为桌面平台上的"第一等公民"，并实现与桌面API的无缝集成。具体地说，这种新型桌面API允许你的Java应用程序实现如下功能：<br /><br />　　· 使用一个特定的统一资源标志符（URI）启动主机系统的默认浏览器<br /><br />　　· 启动主机系统的默认电子邮件客户端<br /><br />　　· 启动特定的应用程序以打开、编辑或打印与之相关联的文件<br /><br />　　这些桌面API使用你的主机操作系统的文件关联以启动与特定文件类型相关联的应用程序。例如，如果开放文档文本（.odt）文件扩展名与OpenOffice书写器应用程序相关联，那么你的Java应用程序就可以启动OpenOffice书写器以打开、编辑或打印与这种关联相关的文件。根据你的主机系统的不同，不同的应用程序可能关联不同的行为。<br /><br />　　<b>二、 运行DesktopDemo应用程序</b><br /><br />　　DesktopDemo是一个简单Java应用程序-它使用了Mustang的桌面API。该应用程序提供了一个主窗口，允许你实现如下三项功能：<br /><br />　　1. 以一个特定的URI启动默认浏览器。<br /><br />　　2. 用一个邮件接收者启动默认电子邮件客户端。<br /><br />　　3. 启动一个相关联的应用程序以打开、编辑或打印文件。<br /><br />　　图1显示了这个用户接口（UI）。<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img style="BORDER-LEFT-COLOR: #000000; BORDER-BOTTOM-COLOR: #000000; BORDER-TOP-COLOR: #000000; BORDER-RIGHT-COLOR: #000000" src="/imagelist/06/21/1xyzpd01d40q.gif" border="1" /><br />图1：DesktopDemo用户接口</div></td></tr></tbody></table><br />　　你可以通过下载应用程序源代码及相关的JAR文件来运行这个应用程序-把你的控制台的活动目录改变为该应用程序工程的dist目录，并且使用一个Mustang JDK执行下列命令：<br /><br />java -jar DesktopDemo.jar<br /><br />　　<b>三、 确定是否支持Desktop API</b><br /><br />　　在启动浏览器、电子邮件客户端或任何应用程序之前，DesktopDemo必须确定是否你的平台支持这种API。然而，DesktopDemo首先停用所有的图形化的文本域和按钮。在确定该平台支持它们之后它该程序才启用这些图形组件。<br /><br />　　在实例化这些UI后，该应用程序的构造器快速停用这个应用程序的少数几个组件，如下列代码所示：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>public DesktopDemo() {<br />　//初始化所有的GUI组件.<br />　initComponents();<br />　// 停用启动浏览器和电子邮件客户端的按钮<br />　// 停用打开，编辑和打印文件的按钮<br />　disableActions();<br />　...<br />}<br />/**<br />* 停用所有的图形组件，直到我们了解<br />* 是否支持它们的功能.<br />*/<br />private void disableActions() {<br />　txtBrowserURI.setEnabled(false);<br />　btnLaunchBrowser.setEnabled(false);<br /><br />　txtMailTo.setEnabled(false);<br />　btnLaunchEmail.setEnabled(false);<br />　rbEdit.setEnabled(false);<br />　rbOpen.setEnabled(false);<br />　rbPrint.setEnabled(false);<br />　txtFile.setEnabled(false);<br />　btnLaunchApplication.setEnabled(false); <br />}<br />...<br />public javax.swing.JTextField txtBrowserURI;<br />public javax.swing.JButton btnLaunchBrowser;<br />public javax.swing.JTextField txtMailTo;<br />public javax.swing.JButton btnLaunchEmail;<br />public javax.swing.JRadioButton rbEdit;<br />public javax.swing.JRadioButton rbOpen;<br />public javax.swing.JRadioButton rbPrint;<br />public javax.swing.JTextField txtFile;<br />public javax.swing.JButton btnLaunchApplication;</td></tr></tbody></table><br />　　使用Desktop.isDesktopSupported()方法来确定是否桌面API可用。在Solaris操作系统和Linux平台上，这种API是依赖于Gnome库的。如果这些库不可用，那么这个方法将返回false。在确定支持这种API（也就是说，isDesktopSupported()返回true）之后，该应用程序就可以使用静态方法getDesktop()来检索一个Desktop实例。<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>Desktop desktop = null;<br />//在使用更多的Desktop API前，首先检查<br />//是否这种API为该特定主机上的特别的虚拟机所支持。<br />if (Desktop.isDesktopSupported()) {<br />desktop = Desktop.getDesktop();<br />...</td></tr></tbody></table><br />　　如果你的应用程序在调用getDesktop()之前不使用isDesktopSupported()进行API支持检查，那么它必须准备捕获一个UnsupportedOperationException异常-当你的应用程序在一个不支持这种特性的平台上请求一个Desktop实例时将抛出这种异常。另外，如果你的应用程序运行于一种无键盘、鼠标和监视器环境下，该getDesktop()方法将抛出一个java.awt.HeadlessException异常。<br /><br />　　一旦检索完毕，该Desktop实例即允许你的应用程序浏览、邮寄、打开、编辑或甚至打印一个文件或URI，但是只有在被检索的Desktop实例支持这些活动的前提下才行。每个这些活动被称为一个行为（Action），并且每一个行为被描述为一个Desktop.Action枚举实例：<br /><br />　　· BROWSE-描述主机的默认浏览器执行的一种浏览行为<br /><br />　　· MAIL-描述主机的默认电子邮件客户端执行的一种邮件行为<br /><br />　　· OPEN-描述一种与打开一特定的文件类型相关联的应用程序执行的打开行为<br /><br />　　· EDIT-描述一种与编辑一特定的文件类型相关联的应用程序执行的编辑行为<br /><br />　　· PRINT-描述一种与打印一特定的文件类型相关联的应用程序执行的打印行为<br /><br /><br />在调用任何这些行为之前，一个应用程序必须确定是否该Desktop实例支持它们。这与确定是否一个Desktop实例可用是有所不同的。这个Desktop.isDesktopSupported()方法告诉你是否能够创建一个实例。一旦获得一个Desktop对象，你就可以查询该对象来确定支持哪些特定类型的行为。如果该Desktop对象不支持特定的行为，或如果该桌面API本身并不被支持，那么DesktopDemo简单地停用那些受影响的图形组件。如图2所示，在停用状态下，不能使用这些组件来调用桌面特性。<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img style="BORDER-LEFT-COLOR: #000000; BORDER-BOTTOM-COLOR: #000000; BORDER-TOP-COLOR: #000000; BORDER-RIGHT-COLOR: #000000" src="/imagelist/06/21/y902c296u59n.gif" border="1" /><br />图2：当不支持桌面API时图形组件被停用。</div></td></tr></tbody></table><br />　　通过使用一个新的Desktop实例，下列代码检查负责是否支持Desktop.Action并且启用适当的图形组件：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>public DesktopDemo() {<br />　... <br />　//在使用更多的桌面API前，首先检查<br />　//是否这种API为该特定主机上的特别的虚拟机所支持。<br />　if (Desktop.isDesktopSupported()) {<br />　　desktop = Desktop.getDesktop();<br />　　// 现在，启用按钮以实现被支持的行为<br />　　enableSupportedActions();<br />　} <br />　...<br />}<br />/**<br />*启用在该主机上被支持的行为。<br />*这些行为有：打开浏览器，<br />*打开电子邮件客户端，和使用它们相关联的应用程序打开，编辑与打印文件。<br />*/<br />private void enableSupportedActions() {<br />　if (desktop.isSupported(Desktop.Action.BROWSE)) {<br />　　txtBrowserURI.setEnabled(true);<br />　　btnLaunchBrowser.setEnabled(true);<br />　}<br /><br />　if (desktop.isSupported(Desktop.Action.MAIL)) {<br />　　txtMailTo.setEnabled(true);<br />　　btnLaunchEmail.setEnabled(true);<br />　} <br />　if (desktop.isSupported(Desktop.Action.OPEN)) {<br />　　rbOpen.setEnabled(true);<br />　}<br />　if (desktop.isSupported(Desktop.Action.EDIT)) {<br />　　rbEdit.setEnabled(true);<br />　}<br />　if (desktop.isSupported(Desktop.Action.PRINT)) {<br />　　rbPrint.setEnabled(true);<br />　}<br /><br />　if (rbEdit.isEnabled() || rbOpen.isEnabled() || rbPrint.isEnabled()) {<br />　　txtFile.setEnabled(true);<br />　　btnLaunchApplication.setEnabled(true);<br />　}<br />}</td></tr></tbody></table><br />　　一旦该应用程序确定了被支持的行为，它即启用适当的图形组件。如果所有的组件都被启用，那么相应的UI应该看上去如图3所示。<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img style="BORDER-LEFT-COLOR: #000000; BORDER-BOTTOM-COLOR: #000000; BORDER-TOP-COLOR: #000000; BORDER-RIGHT-COLOR: #000000" src="/imagelist/06/21/4s1yyx00h3w6.gif" border="1" /><br />图3：当支持桌面API时，启用组件。</div></td></tr></tbody></table><br />　　<b>四、 打开浏览器</b><br /><br />　　调用下列实例方法将打开你的主机的默认浏览器：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>public void browse(URI uri) throws IOException</td></tr></tbody></table><br />　　因为仅当支持相关联的Desktop.ActionDesktopDemo时，UI组件才被启用，所以，在实际调用browse()方法之前，这个简单的演示应用程序不需要进行行为支持检查。然而，在每一种调用之前检查行为支持在实际中将增加程序的健壮性：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>if (desktop.isSupported(Desktop.Action.BROWSE)) {<br />　//启动浏览器<br />　...<br />}</td></tr></tbody></table><br />　　DesktopDemo把一个java.awt.event.ActionListener添加到每一个按钮上。当被启用时，"Launch Browser"按钮通过它的ActionListener调用下列方法：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>private void onLaunchBrowser(java.awt.event.ActionEvent evt) {<br />　URI uri = null;<br />　try {<br />　　uri = new URI(txtBrowserURI.getText());<br />　　desktop.browse(uri);<br />　}<br />　catch(IOException ioe) {<br />　　ioe.printStackTrace();<br />　}<br />　catch(URISyntaxException use) {<br />　　use.printStackTrace();<br />　}<br />　...<br />}</td></tr></tbody></table><br />　　这个browse()方法可能抛出各种类型的异常，这包括：当该URI为null时抛出一个NullPointerException异常；如果不支持BROWSE行为将抛出一个UnsupportedOperationException异常；如果不能发现或启动一个缺省的浏览器或应用程序则抛出一个IOException异常；如果一个安全管理器否定一次调用则抛出一个SecurityException异常。<br /><br />　　然而，如果一切顺利，那么听取器（Listener）将从图4中相联系的文本域中检索文本，创建一个URI并且调用browse()方法。上面的代码将启动你的系统的默认浏览器并且指示该浏览器装载该URI，如图5所示。<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img style="BORDER-LEFT-COLOR: #000000; BORDER-BOTTOM-COLOR: #000000; BORDER-TOP-COLOR: #000000; BORDER-RIGHT-COLOR: #000000" src="/imagelist/06/21/87mhe3p400r7.gif" border="1" /><br />图4:使用一个特定URI启动默认浏览器。<br /><img style="BORDER-LEFT-COLOR: #000000; BORDER-BOTTOM-COLOR: #000000; BORDER-TOP-COLOR: #000000; BORDER-RIGHT-COLOR: #000000" src="/imagelist/06/21/r2f3qwtln7c4.gif" border="1" /><br />图5:使用桌面API启动默认浏览器。</div></td></tr></tbody></table><br /><br /><br /><b>五、 发送电子邮件</b><br /><br />　　如果支持该行为的话，该应用程序能够启动主机的默认电子邮件客户端-通过调用这个Desktop实例方法：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>public void mail(URI uri) throws IOException<br />DesktopDemo为"Launch Mail"按钮提供了一个ActionListener。在这种情况中，该听取器调用下列方法：<br />private void onLaunchMail(java.awt.event.ActionEvent evt) {<br />　String mailTo = txtMailTo.getText();<br />　URI uriMailTo = null;<br />　try {<br />　　if (mailTo.length() &gt; 0) {<br />　　　uriMailTo = new URI("mailto"， mailTo， null);<br />　　　desktop.mail(uriMailTo);<br />　　} else {<br />　　　desktop.mail();<br />　　}<br />　}<br />　catch(IOException ioe) {<br />　　ioe.printStackTrace();<br />　}<br />　catch(URISyntaxException use) {<br />　　use.printStackTrace();<br />　}<br />　...<br />}</td></tr></tbody></table><br />　　该onLaunchMail()方法从相关的文本域中检索电子邮件接收者，并且在存在一位接收者时使用一种mailto模式的参数创建URI，然后调用mail()方法。这个mail()方法被重载，这样你可以使用（或不使用）一个描述其mailto接收者的URI（见图6）来调用这个方法。<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img style="BORDER-LEFT-COLOR: #000000; BORDER-BOTTOM-COLOR: #000000; BORDER-TOP-COLOR: #000000; BORDER-RIGHT-COLOR: #000000" src="/imagelist/06/21/syceoaf4m62n.gif" border="1" /><br />图6:使用一个电子邮件接收者启动默认电子邮件客户端。</div></td></tr></tbody></table><br />　　当创建这个URI时，你可以使用多个电子邮件接收者。这个mailto模式支持CC，BCC，SUBJECT和BODY域。例如，可以使用下列文本来创建一个mailto URI：<br /><br />mailto:duke@sun.com?SUBJECT=Happy New Year!&amp;BODY=Happy New Year， Duke!<br /><br />　　图7显示出相应的结果。<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img style="BORDER-LEFT-COLOR: #000000; BORDER-BOTTOM-COLOR: #000000; BORDER-TOP-COLOR: #000000; BORDER-RIGHT-COLOR: #000000" src="/imagelist/06/21/1rwp00iu0581.gif" border="1" /><br />图7：桌面API使用多个mailto参数启动默认电子邮件客户端。</div></td></tr></tbody></table><br />　　当然，你也可以不使用参数来调用mail()。在这种情况中，你的电子邮件客户端将启动一个新的没有指定接收者、主题或邮件正文的电子邮件窗口。 <br /><br />　　<b>六、 打开、编辑和打印文件</b><br /><br />　　Java应用程序可以分别使用一个Desktop对象的open()，edit()和print()方法来从与其相联系的应用程序中打开，编辑和打印文件(见图8)。同样，仅在该Desktop实例支持它们时，DesktopDemo才允许这些行为，因此在本应用程序环境下，不必再次进行这种支持检查。<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img style="BORDER-LEFT-COLOR: #000000; BORDER-BOTTOM-COLOR: #000000; BORDER-TOP-COLOR: #000000; BORDER-RIGHT-COLOR: #000000" src="/imagelist/06/21/a04wv36dyj3d.gif" border="1" /><br />图8:启动与一特定的文件类型相联系的应用程序。</div></td></tr></tbody></table><br />　　DesktopDemo中的每一个单选按钮也都有它自己的ActionListener。在这种情况中，每一个单选按钮都设置一个实例变量，以便描述最近选择的按钮的相关联Desktop.Action：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>Desktop.Action action;<br />private void onPrintAction(java.awt.event.ActionEvent evt) {<br />　action = Desktop.Action.PRINT;<br />} <br />private void onEditAction(java.awt.event.ActionEvent evt) {<br />　action = Desktop.Action.EDIT;<br />} <br />private void onOpenAction(java.awt.event.ActionEvent evt) {<br />　action = Desktop.Action.OPEN;<br />}</td></tr></tbody></table><br />　　当你按下"Launch Default Application"按钮时，它调用它自己的听取器-这将调用下列方法：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>private void onLaunchDefaultApplication(java.awt.event.ActionEvent evt) {<br />　String fileName = txtFile.getText();<br />　File file = new File(fileName);<br />　try {<br />　　switch(action) {<br />　　　case OPEN:<br />　　　　desktop.open(file);<br />　　　　break;<br />　　　case EDIT:<br />　　　　desktop.edit(file);<br />　　　　break;<br />　　　case PRINT:<br />　　　　desktop.print(file);<br />　　　　break;<br />　　}<br />　}<br />　catch (IOException ioe) {<br />　　ioe.printStackTrace();<br />　}<br />　...<br />}</td></tr></tbody></table><br />　　这个方法决定选择哪个Desktop.Action并且调用适当的Desktop实例方法-open()，edit()或print()。每个方法都需要一个File参数-它被用于执行要求的行为。<br /><br />　　有趣的是，不同的应用程序可以针对甚至相同的文件类型上的这些不同的行为进行注册。例如，可以使用OPEN行为启动Firefox浏览器，使用EDIT行为启动Emacs，甚至使用PRINT行为启动另外不同的应用程序。你的主机桌面的关联用来决定应该调用什么样的应用程序。<br /><br />　　注意 使用Mustang中现有桌面API来操作桌面文件关联是不可能的，而且目前只能使用平台依赖的工具来创建或改变这些关联。<br /><br />　　<b>七、 总结</b><br /><br />　　桌面集成是Mustang的一个重要主题。Mustang支持这种主题的一种方式是提供一组java.awt.Desktop API。这种API允许Java应用程序启动主机的默认浏览器和电子邮件客户端。另外，Java应用程序能够启动与特定的文件类型相关联的应用程序以打开，编辑和打印文件。尽管Java应用程序不能操作，创建，或改变文件关联，但是这些桌面API确定允许Java应用程序启动默认的相关联的应用程序.<br /><br /><br /><br /><img src ="http://www.blogjava.net/xixidabao/aggbug/48366.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/xixidabao/" target="_blank">JAVA之路</a> 2006-05-26 16:37 <a href="http://www.blogjava.net/xixidabao/archive/2006/05/26/48366.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Swing指南:Spinner Model Controls</title><link>http://www.blogjava.net/xixidabao/archive/2006/05/08/45052.html</link><dc:creator>JAVA之路</dc:creator><author>JAVA之路</author><pubDate>Mon, 08 May 2006 09:38:00 GMT</pubDate><guid>http://www.blogjava.net/xixidabao/archive/2006/05/08/45052.html</guid><description><![CDATA[
		<br />　　JSpinner工作起来好像是在JList或者JComboBox中间放了一个JFormattedTextField。在JList或者JComboBox中，用户可以提前设定好要输入的值。JSpinner也提供这样的一种机制。这个控件的另一个部分是JFormattedTextField。如何显示和输入不由那些小的控制格控制，比如JList。相反，可以通过JFormattedTextField来输入或通过边上的两个小箭头来浏览不同的可用的值。<br />　　<br />　　图示1显示了Spinner伴随不同的输入类型是什么样子。图示1的顶端的JSpinner是一个用来显示法语星期，通过SpinnerListModel。中间的，是一个通过SpinnerDateModel显示日期的JSpinner。底部的是使用SpinnerNumberModel的JSpinner。每一个都是通过各自神秘的方式，在本文的后面我们将要学习。<br />　　
<center>　<img src="../imgfiles/2005.11.30.9.36.32.3.1.jpg" /><br />　　图示1.JSpinner实例</center><br />　　<br />　　要创建和操纵JSpinner，许多类都将被调用，最重要的是JSpinner自己。最重要的两个准素集包括SpinnerModel接口，包括可选择的集合中的选项，还有，JSpinner.DefaultEditor的实现，用来捕获所有选择。庆幸的是，许多其它调用的类都是在后台工作的，比如，一旦你给SpinnerNumberModel提供了数字的范围，并且用这个类来协助Spinner，你的工作实际上是完成了。<br />　　<br />　　<b>创建JSpinner控件</b><br />　　<br />　　JSpinner类包括两个构造函数来初始化控件：<br />　　<br />　　public JSpinner()<br />　　JSpinner spinner = new JSpinner();<br />　　public JSpinner(SpinnerModel model)<br />　　SpinnerModel model = new SpinnerListModel(args);<br />　　JSpinner spinner = new JSpinner(model);<br />　　<br />　　开始的时候可以没有数据模型，后面可以使用它来跟踪JSpinner的方法。另一个方法，在创建这个控件的时候使用完整的模型，实现SpinnerModel接口，它里面有三个具体的子类可以使用：SpinnerDateModel，SpinnerListModel和SpinnerNumberModel，伴随着他们的抽象父类AbstractSpinnerModel。如果不指名模型，那么SpinnerNumberModel将默认使用。而显示和编辑的控件是JFormattedTextField，编辑的基本功能是通过一系列JSpinner的内部类实现的：DateEditor，ListEditor和NumberFormat，还有父类中DefaultEditor的支持。<br />　　<br />　　<b>JSpinner属性</b><br />　　<br />　　除了创建JSpinner对象之外，你还可以通过表一中的九个属性中的一个来进行配置。<br />　　<br />　　
<center>Table 1. JSpinner 属性<br />　　　<img src="../imgfiles/2005.11.30.9.36.40.3.2.gif" /></center><br />　　value属性中的值允许你更改当前控件的设置，nextValue和perviousValue可以使你以不同的方向察看模型中的入口。<br />　　<br />　　使用ChangeListener来监听JSpinner events<br />　　<br />　　JSpinner直接支持一种事件监听：changeListener。在别的地方，当commitEdit()方法被调用，这个事件将被触发，告诉你spinner的值发生改变。为了证明，列表1联系到一个自定义的ChangeListener，与图示1的程序相关联。<br />　　<br />　　列表 1. JSpinner with ChangeListener<br />　　import java.awt.*;<br />　　import javax.swing.*;<br />　　import javax.swing.event.*;<br />　　import java.text.*;<br />　　import java.util.*;<br />　　public class SpinnerSample {<br />　　public static void main (String args[]) {<br />　　Runnable runner = new Runnable() {<br />　　public void run() {<br />　　JFrame frame = new JFrame("JSpinner Sample");<br />　　frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);<br />　　DateFormatSymbols symbols =<br />　　new DateFormatSymbols(Locale.FRENCH);<br />　　ChangeListener listener = new ChangeListener() {<br />　　public void stateChanged(ChangeEvent e) {<br />　　System.out.println("Source: " + e.getSource());<br />　　}<br />　　};<br />　　String days[] = symbols.getWeekdays();<br />　　SpinnerModel model1 = new SpinnerListModel(days);<br />　　JSpinner spinner1 = new JSpinner(model1);<br />　　spinner1.addChangeListener(listener);<br />　　JLabel label1 = new JLabel("French Days/List");<br />　　JPanel panel1 = new JPanel(new BorderLayout());<br />　　panel1.add(label1, BorderLayout.WEST);<br />　　panel1.add(spinner1, BorderLayout.CENTER);<br />　　frame.add(panel1, BorderLayout.NORTH);<br />　　SpinnerModel model2 = new SpinnerDateModel();<br />　　JSpinner spinner2 = new JSpinner(model2);<br />　　spinner2.addChangeListener(listener);<br />　　JLabel label2 = new JLabel("Dates/Date");<br />　　JPanel panel2 = new JPanel(new BorderLayout());<br />　　panel2.add(label2, BorderLayout.WEST);<br />　　panel2.add(spinner2, BorderLayout.CENTER);<br />　　frame.add(panel2, BorderLayout.CENTER);<br />　　SpinnerModel model3 = new SpinnerNumberModel();<br />　　JSpinner spinner3 = new JSpinner(model3);<br />　　spinner3.addChangeListener(listener);<br />　　JLabel label3 = new JLabel("Numbers");<br />　　JPanel panel3 = new JPanel(new BorderLayout());<br />　　panel3.add(label3, BorderLayout.WEST);<br />　　panel3.add(spinner3, BorderLayout.CENTER);<br />　　frame.add(panel3, BorderLayout.SOUTH);<br />　　frame.setSize(200, 90);<br />　　frame.setVisible (true);<br />　　}<br />　　};<br />　　EventQueue.invokeLater(runner);<br />　　}<br />　　<br />　　运行这个程序可以示范listener的用法（当然，你也会发现更多关于ChangeListener的有意义的方法）。<br />　　<br />　　<b>定制JSpinner的外观</b><br />　　<br />　　同所有的Swing控件，JSpinner在不同的系统定义look-and-feel类型下，拥有不同的外观，如示图2。这个控件期初看起来像一个textfield，不同点是绘制了两个箭头。<br />　　
<center>　<img src="../imgfiles/2005.11.30.9.37.1.3.3.jpg" /><br />　　示图 2. JSpinner under different look-and-feel types<br />　　</center><br />　　集合中的11个UIResource属性在表格2中列举，有限的方法绘制text field和箭头。<br />　　<br />　　Table 2. JSpinner UIResource 元素<br />　　　<img src="../imgfiles/2005.11.30.9.36.52.3.4.gif" /><center></center><br />　　<b>SpinnerModel 接口</b><br />　　<br />　　到目前，我们已经看到了如何同一个主JSpinner类连接，SpinnerModel接口是控件的数据模型，SpinnerModel的定义如下：<br />　　<br />　　public interface SpinnerModel {<br />　　// Properties<br />　　public Object getValue();<br />　　public void setValue(Object);<br />　　public Object getNextValue();<br />　　public Object getPreviousValue();<br />　　// Listeners<br />　　public void addChangeListener(ChangeListener);<br />　　public void removeChangeListener(ChangeListener);<br />　　}<br />　　<br />　　SpinnerModel中的六个方法直接绘制了JSpinner，而JSpinner的方法间接调用模块中的方法，在监听的状况下，事件将联系到监听器。<br />　　<br />　　<b>AbstractSpinnerModel类</b><br />　　<br />　　AbstractSpinnerModel类基本要实现的是SpinnerModel接口，它提供了管理和通知的监听列表，子类必须实现接口中的四个方法，SpinnerModel中的三个具体实现如下：SpinnerDateModel，SpinnerListModel和SpinnerNumberModel。<br />　　<br />　　<b>SpinnerDateModel类</b><br />　　<br />　　从名字可以推断出，SpinnerDateModel提供了数据的选择。这个类有两个构造函数：一个默认选择所有的数据，另一个要求你给出范围。<br />　　<br />　　public SpinnerDateModel()SpinnerModel model = new SpinnerDateModel();JSpinner spinner = new JSpinner(model);public SpinnerDateModel(Date value, Comparable start, Comparable end,　 int calendarField)Calendar cal = Calendar.getInstance();Date now = cal.getTime();cal.add(Calendar.YEAR, -50);Date startDate = cal.getTime();cal.add(Calendar.YEAR, 100);Date endDate = cal.getTime();SpinnerModel model =　 new SpinnerDateModel(now, startDate, endDate, Calendar.YEAR);JSpinner spinner = new JSpinner(model);<br />　　<br />　　如果不指名任何参数，就没有开始和结束点。下面的例子展示了使用参数来表示100年的范围。最后一个成员变量应该是Calendar类中的一个定值：<br />　　·　　　　Calendar.AM_PM<br />　　·　　　　Calendar.DAY_OF_MONTH<br />　　·　　　　Calendar.DAY_OF_WEEK<br />　　·　　　　Calendar.DAY_OF_WEEK_IN_MONTH<br />　　·　　　　Calendar.DAY_OF_YEAR<br />　　·　　　　Calendar.ERA<br />　　·　　　　Calendar.HOUR<br />　　·　　　　Calendar.HOUR_OF_DAY<br />　　·　　　　Calendar.MILLISECOND<br />　　·　　　　Calendar.MINUTE<br />　　·　　　　Calendar.MONTH<br />　　·　　　　Calendar.SECOND<br />　　·　　　　Calendar.WEEK_OF_MONTH<br />　　·　　　　Calendar.WEEK_OF_YEAR<br />　　·　　　　Calendar.YEAR<br />　　<br />　　注意：SpinnerDateModel不包含任何Calendar类中的时间域，所以不能通过SpinnerDateModel在JSpinner中翻转。<br />　　<br />　　表格3列出了SpinnerModel中的三个属性，四个关于SpinnerDateModel。<br />　　<br />　　
<center>Table 3. SpinnerDateModel 属性<br />　　　<img src="../imgfiles/2005.11.30.9.37.12.3.5.gif" /></center><br />　　典型地，唯一的新属性中你将要用来获得最终的日期，尽管所有的结果都被包裹在getValue()中，以适当的数据类型。如果在构造函数中提供了数据的表示范围，那么previous和next的值将是null，在边界条件下。<br />　　<br />　　<b>SpinnerListModel</b><br />　　<br />　　SpinnerListModel提供了从一个入口列表中选择或者至少是字符串表述，这个类有三个构造函数：<br />　　<br />　　public SpinnerListModel()SpinnerModel model = new SpinnerListModel();<br />　　JSpinner spinner = new JSpinner(model);<br />　　public SpinnerListModel(List&lt;?&gt; values)List&lt;String&gt; list = args;<br />　　SpinnerModel model = new SpinnerListModel(list);<br />　　JSpinner spinner = new JSpinner(model);<br />　　public SpinnerListModel(Object[] values)SpinnerModel model = new SpinnerListModel(args);<br />　　JSpinner spinner = new JSpinner(model);<br />　　<br />　　当没有参数提供时，这个模型包括一个元素：字符串empty。List版保留一个对list的引用。而不是list的拷贝。如果改变了list，那么模型中的list也将改变。数组版本的创建了一个私有的内部类，并且实例化一个list。对于list和数组版本，初始选择的是第一个元素，否则将抛出一个IllegalArgumentException异常。<br />　　<br />　　如表格4显示，属性中增添的是set和get list。<br />　　<br />　　
<center>Table 4. SpinnerListModel 属性<br />　　　<img src="../imgfiles/2005.11.30.9.37.21.3.6.gif" /></center><br />　　<b>SpinnerNumberModel类</b><br />　　<br />　　SpinnerNumberModel提供了从一个开区间或闭区间选择数字的模式，数字可以使Number类的所有子类，包括Integer和Double。他有四个构造函数。<br />　　<br />　　public SpinnerNumberModel()SpinnerModel model = new SpinnerNumberModel();<br />　　JSpinner spinner = new JSpinner(model);<br />　　public SpinnerNumberModel(double value, double minimum, double maximum,<br />　　double stepSize)SpinnerModel model = new SpinnerNumberModel(50, 0, 100, .25);<br />　　JSpinner spinner = new JSpinner(model);<br />　　public SpinnerNumberModel(int value, int minimum, int maximum, int stepSize)SpinnerModel model = new SpinnerNumberModel(50, 0, 100, 1);<br />　　JSpinner spinner = new JSpinner(model);<br />　　public SpinnerNumberModel(Number value, Comparable minimum, Comparable maximum,<br />　　Number stepSize)Number value = new Integer(50);<br />　　Number min = new Integer(0);Number max = new Integer(100);<br />　　Number step = new Integer(1);SpinnerModel model = new SpinnerNumberModel(value, min, max, step);<br />　　JSpinner spinner = new JSpinner(model);<br />　　<br />　　如果最大或最小值为null，则为开区间。对于没有参数的，初始值为1，步进为1。步进是整形的，如果你设为.333，那么将不会完成。<br />　　<br />　　表格5展示了SpinnerNumberModel的属性：<br />　　<br />　　
<center>Table 5. SpinnerNumberModel 属性<br />　　　<img src="../imgfiles/2005.11.30.9.37.36.3.7.gif" /></center><br />　　<b>自定义模型</b><br />　　<br />　　一般来说，可用的JSpinner模型已经足够了，所以没有必要创建他的子类了。但是，并不是所有场合都能满足。比如，你可能希望使用一个包装了SpinnerListModel的模型，代替停止在第一个或最后一个元素，他包装了另一个结束。在列表2中给出了具体实现：<br />　　<br />　　Listing 2. RolloverSpinnerListModel 类<br />　　<br />　　import javax.swing.*;<br />　　import java.util.*;<br />　　public class RolloverSpinnerListModel extends SpinnerListModel {<br />　　public RolloverSpinnerListModel(List values) {<br />　　super(values);<br />　　}<br />　　public RolloverSpinnerListModel(Object[] values) {<br />　　super(values);<br />　　}<br />　　public Object getNextValue() {<br />　　Object returnValue = super.getNextValue();<br />　　if (returnValue == null) {<br />　　returnValue = getList().get(0);<br />　　}<br />　　return returnValue;<br />　　}<br />　　public Object getPreviousValue() {<br />　　Object returnValue = super.getPreviousValue();<br />　　if (returnValue == null) {<br />　　List list = getList();<br />　　returnValue = list.get(list.size() - 1);<br />　　}<br />　　return returnValue;<br />　　}}<br />　　<br />　　<b>JSpinner编辑器</b><br />　　<br />　　对于JSpinner每个可用的模型，一个次要的支持类，JSpinner的一个内部类。然而这个模块可以控制控件是否可选，JSpinner编辑器允许你控制如何显示和编辑每个可选的值。<br />　　<br />　　<b>JSpinner.DefaultEditor类</b><br />　　<br />　　JSpinner的setEditor()方法允许你将任何Jcomponent作为JSpinner的编辑器，当然你可以那样做，更典型的是，你将用JSpinner.DefaultEditor的子类作运行。以JformattedTextField作为简单的编辑器工作，将提供所有你需要的基本功能。它包括一个基本的构造函数：<br />　　<br />　　public JSpinner.DefaultEditor(JSpinner spinner)JSpinner spinner = new JSpinner();<br />　　JComponent editor = JSpinner.DefaultEditor(spinner);<br />　　spinner.setEditor(editor);<br />　　<br />　　在表格6中可以看到，有两个属性：<br />　　<br />　　
<center>Table 6. JSpinner.DefaultEditor properties<br />　　　<img src="../imgfiles/2005.11.30.9.37.44.3.8.gif" /></center><br />　　在不知道使用的是哪个模型工作的情况下，在这个级别的你可以做的是改变JformattedTextField中的文字显示。更典型的是，你将改变模型编辑器的某些自定义方面。<br />　　<br />　　<b>JSpinner.DateEditor类</b><br />　　<br />　　DateEditor允许你定制不同的日期显示方式，使用java.text包中SimpleDateFormat类。察看Javadoc了解更多的关于SimpleDateFormat的可用格式模式。如果你不喜欢默认的地显示方式，可以通过给构造函数的第二个参数传递一个新参数来改变显示模式。<br />　　<br />　　public JSpinner.DateEditor(JSpinner spinner)SpinnerModel model = new SpinnerDateModel();<br />　　JSpinner spinner = new JSpinner(model);JComponent editor = JSpinner.DateEditor(spinner);<br />　　spinner.setEditor(editor);<br />　　public JSpinner.DateEditor(JSpinner spinner, String dateFormatPattern)SpinnerModel model = new SpinnerDateModel();<br />　　JSpinner spinner = new JSpinner(model);<br />　　JComponent editor = JSpinner.DateEditor(spinner, "MMMM yyyy");<br />　　spinner.setEditor(editor);<br />　　<br />　　默认情况，格式是M/d/yy h:mm a或者12/25/04 12:34 PM 代表2004年的圣诞节的某个时间。后面的例子将要显示2004 December。<br />　　<br />　　编辑器的两个属性在表格7中。<br />　　<br />　　
<center>Table 7. JSpinner.DateEditor属性<br />　　　<img src="../imgfiles/2005.11.30.9.37.51.3.9.gif" /></center><br />　　<b>JSpinner.ListEditor类</b><br />　　<br />　　当使用SpinnerListModel类工作时，ListEditor不支持任何特殊格式。而是提供了前置类型支持。既然模块的所有入口都知道了，编辑器将尝试匹配用户输入的字符。这里只有一个构造函数，但是你可能几乎用不到。<br />　　<br />　　public JSpinner.ListEditor(JSpinner spinner)<br />　　<br />　　在表格8中将看到ListEditor只有一个属性：<br />　　
<center>　<img src="../imgfiles/2005.11.30.9.38.0.3.10.gif" /></center><br />　　<b>JSpinner.NumberEditor类</b><br />　　<br />　　NumberEditor和DateEditor的工作方式很相似，允许你输入定制的显示模式。代替SimpleDateFormat工作，NumberEditor可以协助java.text包中的DecimalFormat类。就像DateEditor一样，他又两个构造函数：<br />　　<br />　　public JSpinner.NumberEditor(JSpinner spinner)SpinnerModel model = new SpinnerNumberModel(50, 0, 100, .25);JSpinner spinner = new JSpinner(model);JComponent editor = JSpinner.NumberEditor(spinner);spinner.setEditor(editor);public JSpinner.NumberEditor(JSpinner spinner, String decimalFormatPattern)SpinnerModel model = new SpinnerNumberModel(50, 0, 100, .25);JSpinner spinner = new JSpinner(model);JComponent editor = JSpinner.NumberEditor(spinner, "#,##0.###");spinner.setEditor(editor);<br />　　<br />　　第二个构造函数使用默认的字符串格式。如果数字太大，将使用逗号。如果结果是一个完整的数，将不会用十进制显示。<br />　　<br />　　表格9中，显示editor的两个属性。<br />　　<br />　　
<center>Table 9. JSpinner.NumberEditor 属性<br />　　　<img src="../imgfiles/2005.11.30.9.38.7.3.11.gif" /></center><br />　　<b>总结</b><br />　　<br />　　在这篇文章中，你学习到了Swing中的JSpinner控件。当你要控制某些选择在一定的范围中时，JSpinner可以让你通过翻滚来选择需要的值。你学习到了如何提供这些要选择的值：通过使用SpinnerDateModel和DateEditor，SpinnerListModel和ListEditor，SpinnerNumberModel和NumberEditor来设置日期。 <br /><br /><br /><img src ="http://www.blogjava.net/xixidabao/aggbug/45052.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/xixidabao/" target="_blank">JAVA之路</a> 2006-05-08 17:38 <a href="http://www.blogjava.net/xixidabao/archive/2006/05/08/45052.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Swing 破局：打造半透明窗口 </title><link>http://www.blogjava.net/xixidabao/archive/2006/05/04/44489.html</link><dc:creator>JAVA之路</dc:creator><author>JAVA之路</author><pubDate>Wed, 03 May 2006 16:26:00 GMT</pubDate><guid>http://www.blogjava.net/xixidabao/archive/2006/05/04/44489.html</guid><description><![CDATA[
		<p>　　要生成一个半透明的成形窗口,而又要避免使用本地的编码,唯有灵活地应用screenshot(屏幕快照).<br /><br />　　半透明窗口是大众对Swing最为渴求的特性之一. 也可以称之为定形窗口,这种窗口有一部分是透明的,可以透过它看到桌面背景和其它的程序.如果不通过JNI(Java Native Interface 本地接口)Java是无法为我们生成一个半透明的窗口的(即使我们可以那样做，还得本地操作平台好支持半透明窗口才行).然而这些现状无法阻止我们对半透明窗口的渴求,通过一个我最喜欢的手段screenshot,我们可以欺骗性地实现这个目的.<br /><br />　　仿造这样一个的半透明窗口的过程,主要的通过以下几点:<br />1.在窗口显示之前,先获得一个screenshot;<br />2.把上一步获取的屏幕快照,作为窗口的背景图<br />3.调整位置,以便于我们捕获的screenshot和实际当前的屏幕完美结合,制造出一种半透明的假象.<br /><br />　　刚刚说到的部分只是小儿科,重头戏在于,如何在移动或变化半透明窗口时,及时地更新screenshot,也就是及时更新半透明窗口的背景.<br /><br />　　在开始我们的旅行之前,先生成一个类，让它继承 JPanel,我们用这个继承类来捕获屏幕,并把捕获的照片作为背景. 类的具体代码如下例6-1<br /><br />例 6－1 。 半透明背景组件<br />public class TransparentBackground extends Jcomponent { <br />    private JFrame frame; <br />    private Image background;<br /><br />public TransparentBackground(JFrame frame) {<br />    this.frame = frame;<br />    updateBackground( );<br />}<br />/**<br />  * @todo 获取屏幕快照后立即更新窗口背景<br />  */<br />public void updateBackground( ) {<br />    try {<br />        Robot rbt = new Robot( );<br />        Toolkit tk = Toolkit.getDefaultToolkit( );<br />        Dimension dim = tk.getScreenSize( );<br />        background = rbt.createScreenCapture(<br />        new Rectangle(0,0,(int)dim.getWidth( ),<br />                          (int)dim.getHeight( )));<br />    } catch (Exception ex) {<br />        //p(ex.toString( )); <br />// 此方法没有申明过，因为无法得知上下文。因为不影响执行效果，先注释掉它<br />        ex.printStackTrace( );<br />    }<br />}<br />public void paintComponent(Graphics g) {<br />    Point pos = this.getLocationOnScreen( );<br />    Point offset = new Point(-pos.x,-pos.y);<br />    g.drawImage(background,offset.x,offset.y,null);<br />}<br />}<br />　　首先,构造方法把一个reference保存到父的JFrame,然后调用updateBackground()方法,在这个方法中,我们可以利用java.awt.Robot类捕获到整个屏幕,并把捕获到的图像保存到一个定义了的放置背景的变量中. paintComponent()方法可以帮助我们获得窗口在屏幕上的绝对位置,并用刚刚得到的背景作为panel的背景图,同时这个背景图会因为panel位置的不同而作对应的移动,以使panel的背景和panel覆盖的那部分屏幕图像无缝重叠在一起,同时也就使panel和周围的屏幕关联起来.<br /><br />我们可以通过下面这个main方法简单的运行一下,随便放置一些组件到panel上,再把panel放置到frame中显示.<br />public static void main(String[] args) {<br />    JFrame frame = new JFrame("Transparent Window");<br />    TransparentBackground bg = new TransparentBackground(frame);<br />    bg.setLayout(new BorderLayout( ));<br />    JButton button = new JButton("This is a button");<br />    bg.add("North",button);<br />        JLabel label = new JLabel("This is a label");<br />    bg.add("South",label);<br />    frame.getContentPane( ).add("Center",bg);<br />    frame.pack( );<br />    frame.setSize(150,100);<br />    frame.show( );<br />}<br />通过这段代码,运行出的效果如下图6－1所示：<br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200602/20060205101620844.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br />图6-1 展示中的半透明窗口<br /><br />　　这段代码相当简单，却带有两个不足之处。首先，如果移动窗口，panel中的背景无法自动的更新，而paintComponent（）只在改变窗口大小时被调用；其次，如果屏幕曾经发生过变化，那么我们制作的窗口将永远无法和和屏幕背景联合成整体。<br /><br />　　谁也不想时不时地跑去更新screenshot，想想看，要找到隐藏于窗口后的东西，要获得一份新的screenshot，还要时不时的用这些screenshot来更新我们的半透明窗口，这些事情足以让用户无法安心工作。事实上，想要获取窗口之外的屏幕的变化几乎是不太可能的事，但多数变动都是发生在foreground窗口发生焦点变化或被移动之时。如果你接受这的观点（至少我接受这个观点）,那么你可以只监控下面提到的几个事件，并只需在这几个事件被触发时，去更新screenshot。<br />public class TransparentBackground extends JComponent<br />        implements ComponentListener, WindowFocusListener,<br />        Runnable {<br />    private JFrame frame;<br />    private Image background;<br />    private long lastupdate = 0;<br />    public boolean refreshRequested = true;<br />    public TransparentBackground(JFrame frame) { <br />        this.frame = frame;<br />        updateBackground( );<br />        frame.addComponentListener(this); <br />        frame.addWindowFocusListener(this);<br />        new Thread(this).start( );<br />    }<br />    public void componentShown(ComponentEvent evt) { repaint( ); }<br />    public void componentResized(ComponentEvent evt) { repaint( ); }<br />    public void componentMoved(ComponentEvent evt) { repaint( ); }<br />    public void componentHidden(ComponentEvent evt) { }<br /><br />    public void windowGainedFocus(WindowEvent evt) { refresh( ); }    <br />    public void windowLostFocus(WindowEvent evt) { refresh( ); }<br />　　首先，让我们的半透明窗口即panel实现ComponentListener接口,<br />WindowFocusListener接口和Runnable接口。Listener接口可以帮助我们捕获到窗口的移动，大小变化，和焦点变化。实现Runnable接口可以使得panel生成一个线程去控制定制的repaint()方法。<br /><br />　　ComponentListener接口带有四个component开头的方法。它们都可以很方便地调用repaint()方法，所以窗口的背景也就可以随着窗口的移动，大小的变化而相应地更新。还有两个是焦点处理的，它们只调用refresh(),如下示意：<br />public void refresh( ) {<br />    if(frame.isVisible( )) {<br />        repaint( );<br />        refreshRequested = true;<br />        lastupdate = new Date( ).getTime( );<br />    }<br />}<br />public void run( ) {<br />    try {<br />        while(true) {<br />            Thread.sleep(250);<br />            long now = new Date( ).getTime( );<br />            if(refreshRequested &amp;&amp;<br />                ((now - lastupdate) &gt; 1000)) {<br />                if(frame.isVisible( )) {<br />                    Point location = frame.getLocation( );<br />                    frame.hide( );<br />                    updateBackground( );<br />                    frame.show( );<br />                frame.setLocation(location);<br />                    refresh( );<br />                }<br />                lastupdate = now;<br />                refreshRequested = false;<br />                }<br />            }<br />        } catch (Exception ex) {<br />            p(ex.toString( ));<br />            ex.printStackTrace( );<br />        } <br />    }</p>
		<p>　　refresh()可以保证frame可见，并适时得调用repaint()。它也会对refreshRequest变量置真(true)，同时保存当前时间值，现在所做的这些对接下来要做的事是非常重要的铺垫。<br /><br />　　除了每四分之一秒被唤醒一次，用来检测是否有新的刷新的要求或者是否离上次刷新时间超过了一秒，方法run()一般地处于休眠状态。如果离上次刷新超过了一秒并且frame是可见的，那么run()将保存frame的位置，隐藏frame，获取一个screenshot，更新frame背景，再根据隐藏frame时保存的位置信息，重新显示已经更新了背景的frame，接着调用refresh（）方法。通过这样的控制，使得背景更新不至于比需要的多太多。<br /><br />　　那么我们为什么要对用一个线程控制刷新如此长篇大论呢？一个词：递归。事件处理可以直接轻松地调用repaint()，但是隐藏和显示窗口已便于获取screenshot 却交替了很多“得焦”和“失焦”事件。所有这些都会触发一个新的背景更新，导致窗口再次被隐藏，如此往返，将导致永无止境的循环。一个新的“得焦”事件，将在执行refresh()几毫秒之后被调用，所以简单地检测isRecursing标志是无法阻止循环的继续。<br /><br />　　另外，用户任意一个改变屏幕的动作，将会随之引出一堆的事件来，而不仅仅是简单一个。应该是最后一个事件去触发updateBackground(),而不是第一个。为了全面解决这些问题，代码产生一个线程，然后用这个线程去监控重画（repaint）要求，并保证当前的执行动作是发生在过去的1000毫秒内没有发生过此动作。如果一个客户每五秒不间断地产生事件（比如，寻找丢失的浏览窗口），那么只有在其它所有工作在一秒内完成才执行更新。这样就避免了，用户不至于在移动东西时，窗口却消失不见了的尴尬。<br /><br />　　另一件烦恼的事就是，我们的窗口仍旧有边框，这条边框使得我们无法完美和背景融为一体。更为痛苦的是使用setUndecorated(true)移除边框时，我们的标题栏和窗口控制栏也跟着移除了。可是这也算不上是什么大问题，因为那类使用定形窗口的应用程序一般都具有可拖动的背景【Hack＃34】<br /><br />　　接下来，我们在下面这个简单的测试程序中把所讲的东西落实进去：<br />public static void main(String[] args) {<br />    JFrame frame = new JFrame("Transparent Window");<br />    frame.setUndecorated(true);<br />    <br />    TransparentBackground bg = new TransparentBackground(frame);<br />    bg.snapBackground( );<br />    bg.setLayout(new BorderLayout( ));<br /><br />   JPanel panel = new JPanel( ) {<br />        public void paintComponent(Graphics g) { <br />            g.setColor(Color.blue);<br />            Image img = new ImageIcon("mp3.png").getImage( ); <br />            g.drawImage(img,0,0,null);<br />        }<br />    };<br />    panel.setOpaque(false);<br /><br />    bg.add("Center",panel);<br /><br />    frame.getContentPane( ).add("Center",bg);<br />    frame.pack( );<br />    frame.setSize(200,200);<br />    frame.setLocation(500,500);<br />    frame.show( );<br />}<br />　　这段代码通过继承JPanel，加上一个透明的PNG格式图片，人工生成一个mp3播放器界面。注意使用了setUndecorated（）来隐藏边框和标题栏。调用setOpaque(false),将隐藏默认的背景（一般为灰色），这样screenshot的背景就可以和图片中透明的部分合成一个整体，去配合程序窗口周围的屏幕背景。（如图6-2）通过一系列的努力，就可以看到图6－3的效果。是不是很让人惊诧？会不会感叹Java的新版本腾空出世？<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200602/20060205101622347.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br />图 6－2. mp3 播放器外观模板<br /><br /><img onmouseover="javascript:imgShowTip(this);" style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://java.chinaitlab.com/UploadFiles_8734/200602/20060205101622824.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" />  <br />图 6-3. 运行中的mp3播放器<br /></p>
		<br />
		<br />
		<br />
<img src ="http://www.blogjava.net/xixidabao/aggbug/44489.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/xixidabao/" target="_blank">JAVA之路</a> 2006-05-04 00:26 <a href="http://www.blogjava.net/xixidabao/archive/2006/05/04/44489.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>