【IT168 技术】用户界面设计和开发近年来有了一些改变,有人可能会说 Java 平台并不能保持。1997 年发布的 Swing 至今仍然一个标准的工具包,可以在 JVM 上构建用户界面。好的一点是,它是一个较为熟悉的标准,易于操作。不好的是,它缺乏将普通 UI 设计变丰富的特性。
▲图1 Java 平台的 Metal 外观和效果
▲图2 Substance 的 Raven 外观和效果
▲图3 SwingX TreeTable 组件
▲图4 RSyntaxTextArea 显示一个 XML 文件
▲图5 Java Look-And-Feel Graphics Repository 图标
在本期的 5 件事 系列 文章中,我将向您介绍 4 个免费开源组件,您可以用来现代化您的 Swing GUI,并围绕那些您可能还不可了解的 Swing 线程展开讨论。
1. Substance
将一个 Java 应用程序和一个本地
操作系统整 合起来是比较麻烦的,主要是因为 Swing 的组件是人工绘制的。可以改变的就是 Java 外观和效果,允许 JVM 将应用程序的组件显示委托给本地外观和效果。因此,当使用 Windows® 外观和效果时,Swing 应用程序看起来像 Windows 应用程序;当使用 Mac 外观和效果时,它们看起来 Mac 应用程序。
Swing 附带的标准、本地外观和效果,以及它自己的平台独立感觉和效果,称为 Metal。另外,Substance 是一个开源项目,Kirill Grouchnikov 开发的,提供十几种换肤外观和效果。想要尝试, 从 Java.net 下载 Substance:
将 substance.jar 文件添加到您的 CLASSPATH。
将以下系统属性添加到您应用程序的启动项:
-
Dswing.defaultlaf
=
org.jvnet.substance.skin.lookandfeelname
在第 2 步中替换 lookandfeelname 变量,尝试下列值:
SubstanceAutumnLookAndFeel
SubstanceBusinessBlackSteelLookAndFeel
SubstanceBusinessBlueSteelLookAndFeel
SubstanceBusinessLookAndFeel
SubstanceChallengerDeepLookAndFeel
SubstanceCremeCoffeeLookAndFeel
SubstanceCremeLookAndFeel
SubstanceDustCoffeeLookAndFeel
SubstanceDustLookAndFeel
SubstanceEmeraldDuskLookAndFeel
SubstanceMagmaLookAndFeel
SubstanceMistAquaLookAndFeel
SubstanceMistSilverLookAndFeel
SubstanceModerateLookAndFeel
SubstanceNebulaBrickWallLookAndFeel
SubstanceNebulaLookAndFeel
SubstanceOfficeBlue2007LookAndFeel
SubstanceOfficeSilver2007LookAndFeel
SubstanceRavenGraphiteGlassLookAndFeel
SubstanceRavenGraphiteLookAndFeel
SubstanceRavenLookAndFeel
SubstanceSaharaLookAndFeel
SubstanceTwilightLookAndFeel
SubstanceBusinessBlackSteelLookAndFeel
SubstanceBusinessBlueSteelLookAndFeel
SubstanceBusinessLookAndFeel
SubstanceChallengerDeepLookAndFeel
SubstanceCremeCoffeeLookAndFeel
SubstanceCremeLookAndFeel
SubstanceDustCoffeeLookAndFeel
SubstanceDustLookAndFeel
SubstanceEmeraldDuskLookAndFeel
SubstanceMagmaLookAndFeel
SubstanceMistAquaLookAndFeel
SubstanceMistSilverLookAndFeel
SubstanceModerateLookAndFeel
SubstanceNebulaBrickWallLookAndFeel
SubstanceNebulaLookAndFeel
SubstanceOfficeBlue2007LookAndFeel
SubstanceOfficeSilver2007LookAndFeel
SubstanceRavenGraphiteGlassLookAndFeel
SubstanceRavenGraphiteLookAndFeel
SubstanceRavenLookAndFeel
SubstanceSaharaLookAndFeel
SubstanceTwilightLookAndFeel
图 1 展示了一个带有默认 Metal 外观和效果的 Java 应用程序,而图 2 中的是 Substance Raven 外观和效果:

