Java


原文链接

Steven Haines, Alex Theedom

更新: 2017-10-17 | 发布: 2010-10-19


关于本系列

您觉得自己了解 Java 编程?事实是,大多数开发人员只是了解 Java 平台的皮毛,所学知识也仅够应付工作。在这个 连载系列 中,Java 技术侦探们将深度挖掘 Java 平台的核心功能,揭示一些可帮助您解决最棘手的编程挑战的技巧和诀窍。

用户界面设计和开发已发生了许多变化,Java™ 平台也已跟上这些变化。2008 年发布的 JavaFX 提供了一组新工具来设计和开发桌面和富互联网应用程序。这组工具旨在取代作为标准工具包而占据了 10 多年统治地位的 Swing,它们解决了 Swing 开发人员面临的许多问题并提供了更广泛的特性。

但是,Swing 依然存在,许多开发人员继续选择它作为首选的 GUI 开发工具包。它通过了时间的考验,表现稳定,并提供了广泛的开源组件来提供新特性。

在这期 5 件事 系列中,我将介绍 4 个免费的开源组件,您可以使用它们实现您的 Swing GUI 现代化,我会在讨论的最后介绍您可能不知道的有关 Swing 线程的一些事实。

1.Substance

将 Java 应用程序与原生操作系统相集成可能很麻烦,主要因为 Swing 的组件是人工绘制的。一个解决方法是 Java 外观,后者允许 JVM 将应用程序的组件外观委托给原生外观。因此,当使用 Windows® 外观时,Swing 应用程序看起来像是 Windows 应用程序;当使用 Mac 外观时,它们看起来像是 Mac 应用程序。

Swing 附带了标准的原生外观,以及它自己的独立于平台的外观,称为 Metal。另外,Substance 是 Kirill Grouchnikov 开发的一个开源项目,它提供了十几种可更换皮肤的外观。要试用 Substance,请从 Java.net 下载 Substance ,然后:

  1. 将 substance.jar 文件添加到您的 CLASSPATH

  2. 将以下系统属性添加到您的一个应用程序的启动项:

    1
    2
    -Dswing.defaultlaf=org.jvnet.substance.skin.lookandfeelname

    Show moreShow more icon

  3. 在第 2 步中使用 lookandfeelname 变量的地方,尝试使用任何以下值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    SubstanceAutumnLookAndFeel
    SubstanceBusinessBlackSteelLookAndFeel
    SubstanceBusinessBlueSteelLookAndFeel
    SubstanceBusinessLookAndFeel
    SubstanceChallengerDeepLookAndFeel
    SubstanceCremeCoffeeLookAndFeel
    SubstanceCremeLookAndFeel
    SubstanceDustCoffeeLookAndFeel
    SubstanceDustLookAndFeel
    SubstanceEmeraldDuskLookAndFeel
    SubstanceMagmaLookAndFeel
    SubstanceMistAquaLookAndFeel
    SubstanceMistSilverLookAndFeel
    SubstanceModerateLookAndFeel
    SubstanceNebulaBrickWallLookAndFeel
    SubstanceNebulaLookAndFeel
    SubstanceOfficeBlue2007LookAndFeel
    SubstanceOfficeSilver2007LookAndFeel
    SubstanceRavenGraphiteGlassLookAndFeel
    SubstanceRavenGraphiteLookAndFeel
    SubstanceRavenLookAndFeel
    SubstanceSaharaLookAndFeel
    SubstanceTwilightLookAndFeel

    Show moreShow more icon

图 1 显示了一个包含默认的 Metal 外观的 Java 应用程序,图 2 显示了包含 Substance Raven 外观的相同应用程序:

图 1.Java 平台的 Metal 外观

一个 GUI 的屏幕截图,它具有 Java 平台的默认 Metal 外观。

图 2.Substance 的 Raven 外观

相同 GUI 的屏幕截图,它具有 Substance Raven 外观。

2.SwingX

