??xml version="1.0" encoding="utf-8" standalone="yes"?>
一?桌面概述
q种新功能是由java.awt.DesktopcL提供的。这UAPI来源于JDesktop集成lgQJDICQ工E。该工程的目的是Q得基于Java技术的应用E序成ؓ桌面q_上的"W一{公?Qƈ实现与桌面API的无~集成。具体地_q种新型桌面API允许你的Java应用E序实现如下功能Q?br />
· 使用一个特定的l一资源标志W(URIQ启动主机系l的默认览?br />
· 启动Lpȝ的默认电(sh)子邮件客L
· 启动特定的应用程序以打开、编辑或打印与之相关联的文g
q些桌面API使用你的L操作pȝ的文件关联以启动与特定文件类型相兌的应用程序。例如,如果开放文档文本(.odtQ文件扩展名与OpenOffice书写器应用程序相兌Q那么你的Java应用E序可以启动OpenOffice书写器以打开、编辑或打印与这U关联相关的文g。根据你的主机系l的不同Q不同的应用E序可能兌不同的行为?br />
二?q行DesktopDemo应用E序
DesktopDemo是一个简单Java应用E序-它用了Mustang的桌面API。该应用E序提供了一个主H口Q允怽实现如下三项功能Q?br />
1. 以一个特定的URI启动默认览器?br />
2. 用一个邮件接收者启动默认电(sh)子邮件客L?br />
3. 启动一个相兌的应用程序以打开、编辑或打印文g?br />
?昄了这个用h口(UIQ?br />
?QDesktopDemo用户接口
你可以通过下蝲应用E序源代码及相关的JAR文g来运行这个应用程?把你的控制台的活动目录改变ؓ该应用程序工E的dist目录Qƈ且用一个Mustang JDK执行下列命oQ?br />
java -jar DesktopDemo.jar
三?定是否支持Desktop API
在启动浏览器、电(sh)子邮件客L或Q何应用程序之前,DesktopDemo必须定是否你的q_支持q种API。然而,DesktopDemo首先停用所有的囑Ş化的文本域和按钮。在定该^台支持它们之后它该程序才启用q些囑Şlg?br />
在实例化q些UI后,该应用程序的构造器快速停用这个应用程序的数几个lgQ如下列代码所C:public DesktopDemo() {
//初始化所有的GUIlg.
initComponents();
// 停用启动览器和?sh)子邮g客户端的按钮
// 停用打开Q编辑和打印文g的按?br /> disableActions();
...
}
/**
* 停用所有的囑ŞlgQ直到我们了?br />* 是否支持它们的功?
*/
private void disableActions() {
txtBrowserURI.setEnabled(false);
btnLaunchBrowser.setEnabled(false);
txtMailTo.setEnabled(false);
btnLaunchEmail.setEnabled(false);
rbEdit.setEnabled(false);
rbOpen.setEnabled(false);
rbPrint.setEnabled(false);
txtFile.setEnabled(false);
btnLaunchApplication.setEnabled(false);
}
...
public javax.swing.JTextField txtBrowserURI;
public javax.swing.JButton btnLaunchBrowser;
public javax.swing.JTextField txtMailTo;
public javax.swing.JButton btnLaunchEmail;
public javax.swing.JRadioButton rbEdit;
public javax.swing.JRadioButton rbOpen;
public javax.swing.JRadioButton rbPrint;
public javax.swing.JTextField txtFile;
public javax.swing.JButton btnLaunchApplication;
使用Desktop.isDesktopSupported()Ҏ(gu)来确定是否桌面API可用。在Solaris操作pȝ和Linuxq_上,q种API是依赖于Gnome库的。如果这些库不可用,那么q个Ҏ(gu)返回false。在定支持q种APIQ也是_isDesktopSupported()q回trueQ之后,该应用程序就可以使用静态方法getDesktop()来检索一个Desktop实例?br />Desktop desktop = null;
//在用更多的Desktop API前,首先?br />//是否q种API特定L上的特别的虚拟机所支持?br />if (Desktop.isDesktopSupported()) {
desktop = Desktop.getDesktop();
...
如果你的应用E序在调用getDesktop()之前不用isDesktopSupported()q行API支持查,那么它必d备捕获一个UnsupportedOperationException异常-当你的应用程序在一个不支持q种Ҏ(gu)的q_上请求一个Desktop实例时将抛出q种异常。另外,如果你的应用E序q行于一U无键盘、鼠标和监视器环境下Q该getDesktop()Ҏ(gu)抛Z个java.awt.HeadlessException异常?br />
一旦检索完毕,该Desktop实例卛_怽的应用程序浏览、邮寄、打开、编辑或甚至打印一个文件或URIQ但是只有在被检索的Desktop实例支持q些zd的前提下才行。每个这些活动被UCؓ一个行为(ActionQ,q且每一个行描述Z个Desktop.Action枚D实例Q?br />
· BROWSE-描述L的默认浏览器执行的一U浏览行?br />
· MAIL-描述L的默认电(sh)子邮件客L执行的一U邮件行?br />
· OPEN-描述一U与打开一特定的文件类型相兌的应用程序执行的打开行ؓ
· EDIT-描述一U与~辑一特定的文件类型相兌的应用程序执行的~辑行ؓ
· PRINT-描述一U与打印一特定的文件类型相兌的应用程序执行的打印行ؓ
在调用Q何这些行Z前,一个应用程序必ȝ定是否该Desktop实例支持它们。这与确定是否一个Desktop实例可用是有所不同的。这个Desktop.isDesktopSupported()Ҏ(gu)告诉你是否能够创Z个实例。一旦获得一个Desktop对象Q你可以查询该对象来确定支持哪些特定类型的行ؓ。如果该Desktop对象不支持特定的行ؓQ或如果该桌面API本nq不被支持,那么DesktopDemo单地停用那些受媄响的囑Şlg。如?所C,在停用状态下Q不能用这些组件来调用桌面Ҏ(gu)?br />
?Q当不支持桌面API时图形组件被停用?/div>
通过使用一个新的Desktop实例Q下列代码检查负责是否支持Desktop.Actionq且启用适当的图形组Ӟpublic DesktopDemo() {
...
//在用更多的桌面API前,首先?br /> //是否q种API特定L上的特别的虚拟机所支持?br /> if (Desktop.isDesktopSupported()) {
desktop = Desktop.getDesktop();
// 现在Q启用按钮以实现被支持的行ؓ
enableSupportedActions();
}
...
}
/**
*启用在该L上被支持的行为?br />*q些行ؓ有:打开览器,
*打开?sh)子邮g客户端,和用它们相兌的应用程序打开Q编辑与打印文g?br />*/
private void enableSupportedActions() {
if (desktop.isSupported(Desktop.Action.BROWSE)) {
txtBrowserURI.setEnabled(true);
btnLaunchBrowser.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.MAIL)) {
txtMailTo.setEnabled(true);
btnLaunchEmail.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.OPEN)) {
rbOpen.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.EDIT)) {
rbEdit.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.PRINT)) {
rbPrint.setEnabled(true);
}
if (rbEdit.isEnabled() || rbOpen.isEnabled() || rbPrint.isEnabled()) {
txtFile.setEnabled(true);
btnLaunchApplication.setEnabled(true);
}
}
一旦该应用E序定了被支持的行为,它即启用适当的图形组件。如果所有的lg都被启用Q那么相应的UI应该看上d?所C?br />
?Q当支持桌面APIӞ启用lg?/div>
四?打开览?/b>
调用下列实例Ҏ(gu)打开你的L的默认浏览器Q?br />public void browse(URI uri) throws IOException
因ؓ仅当支持相关联的Desktop.ActionDesktopDemoӞUIlg才被启用Q所以,在实际调用browse()Ҏ(gu)之前Q这个简单的演示应用E序不需要进行行为支持检查。然而,在每一U调用之前检查行为支持在实际中将增加E序的健壮性:if (desktop.isSupported(Desktop.Action.BROWSE)) {
//启动览?br /> ...
}
DesktopDemo把一个java.awt.event.ActionListenerd到每一个按钮上。当被启用时Q?Launch Browser"按钮通过它的ActionListener调用下列Ҏ(gu)Q?br />private void onLaunchBrowser(java.awt.event.ActionEvent evt) {
URI uri = null;
try {
uri = new URI(txtBrowserURI.getText());
desktop.browse(uri);
}
catch(IOException ioe) {
ioe.printStackTrace();
}
catch(URISyntaxException use) {
use.printStackTrace();
}
...
}
q个browse()Ҏ(gu)可能抛出各种cd的异常,q包括:当该URI为null时抛Z个NullPointerException异常Q如果不支持BROWSE行ؓ抛Z个UnsupportedOperationException异常Q如果不能发现或启动一个缺省的览器或应用E序则抛Z个IOException异常Q如果一个安全管理器否定一ơ调用则抛出一个SecurityException异常?br />
然而,如果一切顺利,那么听取器(ListenerQ将从图4中相联系的文本域中检索文本,创徏一个URIq且调用browse()Ҏ(gu)。上面的代码启动你的系l的默认览器ƈ且指C览器装载该URIQ如?所C?br />
?:使用一个特定URI启动默认览器?br />
?:使用桌面API启动默认览器?/div>
五?发送电(sh)子邮?/b>
如果支持该行为的话,该应用程序能够启动主机的默认?sh)子邮g客户?通过调用q个Desktop实例Ҏ(gu)Q?br />public void mail(URI uri) throws IOException
DesktopDemo?Launch Mail"按钮提供了一个ActionListener。在q种情况中,该听取器调用下列Ҏ(gu)Q?br />private void onLaunchMail(java.awt.event.ActionEvent evt) {
String mailTo = txtMailTo.getText();
URI uriMailTo = null;
try {
if (mailTo.length() > 0) {
uriMailTo = new URI("mailto"Q?mailToQ?null);
desktop.mail(uriMailTo);
} else {
desktop.mail();
}
}
catch(IOException ioe) {
ioe.printStackTrace();
}
catch(URISyntaxException use) {
use.printStackTrace();
}
...
}
该onLaunchMail()Ҏ(gu)从相关的文本域中索电(sh)子邮件接收者,q且在存在一位接收者时使用一Umailto模式的参数创建URIQ然后调用mail()Ҏ(gu)。这个mail()Ҏ(gu)被重载,q样你可以用(或不使用Q一个描q其mailto接收者的URIQ见?Q来调用q个Ҏ(gu)?br />
?:使用一个电(sh)子邮件接收者启动默认电(sh)子邮件客L?/div>
当创个URIӞ你可以用多个电(sh)子邮件接收者。这个mailto模式支持CCQBCCQSUBJECT和BODY域。例如,可以使用下列文本来创Z个mailto URIQ?br />
mailto:duke@sun.com?SUBJECT=Happy New Year!&BODY=Happy New YearQ?Duke!
?昄出相应的l果?br />
?Q桌面API使用多个mailto参数启动默认?sh)子邮g客户端?/div>
当然Q你也可以不使用参数来调用mail()。在q种情况中,你的?sh)子邮g客户端将启动一个新的没有指定接收者、主题或邮g正文的电(sh)子邮件窗口?
六?打开、编辑和打印文g
Java应用E序可以分别使用一个Desktop对象的open()Qedit()和print()Ҏ(gu)来从与其相联pȝ应用E序中打开Q编辑和打印文g(见图8)。同P仅在该Desktop实例支持它们ӞDesktopDemo才允许这些行为,因此在本应用E序环境下,不必再次q行q种支持查?br />
?:启动与一特定的文件类型相联系的应用程序?/div>
DesktopDemo中的每一个单选按钮也都有它自qActionListener。在q种情况中,每一个单选按钮都讄一个实例变量,以便描述最q选择的按钮的相关联Desktop.ActionQ?br />Desktop.Action action;
private void onPrintAction(java.awt.event.ActionEvent evt) {
action = Desktop.Action.PRINT;
}
private void onEditAction(java.awt.event.ActionEvent evt) {
action = Desktop.Action.EDIT;
}
private void onOpenAction(java.awt.event.ActionEvent evt) {
action = Desktop.Action.OPEN;
}
当你按下"Launch Default Application"按钮Ӟ它调用它自己的听取器-q将调用下列Ҏ(gu)Q?br />private void onLaunchDefaultApplication(java.awt.event.ActionEvent evt) {
String fileName = txtFile.getText();
File file = new File(fileName);
try {
switch(action) {
case OPEN:
desktop.open(file);
break;
case EDIT:
desktop.edit(file);
break;
case PRINT:
desktop.print(file);
break;
}
}
catch (IOException ioe) {
ioe.printStackTrace();
}
...
}
q个Ҏ(gu)军_选择哪个Desktop.Actionq且调用适当的Desktop实例Ҏ(gu)-open()Qedit()或print()。每个方法都需要一个File参数-它被用于执行要求的行为?br />
有趣的是Q不同的应用E序可以针对甚至相同的文件类型上的这些不同的行ؓq行注册。例如,可以使用OPEN行ؓ启动Firefox览器,使用EDIT行ؓ启动EmacsQ甚至用PRINT行ؓ启动另外不同的应用程序。你的主机桌面的兌用来军_应该调用什么样的应用程序?br />
注意 使用Mustang中现有桌面API来操作桌面文件关联是不可能的Q而且目前只能使用q_依赖的工h创徏或改变这些关联?br />
七?ȝ
桌面集成是Mustang的一个重要主题。Mustang支持q种主题的一U方式是提供一ljava.awt.Desktop API。这UAPI允许Java应用E序启动L的默认浏览器和电(sh)子邮件客L。另外,Java应用E序能够启动与特定的文gcd相关联的应用E序以打开Q编辑和打印文g。尽Java应用E序不能操作Q创建,或改变文件关联,但是q些桌面API定允许Java应用E序启动默认的相兌的应用程?
]]>
囄1昄了Spinner伴随不同的输入类型是什么样子。图C?的顶端的JSpinner是一个用来显C法语星期,通过SpinnerListModel。中间的Q是一个通过SpinnerDateModel昄日期的JSpinner。底部的是用SpinnerNumberModel的JSpinner。每一个都是通过各自秘的方式,在本文的后面我们要学习?br />
囄1.JSpinner实例
要创建和操纵JSpinnerQ许多类都将被调用,最重要的是JSpinner自己。最重要的两个准素集包括SpinnerModel接口Q包括可选择的集合中的选项Q还有,JSpinner.DefaultEditor的实玎ͼ用来捕获所有选择。庆q的是,许多其它调用的类都是在后台工作的Q比如,一旦你lSpinnerNumberModel提供了数字的范围Qƈ且用q个cL协助SpinnerQ你的工作实际上是完成了?br />
创徏JSpinner控g
JSpinnercd括两个构造函数来初始化控Ӟ
public JSpinner()
JSpinner spinner = new JSpinner();
public JSpinner(SpinnerModel model)
SpinnerModel model = new SpinnerListModel(args);
JSpinner spinner = new JSpinner(model);
开始的时候可以没有数据模型,后面可以使用它来跟踪JSpinner的方法。另一个方法,在创个控件的时候用完整的模型Q实现SpinnerModel接口Q它里面有三个具体的子类可以使用QSpinnerDateModelQSpinnerListModel和SpinnerNumberModelQ伴随着他们的抽象父cAbstractSpinnerModel。如果不指名模型Q那么SpinnerNumberModel默认用。而显C和~辑的控件是JFormattedTextFieldQ编辑的基本功能是通过一pdJSpinner的内部类实现的:DateEditorQListEditor和NumberFormatQ还有父cMDefaultEditor的支持?br />
JSpinner属?/b>
除了创徏JSpinner对象之外Q你q可以通过表一中的?ji)个属性中的一个来q行配置?br />
value属性中的值允怽更改当前控g的设|,nextValue和perviousValue可以使你以不同的方向察看模型中的入口?br />
使用ChangeListener来监听JSpinner events
JSpinner直接支持一U事件监听:changeListener。在别的地方Q当commitEdit()Ҏ(gu)被调用,q个事g被触发Q告诉你spinner的值发生改变。ؓ了证明,列表1联系C个自定义的ChangeListenerQ与囄1的程序相兌?br />
列表 1. JSpinner with ChangeListener
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import java.text.*;
import java.util.*;
public class SpinnerSample {
public static void main (String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("JSpinner Sample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DateFormatSymbols symbols =
new DateFormatSymbols(Locale.FRENCH);
ChangeListener listener = new ChangeListener() {
public void stateChanged(ChangeEvent e) {
System.out.println("Source: " + e.getSource());
}
};
String days[] = symbols.getWeekdays();
SpinnerModel model1 = new SpinnerListModel(days);
JSpinner spinner1 = new JSpinner(model1);
spinner1.addChangeListener(listener);
JLabel label1 = new JLabel("French Days/List");
JPanel panel1 = new JPanel(new BorderLayout());
panel1.add(label1, BorderLayout.WEST);
panel1.add(spinner1, BorderLayout.CENTER);
frame.add(panel1, BorderLayout.NORTH);
SpinnerModel model2 = new SpinnerDateModel();
JSpinner spinner2 = new JSpinner(model2);
spinner2.addChangeListener(listener);
JLabel label2 = new JLabel("Dates/Date");
JPanel panel2 = new JPanel(new BorderLayout());
panel2.add(label2, BorderLayout.WEST);
panel2.add(spinner2, BorderLayout.CENTER);
frame.add(panel2, BorderLayout.CENTER);
SpinnerModel model3 = new SpinnerNumberModel();
JSpinner spinner3 = new JSpinner(model3);
spinner3.addChangeListener(listener);
JLabel label3 = new JLabel("Numbers");
JPanel panel3 = new JPanel(new BorderLayout());
panel3.add(label3, BorderLayout.WEST);
panel3.add(spinner3, BorderLayout.CENTER);
frame.add(panel3, BorderLayout.SOUTH);
frame.setSize(200, 90);
frame.setVisible (true);
}
};
EventQueue.invokeLater(runner);
}
q行q个E序可以Clistener的用法(当然Q你也会发现更多关于ChangeListener的有意义的方法)?br />
定制JSpinner的外?/b>
同所有的Swing控gQJSpinner在不同的pȝ定义look-and-feelcd下,拥有不同的外观,如示?。这个控件期初看h像一个textfieldQ不同点是绘制了两个头?br />
C图 2. JSpinner under different look-and-feel types
集合中的11个UIResource属性在表格2中列举,有限的方法绘制text field和箭头?br />
Table 2. JSpinner UIResource 元素
SpinnerModel 接口
到目前,我们已经看到了如何同一个主JSpinnerc连接,SpinnerModel接口是控件的数据模型QSpinnerModel的定义如下:
public interface SpinnerModel {
// Properties
public Object getValue();
public void setValue(Object);
public Object getNextValue();
public Object getPreviousValue();
// Listeners
public void addChangeListener(ChangeListener);
public void removeChangeListener(ChangeListener);
}
SpinnerModel中的六个Ҏ(gu)直接l制了JSpinnerQ而JSpinner的方法间接调用模块中的方法,在监听的状况下,事g联pd监听器?br />
AbstractSpinnerModelc?/b>
AbstractSpinnerModelcd本要实现的是SpinnerModel接口Q它提供了管理和通知的监听列表,子类必须实现接口中的四个Ҏ(gu)QSpinnerModel中的三个具体实现如下QSpinnerDateModelQSpinnerListModel和SpinnerNumberModel?br />
SpinnerDateModelc?/b>
从名字可以推断出QSpinnerDateModel提供了数据的选择。这个类有两个构造函敎ͼ一个默认选择所有的数据Q另一个要求你l出范围?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);
如果不指名Q何参敎ͼ没有开始和l束炏V下面的例子展示了用参数来表示100q的范围。最后一个成员变量应该是CalendarcM的一个定|
· Calendar.AM_PM
· Calendar.DAY_OF_MONTH
· Calendar.DAY_OF_WEEK
· Calendar.DAY_OF_WEEK_IN_MONTH
· Calendar.DAY_OF_YEAR
· Calendar.ERA
· Calendar.HOUR
· Calendar.HOUR_OF_DAY
· Calendar.MILLISECOND
· Calendar.MINUTE
· Calendar.MONTH
· Calendar.SECOND
· Calendar.WEEK_OF_MONTH
· Calendar.WEEK_OF_YEAR
· Calendar.YEAR
注意QSpinnerDateModel不包含Q何CalendarcM的时间域Q所以不能通过SpinnerDateModel在JSpinner中翻转?br />
表格3列出了SpinnerModel中的三个属性,四个关于SpinnerDateModel?br />
典型圎ͼ唯一的新属性中你将要用来获得最l的日期Q尽所有的l果都被包裹在getValue()中,以适当的数据类型。如果在构造函C提供了数据的表示范围Q那么previous和next的值将是nullQ在边界条g下?br />
SpinnerListModel
SpinnerListModel提供了从一个入口列表中选择或者至是字符串表qͼq个cL三个构造函敎ͼ
public SpinnerListModel()SpinnerModel model = new SpinnerListModel();
JSpinner spinner = new JSpinner(model);
public SpinnerListModel(List<?> values)List<String> list = args;
SpinnerModel model = new SpinnerListModel(list);
JSpinner spinner = new JSpinner(model);
public SpinnerListModel(Object[] values)SpinnerModel model = new SpinnerListModel(args);
JSpinner spinner = new JSpinner(model);
当没有参数提供时Q这个模型包括一个元素:字符串empty。List版保留一个对list的引用。而不是list的拷贝。如果改变了listQ那么模型中的list也将改变。数l版本的创徏了一个私有的内部c,q且实例化一个list。对于list和数l版本,初始选择的是W一个元素,否则抛Z个IllegalArgumentException异常?br />
如表?昄Q属性中增添的是set和get list?br />
SpinnerNumberModelc?/b>
SpinnerNumberModel提供了从一个开区间或闭区间选择数字的模式,数字可以使Numbercȝ所有子c,包括Integer和Double。他有四个构造函数?br />
public SpinnerNumberModel()SpinnerModel model = new SpinnerNumberModel();
JSpinner spinner = new JSpinner(model);
public SpinnerNumberModel(double value, double minimum, double maximum,
double stepSize)SpinnerModel model = new SpinnerNumberModel(50, 0, 100, .25);
JSpinner spinner = new JSpinner(model);
public SpinnerNumberModel(int value, int minimum, int maximum, int stepSize)SpinnerModel model = new SpinnerNumberModel(50, 0, 100, 1);
JSpinner spinner = new JSpinner(model);
public SpinnerNumberModel(Number value, Comparable minimum, Comparable maximum,
Number stepSize)Number value = new Integer(50);
Number min = new Integer(0);Number max = new Integer(100);
Number step = new Integer(1);SpinnerModel model = new SpinnerNumberModel(value, min, max, step);
JSpinner spinner = new JSpinner(model);
如果最大或最gؓnullQ则为开区间。对于没有参数的Q初始gؓ1Q步qؓ1。步q是整Ş的,如果你设?333Q那么将不会完成?br />
表格5展示了SpinnerNumberModel的属性:
自定义模?/b>
一般来_可用的JSpinner模型已经_了,所以没有必要创Z的子cM。但是,q不是所有场合都能满뀂比如,你可能希望用一个包装了SpinnerListModel的模型,代替停止在第一个或最后一个元素,他包装了另一个结束。在列表2中给Z具体实现Q?br />
Listing 2. RolloverSpinnerListModel c?br />
import javax.swing.*;
import java.util.*;
public class RolloverSpinnerListModel extends SpinnerListModel {
public RolloverSpinnerListModel(List values) {
super(values);
}
public RolloverSpinnerListModel(Object[] values) {
super(values);
}
public Object getNextValue() {
Object returnValue = super.getNextValue();
if (returnValue == null) {
returnValue = getList().get(0);
}
return returnValue;
}
public Object getPreviousValue() {
Object returnValue = super.getPreviousValue();
if (returnValue == null) {
List list = getList();
returnValue = list.get(list.size() - 1);
}
return returnValue;
}}
JSpinner~辑?/b>
对于JSpinner每个可用的模型,一个次要的支持c,JSpinner的一个内部类。然而这个模块可以控制控件是否可选,JSpinner~辑器允怽控制如何昄和编辑每个可选的倹{?br />
JSpinner.DefaultEditorc?/b>
JSpinner的setEditor()Ҏ(gu)允许你将MJcomponent作ؓJSpinner的编辑器Q当然你可以那样做,更典型的是,你将用JSpinner.DefaultEditor的子cMq行。以JformattedTextField作ؓ单的~辑器工作,提供所有你需要的基本功能。它包括一个基本的构造函敎ͼ
public JSpinner.DefaultEditor(JSpinner spinner)JSpinner spinner = new JSpinner();
JComponent editor = JSpinner.DefaultEditor(spinner);
spinner.setEditor(editor);
在表?中可以看刎ͼ有两个属性:
在不知道使用的是哪个模型工作的情况下Q在q个U别的你可以做的是改变JformattedTextField中的文字昄。更典型的是Q你改变模型编辑器的某些自定义斚w?br />
JSpinner.DateEditorc?/b>
DateEditor允许你定制不同的日期昄方式Q用java.text包中SimpleDateFormatcR察看Javadoc了解更多的关于SimpleDateFormat的可用格式模式。如果你不喜Ƣ默认的地显C方式,可以通过l构造函数的W二个参C递一个新参数来改变显C模式?br />
public JSpinner.DateEditor(JSpinner spinner)SpinnerModel model = new SpinnerDateModel();
JSpinner spinner = new JSpinner(model);JComponent editor = JSpinner.DateEditor(spinner);
spinner.setEditor(editor);
public JSpinner.DateEditor(JSpinner spinner, String dateFormatPattern)SpinnerModel model = new SpinnerDateModel();
JSpinner spinner = new JSpinner(model);
JComponent editor = JSpinner.DateEditor(spinner, "MMMM yyyy");
spinner.setEditor(editor);
默认情况Q格式是M/d/yy h:mm a或?2/25/04 12:34 PM 代表2004q的圣诞节的某个旉。后面的例子要昄2004 December?br />
~辑器的两个属性在表格7中?br />
JSpinner.ListEditorc?/b>
当用SpinnerListModelcd作时QListEditor不支持Q何特D格式。而是提供了前|类型支持。既然模块的所有入口都知道了,~辑器将试匚w用户输入的字W。这里只有一个构造函敎ͼ但是你可能几乎用不到?br />
public JSpinner.ListEditor(JSpinner spinner)
在表?中将看到ListEditor只有一个属性:
JSpinner.NumberEditorc?/b>
NumberEditor和DateEditor的工作方式很怼Q允怽输入定制的显C模式。代替SimpleDateFormat工作QNumberEditor可以协助java.text包中的DecimalFormatcR就像DateEditor一P他又两个构造函敎ͼ
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);
W二个构造函C用默认的字符串格式。如果数字太大,用逗号。如果结果是一个完整的敎ͼ不会用十进制显C?br />
表格9中,昄editor的两个属性?br />
ȝ
在这文章中Q你学习CSwing中的JSpinner控g。当你要控制某些选择在一定的范围中时QJSpinner可以让你通过L来选择需要的倹{你学习C如何提供q些要选择的|通过使用SpinnerDateModel和DateEditorQSpinnerListModel和ListEditorQSpinnerNumberModel和NumberEditor来设|日期?
]]>
半透明H口是大众对Swing最为求的Ҏ(gu)之一. 也可以称之ؓ定ŞH口,q种H口有一部分是透明?可以透过它看到桌面背景和其它的程?如果不通过JNI(Java Native Interface 本地接口)Java是无法ؓ我们生成一个半透明的窗口的(即我们可以那样做,q得本地操作q_好支持半透明H口才行).然而这些现状无法阻止我们对半透明H口的?通过一个我最喜欢的手Dscreenshot,我们可以ƺ骗性地实现q个目的.
仉K这样一个的半透明H口的过E?主要的通过以下几点:
1.在窗口显CZ?先获得一个screenshot;
2.把上一步获取的屏幕快照,作ؓH口的背景图
3.调整位置,以便于我们捕Lscreenshot和实际当前的屏幕完美l合,刉出一U半透明的假?
刚刚说到的部分只是小儿科,重头戏在?如何在移动或变化半透明H口?及时地更新screenshot,也就是及时更新半透明H口的背?
在开始我们的旅行之前,先生成一个类Q让它?JPanel,我们用这个承类来捕获屏q?q把捕获的照片作? cȝ具体代码如下?-1
?6Q? ?半透明背景lg
public class TransparentBackground extends Jcomponent {
private JFrame frame;
private Image background;
public TransparentBackground(JFrame frame) {
this.frame = frame;
updateBackground( );
}
/**
* @todo 获取屏幕快照后立x新窗口背?br /> */
public void updateBackground( ) {
try {
Robot rbt = new Robot( );
Toolkit tk = Toolkit.getDefaultToolkit( );
Dimension dim = tk.getScreenSize( );
background = rbt.createScreenCapture(
new Rectangle(0,0,(int)dim.getWidth( ),
(int)dim.getHeight( )));
} catch (Exception ex) {
//p(ex.toString( ));
// 此方法没有申明过Q因为无法得知上下文。因Z影响执行效果Q先注释掉它
ex.printStackTrace( );
}
}
public void paintComponent(Graphics g) {
Point pos = this.getLocationOnScreen( );
Point offset = new Point(-pos.x,-pos.y);
g.drawImage(background,offset.x,offset.y,null);
}
}
首先,构造方法把一个reference保存到父的JFrame,然后调用updateBackground()Ҏ(gu),在这个方法中,我们可以利用java.awt.RobotcL获到整个屏幕,q把捕获到的囑փ保存C个定义了的放|背景的变量? paintComponent()Ҏ(gu)可以帮助我们获得H口在屏q上的绝对位|?q用刚刚得到的背景作为panel的背景图,同时q个背景图会因ؓpanel位置的不同而作对应的移?以panel的背景和panel覆盖的那部分屏幕囑փ无缝重叠在一?同时也就使panel和周围的屏幕兌h.
我们可以通过下面q个mainҎ(gu)单的q行一?随便攄一些组件到panel?再把panel攄到frame中显C?
public static void main(String[] args) {
JFrame frame = new JFrame("Transparent Window");
TransparentBackground bg = new TransparentBackground(frame);
bg.setLayout(new BorderLayout( ));
JButton button = new JButton("This is a button");
bg.add("North",button);
JLabel label = new JLabel("This is a label");
bg.add("South",label);
frame.getContentPane( ).add("Center",bg);
frame.pack( );
frame.setSize(150,100);
frame.show( );
}
通过q段代码,q行出的效果如下?Q?所C:
?-1 展示中的半透明H口
q段代码相当单,却带有两个不之处。首先,如果UdH口Qpanel中的背景无法自动的更斎ͼ而paintComponentQ)只在改变H口大小时被调用Q其ơ,如果屏幕曄发生q变化,那么我们制作的窗口将永远无法和和屏幕背景联合成整体?br />
谁也不想时不时地跑去更新screenshotQ想想看Q要扑ֈ隐藏于窗口后的东西,要获得一份新的screenshotQ还要时不时的用q些screenshot来更新我们的半透明H口Q这些事情以让用户无法安心工作。事实上Q想要获取窗口之外的屏幕的变化几乎是不太可能的事Q但多数变动都是发生在foregroundH口发生焦点变化或被Ud之时。如果你接受q的观点Q至我接受q个观点Q?那么你可以只监控下面提到的几个事Ӟq只需在这几个事g被触发时Q去更新screenshot?br />public class TransparentBackground extends JComponent
implements ComponentListener, WindowFocusListener,
Runnable {
private JFrame frame;
private Image background;
private long lastupdate = 0;
public boolean refreshRequested = true;
public TransparentBackground(JFrame frame) {
this.frame = frame;
updateBackground( );
frame.addComponentListener(this);
frame.addWindowFocusListener(this);
new Thread(this).start( );
}
public void componentShown(ComponentEvent evt) { repaint( ); }
public void componentResized(ComponentEvent evt) { repaint( ); }
public void componentMoved(ComponentEvent evt) { repaint( ); }
public void componentHidden(ComponentEvent evt) { }
public void windowGainedFocus(WindowEvent evt) { refresh( ); }
public void windowLostFocus(WindowEvent evt) { refresh( ); }
首先Q让我们的半透明H口即panel实现ComponentListener接口,
WindowFocusListener接口和Runnable接口。Listener接口可以帮助我们捕获到窗口的UdQ大变化,和焦点变化。实现Runnable接口可以使得panel生成一个线E去控制定制的repaint()Ҏ(gu)?br />
ComponentListener接口带有四个component开头的Ҏ(gu)。它们都可以很方便地调用repaint()Ҏ(gu)Q所以窗口的背景也就可以随着H口的移动,大小的变化而相应地更新。还有两个是焦点处理的,它们只调用refresh(),如下C意Q?br />public void refresh( ) {
if(frame.isVisible( )) {
repaint( );
refreshRequested = true;
lastupdate = new Date( ).getTime( );
}
}
public void run( ) {
try {
while(true) {
Thread.sleep(250);
long now = new Date( ).getTime( );
if(refreshRequested &&
((now - lastupdate) > 1000)) {
if(frame.isVisible( )) {
Point location = frame.getLocation( );
frame.hide( );
updateBackground( );
frame.show( );
frame.setLocation(location);
refresh( );
}
lastupdate = now;
refreshRequested = false;
}
}
} catch (Exception ex) {
p(ex.toString( ));
ex.printStackTrace( );
}
}
refresh()可以保证frame可见Qƈ适时得调用repaint()。它也会对refreshRequest变量|真(true)Q同时保存当前时间|现在所做的q些Ҏ(gu)下来要做的事是非帔R要的铺垫?br />
除了每四分之一U被唤醒一ơ,用来是否有新的h的要求或者是否离上次h旉过了一U,Ҏ(gu)run()一般地处于休眠状态。如果离上次h过了一Uƈ且frame是可见的Q那么run()保存frame的位|,隐藏frameQ获取一个screenshotQ更新frame背景Q再Ҏ(gu)隐藏frame时保存的位置信息Q重新显C已l更C背景的frameQ接着调用refreshQ)Ҏ(gu)。通过q样的控Ӟ使得背景更新不至于比需要的多太多?br />
那么我们Z么要对用一个线E控制刷新如此长大论呢Q一个词Q递归。事件处理可以直接轻村֜调用repaint()Q但是隐藏和昄H口已便于获取screenshot 却交替了很多“得焦”和“失焦”事件。所有这些都会触发一个新的背景更斎ͼDH口再次被隐藏,如此往q,导致永无止境的循环。一个新的“得焦”事Ӟ在执行refresh()几毫U之后被调用Q所以简单地isRecursing标志是无法阻止@环的l箋?br />
另外Q用户Q意一个改变屏q的动作Q将会随之引Z堆的事g来,而不仅仅是简单一个。应该是最后一个事件去触发updateBackground(),而不是第一个。ؓ了全面解册些问题,代码产生一个线E,然后用这个线E去监控重画QrepaintQ要求,q保证当前的执行动作是发生在q去?000毫秒内没有发生过此动作。如果一个客h五秒不间断地产生事gQ比如,L丢失的浏览窗口)Q那么只有在其它所有工作在一U内完成才执行更新。这样就避免了,用户不至于在Ud东西ӞH口却消׃见了的尴?br />
另一件烦恼的事就是,我们的窗口仍旧有Ҏ(gu)Q这条边框得我们无法完和背景融ؓ一体。更为痛苦的是用setUndecorated(true)U除Ҏ(gu)Ӟ我们的标题栏和窗口控制栏也跟着U除了。可是这也算不上是什么大问题Q因为那cM用定形窗口的应用E序一般都h可拖动的背景【HackQ?4?br />
接下来,我们在下面这个简单的试E序中把所讲的东西落实q去Q?br />public static void main(String[] args) {
JFrame frame = new JFrame("Transparent Window");
frame.setUndecorated(true);
TransparentBackground bg = new TransparentBackground(frame);
bg.snapBackground( );
bg.setLayout(new BorderLayout( ));
JPanel panel = new JPanel( ) {
public void paintComponent(Graphics g) {
g.setColor(Color.blue);
Image img = new ImageIcon("mp3.png").getImage( );
g.drawImage(img,0,0,null);
}
};
panel.setOpaque(false);
bg.add("Center",panel);
frame.getContentPane( ).add("Center",bg);
frame.pack( );
frame.setSize(200,200);
frame.setLocation(500,500);
frame.show( );
}
q段代码通过l承JPanelQ加上一个透明的PNG格式囄Qh工生成一个mp3播放器界面。注意用了setUndecoratedQ)来隐藏边框和标题栏。调用setOpaque(false),隐藏默认的背景Q一般ؓ灰色Q,q样screenshot的背景就可以和图片中透明的部分合成一个整体,去配合程序窗口周围的屏幕背景。(如图6-2Q通过一pd的努力,可以看到图6Q?的效果。是不是很让人惊诧?会不会感叹Java的新版本腄ZQ?br />
?6Q?. mp3 播放器外观模?br />
?6-3. q行中的mp3播放?br />