▲图1 Java 平台的 Metal 外观和效果

▲图2 Substance 的 Raven 外观和效果
2. SwingX
Swing 框架包括您需要的大部分标准控件,包括树、表、列表等等。但是它还缺少一些更为现代的控件,比如树形表。SwingX 项目(SwingLabs 的一部分)提供一个丰富的组件集,包括:
表、树和列表的分类、过滤、和突出显示
Find/search
Auto-completion
Login/authentication 框架
TreeTable 组件
Collapsible panel 组件
Date picker 组件
Tip-of-the-Day 组件
要尝试它,从 SwingLabs 下载 SwingX JAR 并将它添加到您的 CLASSPATH,或者仅将以下依赖项添加到您的 Maven POM 文件:
<
dependency
>
< groupId > org.swinglabs </ groupId >
< artifactId > swingx </ artifactId >
< version > 1.6 </ version >
</ dependency >
< groupId > org.swinglabs </ groupId >
< artifactId > swingx </ artifactId >
< version > 1.6 </ version >
</ dependency >
图 3 中的树形表是 SwingX 组件的一个示例:

▲图3 SwingX TreeTable 组件
构建一个 SwingX TreeTable
使用 SwingX JXTreeTable 控件,构建一个树形表是非常简单的。想象一下:您表中的每行可能有列数据,也可能有子节点。SwingX 提供一个模型类,可以扩展来提供这一功能,称为 org.jdesktop.swingx.treetable.AbstractTreeTableModel。清单 1 展示了一个树形表模型实现的示例:
清单 1. MyTreeTableModel.java
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;
}
}
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;
}
}
清单 2 显示了一个定制的树节点:
清单 2. MyTreeNode.java
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;
}
}
{
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;
}
}
如果您想使用这个树形表模型,您将需要创建一个它的实例,然后将实例传递到 JXTreeTable 构造器,像这样:
private
MyTreeTableModel treeTableModel
=
new
MyTreeTableModel();
private JXTreeTable treeTable = new JXTreeTable( treeTableModel );
private JXTreeTable treeTable = new JXTreeTable( treeTableModel );
现在,您可以将 treeTable 添加到任何 Swing 容器,比如 JPanel 或者 JFrame 的内容面板。
3. RSyntaxTextArea
Swing 所缺少的另一个组件是一个带有语法高亮显示功能的文本编辑器。如果您编写了一个 XML 文档,那么就会知道它对于直观区别标记、属性、属性值和标记值是多么的有帮助。FifeSoft 的开发人员已经构建了一组丰富的组件,您可以在基于 Swing 的 Java 应用程序中使用它们,其中之一是 RSyntaxTextArea 组件。
RSyntaxTextArea 支持大多数开箱即用的编程语言,包括 C、C++、Perl、PHP、以及 Java,还有 HTML、JavaScript、XML、甚至 SQL。
图 4 是一个 RSyntaxTextArea组件的屏幕截图,显示了一个 XML 文件:

▲图4 RSyntaxTextArea 显示一个 XML 文件
将语法高亮显示添加到 Swing 应用程序
首先,从 Sourceforge 下载 RSyntaxTextArea JAR 文件。如果您正在使用 Maven,您可能想将它安装到您的本地库中,使用像这样一个命令:
mvn install:install
-
file
-
DgroupId
=
com.fifesoft
-
DartifactId
=
rsyntaxtextarea
- Dversion = 1.0 - Dpackaging = jar - Dfile =/ path / to / file
- Dversion = 1.0 - Dpackaging = jar - Dfile =/ path / to / file
当您将 JAR 文件添加到您的项目之后,您就可以开始在您的应用程序中创建一个 RSyntaxTextArea 实例了,并且如果您想要滚动功能,就将它添加到 RTestScrollPane 中,然后调用 setSyntaxEditingStyle() 方法,为它传递一个 SyntaxConstants。例如,清单 3 创建了一个可滚动的 RSyntaxTextArea,以 XML 形式呈现文本:
清单 3. 在 Swing 中语法高亮显示
RSyntaxTextArea text
=
new
RSyntaxTextArea();
add( new RTextScrollPane( text ) );
text.setSyntaxEditingStyle( SyntaxConstants.SYNTAX_STYLE_XML );
add( new RTextScrollPane( text ) );
text.setSyntaxEditingStyle( SyntaxConstants.SYNTAX_STYLE_XML );
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 图标
如果 Java Look-And-Feel Graphics Repository 只提供预构建图形,那就太好了,但是它还提供标准约定,您构建或命名菜单、工具栏,以及快捷键时都要使用这些约定。比如,复制功能应该使用快捷键 Ctrl-C(称为 Copy)实现,而且提供了一个 Copy 工具提示。当在菜单中时,复制函数的助记符应该是 C、P、或者至少应该是 Y。
使用 Java Look-And-Feel Graphics Repository 图标
要试用 图 5 中显示的一些预构建图形,从 Oracle 中 下载 Java Look-and-Feel Graphics Repository JAR,并添加到您的 CLASSPATH。然后,您需要从 JAR 文件内将图标作为资源加载:图标是以下格式的:
...
toolbarButtonGraphics / general / Copy16.gif
toolbarButtonGraphics / general / Copy24.gif
toolbarButtonGraphics / general / Cut16.gif
toolbarButtonGraphics / general / Cut24.gif
toolbarButtonGraphics / general / Delete16.gif
toolbarButtonGraphics / general / Delete24.gif
...
toolbarButtonGraphics / general / Copy16.gif
toolbarButtonGraphics / general / Copy24.gif
toolbarButtonGraphics / general / Cut16.gif
toolbarButtonGraphics / general / Cut24.gif
toolbarButtonGraphics / general / Delete16.gif
toolbarButtonGraphics / general / Delete24.gif
...
所有图标都包含在 toolbarButtonGraphics 目录下,分成两类在 图 5 中显示。在这种情况下,我们将考虑从总类中复制、剪切、粘贴和删除。名称中的 “16” 和 “24” 反映了图标的大小:16x16 或 24x24。您可以创建一个 ImageIcon 图标添加到文件中,例如:
Class class
=
this.getClass();
String urlString = " /toolbarButtonGraphics/general/Cut16.gif "
URL url = class.getResource( urlString );
ImageIcon icon = new ImageIcon( url );
String urlString = " /toolbarButtonGraphics/general/Cut16.gif "
URL url = class.getResource( urlString );
ImageIcon icon = new ImageIcon( url );
5. Swing 线程
在本文发布示例时,您可能遇一些奇怪内容,看起来像运行时错误。如果是这样,您的应用程序有可能出现了一个常见的线程处理错误。许多 Java 开发人员不明白,Swing 应用程序应该运行在它们自己的线程中,而不是主执行线程中。Swing 将忽略这种错误 ,但是迄今为止引入的很多组件却不会。
Java 平台提供了一个名为 SwingUtilities 的类,可以帮助您将您的 Swing 应用程序发布到它自己的线程上,该类有一个 invokeLater() 方法,您可以用来启动 Swing 应用程序。清单 4 显示了使用 SwingUtilities.invokeLater() 方法发布的 JXTreeTable:
清单 4. SwingXExample.java
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();
}
}
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();
}
}
构造器将 JFrame 的可视度设置为 true,当 Swing 运行在应用程序的主线程上是,这是不被允许的。清单 4 创建了一个单独的类 AppStarter,它扩展 Thread 并创建 SwingXExample 类。main() 方法创建一个 AppStarter 类实例,并将它传递给 SwingUtilities.invokeLater() 方法,来正确启动应用程序。尽量养成以这种方式运行您 Swing 应用程序的习惯 — 不仅是因为它正确,而且如果不这样做,有些第三方组件可能无法运行。
结束语
Swing 是一个功能强大的库,允许您在 Java 平台上构建用户界面,但是它缺少一些您想合并到您应用程序中的现代组件。在本文中,我为您提供一些有用的技巧,来美化(并现代化)您的 Swing 应用程序。像 Substance、SwingX 这类开源项目以及 Java Look-And-Feel Graphics Repository 使得在 Java 平台上构建丰富的用户界面比较容易。
转载于:https://blog.51cto.com/wujuxiang/468016