Java Swing的进化

摘 要:Swing已是一个比较老的工具集了,在美观的用户界面出来之前需要开发很长时间。它缺少一些你在开发富UI时所需的组件。幸运地是,像 Substance,SwingX及Java Look-and_Feel图形仓库这样的开源项目使这一切变得不同。作者Steven Haines向你展示了如何无痛苦地向你的Swing UI中添加树表,语法高亮,以及其它更多的东西。

在最近这些年里,用户界面设计与开发已经发生了很大的改变,一些人可能会说Java平台已经停滞不前了。发布于1997年的Swing仍然是在JVM中构
建用户界面的标准工具包。从好的方面说,相似的标准便于协作;从坏的方面说,它缺少富UI设计中已经普遍存在的特性。
在本期的5 things系列中,我会介绍四个免费的开源组件,你能用它们使Swing GUI更时髦。然后,我们所讨论的内容将围绕着你所不知道的Swing线程。

1. Substance
    将Java应用程序与本地操作系统进行整合是困难的,主要是因为Swing要手工绘制它自己的组件。解决该问题的权宜之计之一就是Java外观,它允许JVM将应用程序的组件外观代理成本地外观;当使用Mac外观时,它们看起来就是像是Mac应用。
    Swing提供标准的本地外观,也提供它自己的独立于平台的外观,叫作Metal。另外,Kirill Grouchnikov开发的Substance是一个开源的项目,它提供了更多的外观皮肤。要想尝试一下,可以从java.net下载Substance,然后:
1. 将substance.jar文件加到你的CLASSPATH中。
2. 将下面的系统配置加到应用程序中的启动脚本中:

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

3. 在第二步中,对于lookandfeelname变量所处的位置,可尝试下列任一值:

SubstanceAutumnLookAndFeel
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的一部分,提供了一个富组件集,包括如下:
    * Sorting, filtering, and highlighting for tables, trees, and lists
    * Find/search
    * Auto-completion
    * Login/authentication framework
    * TreeTable component
    * Collapsible panel component
    * Date picker component
    * Tip-of-the-Day component

要尝试的话,从SwingLabs中下载SwingX的JAR文件,然后把它加到CLASSPATH中,或者把下面的依赖加到Maven POM文件中:

<dependency>
    <groupId>org.swinglabs</groupId>
    <artifactId>swingx</artifactId>
    <version>1.6</version>
</dependency>

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

图3. SwingX TreeTable组件

构建一个SwingX树表
使
用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;     }}

清单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;
    }
}

如果你想使用这个树表模式,你将需要创建一个它的实例,然后将该实例传给JXTreeTable的构造器,就像这样:

private MyTreeTableModel treeTableModel = new MyTreeTableModel();
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

一旦你在项目使用这个JAR文件,你就能在应用中创建
RSyntaxTextArea的实例。如果你希望有滑动功能,就把它加入
RTestScrollPane中,然后调用setSyntaxEditingStyle()方法,并传入一个SyntaxConstants作为该方法
的参数。

清单3. Swing中的语法高亮

RSyntaxTextArea text = new RSyntaxTextArea();
add( new RTextScrollPane( text ) );
text.setSyntaxEditingStyle( SyntaxConstants.SYNTAX_STYLE_XML );

4. Java外观图形仓库
   
Microsoft作的很好的工作之一就是确保Windows应用都有着一致的外观。如果你已经编写过一个Java
Swing应用,无论用了多长时间,你可能已经访问过Oracle的Java外观图形仓库。如果没有,你会对它感满意的。Java外观图形仓库创建一组针
对标准应用行为的图标,例如File->New和Edit->Copy,还有更多的鲜为人知的命令,如媒体控件,浏览器导航功能,以及针对
Java开发员的编程工作。图5展示了一个从Oracle网站上获取的图标的截屏:

图5. Java外观图形仓库图标

如果Java外观图形仓库只是提供预置的图形,它也足够好了,但它还提供了当你在构建和命名菜单,菜单栏,以及快捷键的标准规范。例如,复制功能应该有Ctrl-C快捷键,命名为Copy,并给一个Copy的提示。当它在菜单中,复制功能的助记符应为C,P,或至少是Y。

使用Java外观图形仓库的图标
尝试图5所示的一些预置图形,要从Oracle网站上下载Java外观图形仓库的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目录中,
被分割成图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 );

5. Swing线程

当启动文中示例时,你可能会遇到一些看起来奇怪的运行时错误。如果是这样,在你的Swing应用中,你可能会犯一个通常的线程错误。许多Java开发者不
知道Swing应用程序希望运行在它们自己的线程中,而不是运行在主运行线程中。Swing不会原谅这方面的错误,但介绍过的许多组件目前还不会这样。

了帮助你在Swing应用自己的线程中启动它自己,Java平台提供了一个叫作SwingUtilties的类,它有一个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();
    }
}

构造器设置JFrame的可视性为true,而如果运行在应用的主线程
中,Swing是不允许这么做的。所以清单创建一个独立的类,叫作
AppStarter,它继承自Thread并会创建SwingXExample类的实例。main()方法创建AppStarter类的一个实例,并将
它传给SwingUtilities.invokeLater()方法以方便启动应用。尝试着养成这样的习惯去运行Swing应用--不仅因为这是正确的
方式,也因为如果你不这么做一些第三方的组件将无法工作。

结论
   