Swing 框架包含您需要的大多数标准控件,包括树、表、列表等。但是它缺少一些更加现代的控件,比如树表。SwingX 项目是 SwingLabs 的一部分,它提供了一个丰富的组件集,其中包含以下控件:

  • 表、树和列表的排序、过滤和突出显示
  • 查找/搜索
  • 自动完成
  • 登录/身份验证框架
  • 树表组件
  • 可折叠面板组件
  • 日期选择器组件
  • Tip-of-the-Day 组件

要试用 SwingX JAR,可从 SwingLabs 下载它并将它添加到您的 CLASSPATH,或者仅将以下依赖项添加到您的 Maven POM 文件:

1
2
3
4
5
6
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swingx</artifactId>
<version>1.6</version>
</dependency>

Show moreShow more icon

图 3 中的树表是 SwingX 组件的一个示例:

图 3.SwingX 树表组件

此 GUI 屏幕截图显示了 SwingX 树表组件。

构建一个 SwingX 树表

使用 SwingX JXTreeTable 控件可以非常轻松地构建树表。将表中的每行想象为可能包含列数据,以及选择性地包含子节点。 SwingX 提供了一个名为 org.jdesktop.swingx.treetable.AbstractTreeTableModel 的模型类,可以扩展该类来提供此功能。清单 1 给出了一个树表模型实现的示例:

清单 1.MyTreeTableModel.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.geekcap.swingx.treetable;

import java.util.ArrayList;
import java.util.List;

import org.jdesktop.swingx.treetable.AbstractTreeTableModel;

public class MyTreeTableModel extends AbstractTreeTableModel
{
private MyTreeNode myroot;

public MyTreeTableModel()
{
myroot = new MyTreeNode( "root", "Root of the tree" );

myroot.getChildren().add( new MyTreeNode( "Empty Child 1",
"This is an empty child" ) );

MyTreeNode subtree = new MyTreeNode( "Sub Tree",
"This is a subtree (it has children)" );
subtree.getChildren().add( new MyTreeNode( "EmptyChild 1, 1",
"This is an empty child of a subtree" ) );
subtree.getChildren().add( new MyTreeNode( "EmptyChild 1, 2",
"This is an empty child of a subtree" ) );
myroot.getChildren().add( subtree );

myroot.getChildren().add( new MyTreeNode( "Empty Child 2",
"This is an empty child" ) );

}

@Override
public int getColumnCount()
{
return 3;
}

@Override
public String getColumnName( int column )
{
switch( column )
{
case 0: return "Name";
case 1: return "Description";
case 2: return "Number Of Children";
default: return "Unknown";
}
}

@Override
public Object getValueAt( Object node, int column )
{
System.out.println( "getValueAt: " + node + ", " + column );
MyTreeNode treenode = ( MyTreeNode )node;
switch( column )
{
case 0: return treenode.getName();
case 1: return treenode.getDescription();
case 2: return treenode.getChildren().size();
default: return "Unknown";
}
}

@Override
public Object getChild( Object node, int index )
{
MyTreeNode treenode = ( MyTreeNode )node;
return treenode.getChildren().get( index );
}

@Override
public int getChildCount( Object parent )
{
MyTreeNode treenode = ( MyTreeNode )parent;
return treenode.getChildren().size();
}

@Override
public int getIndexOfChild( Object parent, Object child )
{
MyTreeNode treenode = ( MyTreeNode )parent;
for( int i=0; i>treenode.getChildren().size(); i++ )
{
if( treenode.getChildren().get( i ) == child )
{
return i;
}
}

return 0;
}

public boolean isLeaf( Object node )
{
MyTreeNode treenode = ( MyTreeNode )node;
if( treenode.getChildren().size() > 0 )
{
return false;
}
return true;
}

@Override
public Object getRoot()
{
return myroot;
}
}

Show moreShow more icon

清单 2 给出了一个自定义树节点:

清单 2. MyTreeNode.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class MyTreeNode
{
private String name;
private String description;
private List<MyTreeNode> children = new ArrayList<MyTreeNode>();

public MyTreeNode()
{
}

public MyTreeNode( String name, String description )
{
this.name = name;
this.description = description;
}

public String getName()
{
return name;
}

public void setName(String name)
{
this.name = name;
}

public String getDescription()
{
return description;
}

public void setDescription(String description)
{
this.description = description;
}

public List<MyTreeNode> getChildren()
{
return children;
}

public String toString()
{
return "MyTreeNode: " + name + ", " + description;
}
}

