1 改变Swing应用程序的默认字体/字号
经常使用Swing作为程序UI的人可能会注意到,Swing组件默认显示文字的字号为11。这对于英文显示毫无问题,但是如果用这个字号显示中文的话,这么小的字号就会使程序变得很难看。我当年在用IReport0.56的时候就发现他的菜单栏和弹出的Dialog里的字很难看,但是将字号调大之后就好多了。虽然在最近版本的JDK里似乎修正了这个字体问题,但是如果你的程序必须使用以前版本的JDK的话,这个问题就需要处理一下,下面就是一个不错的解决方案:
Font vFont =new Font("Dialog", Font.PLAIN, 13);
UIManager.put("ToolTip.font", vFont);
UIManager.put("Table.font", vFont);
UIManager.put("TableHeader.font", vFont);
UIManager.put("TextField.font", vFont);
UIManager.put("ComboBox.font", vFont);
UIManager.put("TextField.font", vFont);
UIManager.put("PasswordField.font", vFont);
UIManager.put("TextArea.font", vFont);
UIManager.put("TextPane.font", vFont);
UIManager.put("EditorPane.font", vFont);
UIManager.put("FormattedTextField.font", vFont);
UIManager.put("Button.font", vFont);
UIManager.put("CheckBox.font", vFont);
UIManager.put("RadioButton.font", vFont);
UIManager.put("ToggleButton.font", vFont);
UIManager.put("ProgressBar.font", vFont);
UIManager.put("DesktopIcon.font", vFont);
UIManager.put("TitledBorder.font", vFont);
UIManager.put("Label.font", vFont);
UIManager.put("List.font", vFont);
UIManager.put("TabbedPane.font", vFont);
UIManager.put("MenuBar.font", vFont);
UIManager.put("Menu.font", vFont);
UIManager.put("MenuItem.font", vFont);
UIManager.put("PopupMenu.font", vFont);
UIManager.put("CheckBoxMenuItem.font", vFont);
UIManager.put("RadioButtonMenuItem.font", vFont);
UIManager.put("Spinner.font", vFont);
UIManager.put("Tree.font", vFont);
UIManager.put("ToolBar.font", vFont);
UIManager.put("OptionPane.messageFont", vFont);
UIManager.put("OptionPane.buttonFont", vFont);
这段代码用在程序的开始部分,可以有效地将Swing组件的显示字体设置为我们在vFont所设定的内容。
1.1 让窗口更好地居中显示
无论是顶层组件JFrame还是对话框JDialog,让他们的窗口居中显示是一个很常见的问题,因为他们默认总是从左上角弹出来,这也太不爽了!对于这个问题,JBuilder应用程序生成向导给出了解决方案:
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
if (frameSize.height > screenSize.height)
frameSize.height = screenSize.height;
if (frameSize.width > screenSize.width)
frameSize.width = screenSize.width;
frame.setLocation((screenSize.width-frameSize.width)/2,screenSize.height-frameSize.height) / 2);
这个方法对于大多数窗口组件来说都足够了,但是还有其他问题存在,比如说分辨率和显示器的尺寸都会导致应用程序窗口“变形”,明明在17寸显示器1024*768分辨率下显示好好的窗口到了19寸的1280*800的宽屏下就会被“拉”得很“长”。于是,虽然有布局管理器帮我们管理拉伸后组件的放置,但仍然解决不了拉长后带来的美观问题。我的经验是,对于某些窗口,由于它被“拉长”之后由于其内部组件之间的间隙变大,会显得很难看。所以应该为他们设定一个最合适的显示大小。在居中显示的时候只调整位置而不改变大小,这样就不会影响窗口的美观。所以我们只需要对上面的代码小改一下即可,以JFrame为例:
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
screenSize = Toolkit.getDefaultToolkit().getScreenSize();
frame.setPreferredSize(new Dimension(512,450));
int frameWidth = this.getPreferredSize().width;
int frameHeight = this.getPreferredSize().height;
frame.setSize(frameWidth, frameHeight);
frame.setLocation((screenSize.width - frameWidth) / 2,(screenSize.height - frameHeight) / 2);
2 自定义JFrame的关闭事件
有的时候,当用户关闭应用程序窗口的时候,我们可能希望程序在结束之前保存一些必要的数据。对于这种需求,我们有两种备选方案:
2.1 获取程序关闭的“钩子”
Runtime.getRuntime().addShutdownHook(shutdownHook);
protected Thread shutdownHook = new PlatformShutdownHook();
protected class PlatformShutdownHook extends Thread {
public void run()
{
//一些清理工作在这里进行……
}
}
通过这种方法,我们就可以在程序结束时获得通知,以便进行一些保存或清理的工作。然而这种方法的缺点是,在程序收到结束通知的时候,所有的UI组件已经被销毁了,用户此时看到的是程序已经结束。而事实上如果程序保存需要花很长的时间的话,用户是不能获取任何信息的,这是一个很糟糕的用户体验。因为如果这时用户关机的话,程序就有可能丢失尚未保存的信息,而对于这一切,用户并不知情。
2.2 处理JFrame关闭事件
为了在UI被销毁之前收到程序结束的消息,我们需要自行处理窗口关闭的事件。注意在这里我们没有采用addActionListener(……)方法,因为这样做只能让我们在窗口关闭之后收到通知,这样就与上面的方法没什么区别了。
我们需要在JFrame的构造函数中设置:
//设定标志,让MainFrame自己接收窗口事件
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
然后再实现下面的函数:
protected void processWindowEvent(final WindowEvent pEvent) {
if (pEvent.getID() == WindowEvent.WINDOW_CLOSING) {
/** 防止用户多次点击“关闭”按钮造成重复保存 **/
if( !isClosing )isClosing =true;
else return;
//处理JFrame关闭事件……
}else{
//忽略其他事件,交给JFrame处理
super.processWindowEvent(pEvent);
}
}
如此一来,我们就可以在窗口被关闭之前通知用户程序正在保存数据的信息,例如后面提到的InfiniteProgressPanel可以显示的内容。
3 日期选择组件与JDialog的冲突问题
由于很多应用程序都需要用户输入日期,却又怕用户输入的日期格式错误,所以日期选择组件便应运而生。虽然我们很需要它,但是网上绝大多数的组件都是需要给钱的。在找到SwingX之前,我找到的唯一能够免费使用的日历组件就是一个名为DateChooser的JDialog:
看样子很不错,它支持中文,对于今天高亮显示,可以调整年分和月份……一切都非常符合要求。但是这么好的组件却不能用在我的程序里,原因是在我的程序中,调用这个组件的组件也是一个JDialog,并且设置了setAlwaysOnTop(true)—即总在最前端显示。由于DateChooser也设定了在最前端显示,这就导致了它和其父组件的显示冲突,最终结果是DateChooser不能正常显示。对于这个问题,我最终使用SwingX的组件DatePicker来代替DateChooser完成选择日期的使命,惯于DatePicker的使用我将来会在“SwingX使用详解”中提到,这里就不再细说。但是这个问题仍然值得我们注意,即如果一个窗口组件是设置了总在最前端显示的JDialog,那么就不要以这个JDialog为父组件来弹出其他JDialog,以避免冲突的发生。
4 JTable的实用技巧
无论对于什么样的一个应用程序来说,用表格的形式来显示数据是再平常不过的事情了。于是JTable就成为我们在所有Swing组件中最不可或缺的朋友。对于JTable的操作,大多数情况下我们都可以不假外求,因为JDK自带的例子SwingSet2给我们展示了足够多的功能。
在这个例子里,我们可以改变单元格的间距,行高,选择类型(Selection Style),是否显示水平线,甚至可以将表格内容打印出来。其中,表格除了文字之外还可以包含其他组件和内容,如SwingSet2种就加入了可以选择颜色的JComboBox和喜爱的食物所代表的图片。
但有些时候,我们还会有一些其他的需求。例如说为了保护我们的眼睛,我们希望表格的内容是带有间隔色的,如奇数行显示蓝色,而偶数行显示白色。又或者我们希望表格中某些列的内容是可编辑的,而且他列的内容是不可编辑的。又或者让表格中的列带有排序的功能,能让我们点一下表头它就自己按照从低到高或从高到低的顺序自行排列。最后我们希望表格的表头和单元格力的内容能够居中显示。让我们一个一个来实现这些功能!
4.1 间隔色表格及单元格/表头居中显示
JTable的API并没有为我们提供更改表格行或列的颜色的能力。但是我们知道,表格的表头和内容的呈现形式都是由相应的Renderer来控制的,所以我们只需要继承单元格默认的Renderer并作相应的修改就可以达到目的:
由于实现了接口TableCellRenderer,我们只需要实现唯一的函数getTableCellRendererComponent(…)。在上例中我们看到,在函数中我们判断当前行是奇数还是偶数,如果是奇数,就设置其背景色为淡蓝色,否则就设其背景色为白色。在每次更新表格内容的时候,我们只需要调用下面的函数,就可以保证表格在内容被更改之后依然正确显示间隔色。
/** 为所有表格设置间隔色 **/
private void setRenderColor(){
for( int i = 0; i < table.getColumnModel().getColumnCount(); i++ ) table.getColumn( colname[i] ).setCellRenderer(colorRender );
}
另外,如果我们想要让单元格中的内容居中显示的话,请注意到在设置间隔色部分下面的函数,通过setHorizontalAlignment(SwingConstants.CENTER)我们就可以让单元格内容居中显示。
虽然JTable表格的表头在默认情况下应该是居中显示的,但不知道为什么,在我的应用程序中表格的表头总是左对齐显示,这让我恼火不已。由于和单元格一样,表头的各项显示指标也是由其Renderer控制的,所以只需要设置一下表头的Renderer就能达到目的:
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer) table.getTableHeader().getDefaultRenderer();
renderer.setHorizontalAlignment(renderer.CENTER);
利用这种方法,如果我们需要让他右对齐似乎也不是什么难事,对吗?