Swing是一个强大的类库,它允许你在Java平台上构建用户界面,但它缺少一些你可能想引入的现代的组件。在本文中,为了美化(以及现代化)你的
Swing应用,我提供了一些小窍门。开源项目,如Substance,SwingX以及Java外观图形仓库使在Java平台上构建富用户界面变得更容
易。查看资源章节,以学习关于这些开源项目以及Swing编程的更多知识。

时间: 2024-10-13 12:46:16

Java Swing的进化的相关文章

Atitit。D&amp;D drag&amp;drop拖拽功能c#.net java swing的对比与实现总结

Atitit.D&D drag&drop拖拽功能c#.net java swing的对比与实现总结 1. 实现一个D&D操作一般包括三个步骤: 1 2. .net黑头的拖曳机制.必须有DragEnter事件(单独写DragDrop事件是不会具有拖拽功能的) 2 3. ---java黑头的拖曳..必须有DragEnter事件(单独写 Drop事件是不会具有拖拽功能的) 2 4. 代码 3 5. 参考 5 1. 实现一个D&D操作一般包括三个步骤: 首先实现一个拖拽源,这个拖拽

【小型系统】抽奖系统-使用Java Swing完成

一.需求分析 1. 显示候选人照片和姓名. 2. 可以使用多种模式进行抽奖,包括一人单独抽奖.两人同时抽奖.三人同时抽奖. 3. 一个人可以在不同的批次的抽奖中获取一.二.三等奖,但是不能在同一批次抽奖中获取多个奖项. 二.系统总体框架 系统需求比较简单,采用Java Swing技术可以很好的实现,系统的总体类框架图如下 说明:基于主要的三个类就可以完成所需要的功能,Frame有Panel类型与MouseAdapter类型的成员,Panel主要用户存放具体的控件和绘制相应的信息,MouseAda

解决 GTK+/GNOME 3 环境下 Java Swing 程序使用本地 GTK+ 主题时菜单无边框 bug 的方法

在 GTK+/GNOME 3 环境下采用默认的 Adwaita 主题时,Java Swing 程序如果使用本地 GTK+ 主题会出现菜单无边框的 bug,这个问题也可能在其他常用的 GTK+ 主题中出现.说这是 Java Swing 的 bug 还是 GTK+/GNOME 3 的主题(如 Adwaita)的 bug 其实意义不是太大.这里给出一个简单的解决办法,其思路是修改 GTK+/GNOME 3 的主题,这里以系统默认的 Adwaita 主题为例: Adwaita 主题文件位于 /usr/s

(转载) Java Swing 之 JScrollPane (滚动面板) 使用方法

1 /** 2 * java swing 之JScrollPane面板 3 * 在设置界面时,可能会遇到在一个较小的容器窗体中显示一个较大部分的内容,这时可以使用 4 * JScrollPane面板,JscrollPane面板是带滚动条的面板,也是一种容器,但是常用于布置单个 5 * 控件,并且不可以使用布局管理器.如果需要在JScrollPane面板中放置多个控件,需要将多个 6 * 控件放置到JPanel 面板上,然后将JPanel面板作为一个整体控件添加到JScrollPane控件上. 7

抽奖系统-使用Java Swing完成

一.需求分析 1. 显示候选人照片和姓名. 2. 可以使用多种模式进行抽奖,包括一人单独抽奖.两人同时抽奖.三人同时抽奖. 3. 一个人可以在不同的批次的抽奖中获取一.二.三等奖,但是不能在同一批次抽奖中获取多个奖项. 二.系统总体框架 系统需求比较简单,采用Java Swing技术可以很好的实现,系统的总体类框架图如下 说明:基于主要的三个类就可以完成所需要的功能,Frame有Panel类型与MouseAdapter类型的成员,Panel主要用户存放具体的控件和绘制相应的信息,MouseAda

atitit.软件开发GUI 布局管理优缺点总结java swing wpf web html c++ qt php asp.net winform

atitit.软件开发GUI 布局管理优缺点总结java swing wpf web html c++ qt php asp.net winform 1. Absoluti 布局(经常使用) 1 2. Flow 布局(不经常使用) 1 3. BorderLayout (不经常使用) 1 4. BoxLayout( html默认布局) 2 5. CardLayout (tab 布局) 2 6. GridLayout 3 7. GridBagLayout 3 8. Fixed 定位(不经常使用) 3

Atitit. 悬浮窗口的实现 java swing c# .net c++ js html 的实现

Atitit. 悬浮窗口的实现 java swing c# .net c++ js html 的实现 1. 建立悬浮窗口引用代码 1 1.1. 定义悬浮窗口,设置this主窗口引用,是为了在悬浮窗口中双击可缩小还还原主窗口.以及悬浮窗口右键菜单"显示主界面"中需要还原主窗 1 1.2. //设置主窗口关闭时,先关闭悬浮窗口.and系统托盘 1 1.3. 注入系统托盘图标,退出的时候儿也退出系统托盘 2 2. 悬浮窗口结构and重大的点 2 2.1. 要去掉标题栏: 2 2.2. Opa

JFileChooser (Java Swing提供的文件选择对话框)

JFileChooser() 构造一个指向用户默认目录的 JFileChooser. JFileChooser(File currentDirectory) 使用给定的 File 作为路径来构造一个 JFileChooser. setFileSelectionMode(int mode) 设置 JFileChooser,以允许用户只选择文件.只选择目录,或者可选择文件和目录. mode参数:FILES_AND_DIRECTORIES   指示显示文件和目录. FILES_ONLY        

java swing 计算器

package calc; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; import java.awt.*; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.awt.Color; /** * jav