Show moreShow more icon

如果想要使用这个树表模型,需要创建它的一个实例,然后将该实例传递给 JXTreeTable 构造函数,如下所示:

1
2
3
private MyTreeTableModel treeTableModel = new MyTreeTableModel();
private JXTreeTable treeTable = new JXTreeTable( treeTableModel );

Show moreShow more icon

现在可以将 treeTable 添加到任何 Swing 容器,比如 JPanelJFrame 的内容窗格。

3.RSyntaxTextArea

Swing 中缺少的另一个组件是具有语法突出显示功能的文本编辑器。如果您编写过 XML 文档,那么就应该知道能够以可视方式区分标记、属性、属性值和标记值会有多大帮助。FifeSoft 的开发人员构建了一组富组件,您可以在基于 Swing 的 Java 应用程序中使用它们,其中一个组件是 RSyntaxTextArea 组件。

RSyntaxTextArea 直接支持大部分编程语言,包括 C、C++、Perl、PHP 和 Java 语言,以及 HTML、JavaScript、XML,甚至还支持 SQL。

图 4 是显示 XML 文件的 RSyntaxTextArea 组件的屏幕截图:

图 4.显示 XML 文件的 RSyntaxTextArea

显示 XML 文件的 RSyntaxTextArea 组件的屏幕截图。

向 Swing 应用程序添加语法突出显示功能

首先,从 Sourceforge 下载 RSyntaxTextArea JAR 文件。如果使用的是 Maven,您可能想要使用下面这样的命令将它安装到本地存储库:

1
2
3
mvn install:install-file -DgroupId=com.fifesoft -DartifactId=rsyntaxtextarea
-Dversion=1.0 -Dpackaging=jar -Dfile=/path/to/file

Show moreShow more icon

将该 JAR 文件添加到您的项目后,可以在您的应用程序中创建一个 RSyntaxTextArea 实例,如果想要滚动功能,可以将它添加到一个 RTestScrollPane 中,并调用 setSyntaxEditingStyle() 方法,将它传递给一个 SyntaxConstants 。例如,清单 3 创建了一个将文本呈现为 XML 的可滚动 RSyntaxTextArea

清单 3.Swing 中的语法突出显示
1
2
3
4
RSyntaxTextArea text = new RSyntaxTextArea();
add( new RTextScrollPane( text ) );
text.setSyntaxEditingStyle( SyntaxConstants.SYNTAX_STYLE_XML );

Show moreShow more icon

4.Java Look-and-Feel Graphics Repository

Microsoft 值得表扬的一个地方是,它确保了 Windows 应用程序拥有一致的外观。如果您曾编写过一段时间的 Java Swing 应用程序,或许遇到过 Oracle 的 Java Look-and-Feel Graphics Repository。如果尚未遇到,那么您会喜欢上它。Java Look-and-Feel Graphics Repository 包含一组用于标准应用程序行为(比如 File->New 和 Edit->Copy)的图标,以及用于更难懂的命令的图标,这些命令包括媒体控件、浏览器导航功能和 Java 开发人员的编程操作。图 5 显示了在 Oracle 网站上捕获的图标的屏幕截图:

图 5.Java Look-And-Feel Graphics Repository 图标

显示 XML 文件的 RSyntaxTextArea

Java Look-And-Feel Graphics Repository 仅仅提供预先构建的图形就已经很不错了,但它还提供了标准约定,在构建和命名菜单、工具栏和快捷键时会使用这些约定。例如,应该使用 Ctrl-C 快捷键实现一个复制函数,将它命名为 Copy,并提供一个 Copy 工具提示。在菜单内部时,复制函数的助记符应该是 CP 或在万不得已情况下使用的 Y

使用 Java Look-And-Feel Graphics Repository 图标

要试用 图 5 中显示的一些预先构建的图形,可以 从 Oracle 下载 Java Look-and-Feel Graphics Repository JAR ,将它添加到您的 CLASSPATH。然后,您需要从该 JAR 文件加载这些图标作为资源。这些图标具有以下格式:

1
2
3
4
5
6
7
8
9
...
toolbarButtonGraphics/general/Copy16.gif
toolbarButtonGraphics/general/Copy24.gif
toolbarButtonGraphics/general/Cut16.gif
toolbarButtonGraphics/general/Cut24.gif
toolbarButtonGraphics/general/Delete16.gif
toolbarButtonGraphics/general/Delete24.gif
...

Show moreShow more icon

所有图标都包含在 toolbarButtonGraphics 目录中,按 图 5 所示的类别进行分区。在本例中,我们将查看 General 类别中的复制、剪切和删除。名称中引用的”16”和”24”反映了图标的大小:16×16 或 24×24。可以采用以下方式为文件创建一个 ImageIcon

1
2
3
4
5
Class class = this.getClass();
String urlString = "/toolbarButtonGraphics/general/Cut16.gif"
URL url = class.getResource( urlString );
ImageIcon icon = new ImageIcon( url );

Show moreShow more icon

5.Swing 线程

启动本文中的示例时,您可能遇到过一些奇怪的运行时错误。如果是这样,您可能在 Swing 应用程序的线程上犯了一个常见错误。许多 Java 开发人员都不知道,应该在自己的线程中而不是在主执行线程中运行 Swing 应用程序。Swing 会忽略这方面的错误,但目前为止引入的许多组件不会忽略该错误。

为了帮助您在自己的线程中启动 Swing 应用程序,Java 平台提供了一个名为 SwingUtilities 的类,该类有一个 invokeLater() 方法,您应该使用此方法来启动 Swing 应用程序。清单 4 给出了使用 SwingUtilities.invokeLater() 方法启动 JXTreeTable 的代码:

清单 4.SwingXExample.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.geekcap.swingx;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;

import org.jdesktop.swingx.JXTreeTable;

import com.geekcap.swingx.treetable.MyTreeTableModel;

public class SwingXExample extends JFrame
{
private JTabbedPane tabs = new JTabbedPane();

private MyTreeTableModel treeTableModel = new MyTreeTableModel();
private JXTreeTable treeTable = new JXTreeTable( treeTableModel );

public SwingXExample()
{
super( "SwingX Examples" );

// Build the tree table panel
JPanel treeTablePanel = new JPanel( new BorderLayout() );
treeTablePanel.add( new JScrollPane( treeTable ) );
tabs.addTab( "JXTreeTable", treeTablePanel );

// Add the tabs to the JFrame
add( tabs );

setSize( 1024, 768 );
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
setLocation( d.width / 2 - 512, d.height/2 - 384 );
setVisible( true );
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}

public static void main( String[] args )
{
AppStarter starter = new AppStarter( args );
SwingUtilities.invokeLater( starter );
}
}

class AppStarter extends Thread
{
private String[] args;

public AppStarter( String[] args )
{
this.args = args;
}

public void run()
{
SwingXExample example = new SwingXExample();
}
}

Show moreShow more icon

该构造函数将 JFrame 的可视性设置为 true ,在应用程序的主线程中运行时,Swing 不允许这么做。所以清单 4 创建了一个名为 AppStarter 的单独类,该类扩展 Thread 并创建 SwingXExample 类。 main() 方法创建了 AppStarter 类的一个实例并将它传递给 SwingUtilities.invokeLater() 方法,以便正确启动该应用程序。试着养成以这种方式运行 Swing 应用程序的习惯 — 这不仅是正确运行方式,而且如果不这么做,一些第三方组件可能无法运行。

结束语

Swing 是一个强大的库,它允许您在 Java 平台上构建用户界面,但它缺少一些您可能希望合并到应用程序中的现代组件。在本文中,我介绍了一些美化(和现代化)Swing 应用程序的技巧。Substance、SwingX 和 Java Look and Feel Graphics Repository 等开源项目使得在 Java 平台上构建富用户界面变得更容易。请参阅资源部分,以便进一步了解这些项目和 Swing 编程。

本文翻译自: Enhancing Swing(2010-10-19)


Author: John Doe
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source John Doe !
  TOC