本章要点

  • Swing编程基础
  • Swing组件的继承层次
  • 常用Swing组件的用法
  • 使用JToolBar创建工具条
  • 颜色选择对话框和文件浏览对话框
  • Swing提供的特殊容器
  • Swing的简化拖放操作
  • 使用JLayer装饰组件
  • 开发透明的、不规则形状窗口
  • 开发进度条
  • 开发滑动条
  • 使用JTree和TreeModel开发树
  • 使用JTable和TableModel开发表格
  • 使用JTextPane组件

使用Swing开发图形界面比AWT更加优秀,因为Swing是一种轻量级组件,它采用100%的Java实现,不再依赖于本地平台的图形界面,所以可以在所有平台上保持相同的运行效果,对跨平台支持比较出色。
除此之外,Swing提供了比AWT更多的图形界面组件,因此可以开发出更美观的图形界面。由于AWT需要调用底层平台的GUI实现,所以AWT只能使用各种平台上GUI组件的交集,这大大限制了AWT所支持的GUI组件。对Swing而言,几乎所有组件都采用纯Java实现,所以无需考虑是否支持该组件,因此Swing可以提供如JTabbedPane、JDesktopPane、JInternalFrame等特殊的容器,也可以提供像JTree、JTable、JSpinner、JSlider等特殊的GUI组件。
除此之外,Swing组件都采用MVC(Model-View-Controller,即模型-视图-控制器)设计模式,从而可以实现GUI组件的显示逻辑和数据逻辑的分离,允许程序员自定义Render来改变GUI组件的显示外观,提供更多的灵活性。

Swing概述

前一章已经介绍过了AWT和Swing的关系,因此不难知道:实际使用Java开发图形界面程序时,很少使用AWT组件,绝大部分时候都是用Swing组件开发的。Swing时由100%纯Java实现的,不再依赖于本地平台的GUI,因此可以再所有平台上都保持相同的界面外观。独立于本地平台的Swing组件被称为轻量级组件;而依赖于本地平台的AWT组件被称为重量级组件。
由于Swing的所有组件完全采用Java实现,不再调用本地平台的GUI,所以导致Swing图形界面显示速度要比AWT图形界面的显示速度要快一些,但对于快速发展的硬件设施二十,这种微小的速度差别无妨大碍。
使用Swing开发图形界面由如下几个优势。

  • Swing组件不再依赖于本地平台的GUI,无须采用各种平台的GUI交集,因此Swing提供了大量图形界面组件,远远超出了AWT所提供的图形界面组件集。
  • Swing组件不再依赖于本地平台GUI,因此不会产生与平台相关的bug。
  • Swing组件在各种平台上运行时可以保证具有相同的图形界面外观。
    Swing提供的这些优势,让Java图形界面程序真正实现了“Write Once,Run Anywhere”的目标。
    除此之外,Swing还由如下两个特征。
  • Swing组件采用MVC(Model-View-Controller,即模型-视图-控制器)设计模式,其中模型(Model)用于维护组件的各种状态,视图(View)时组件的可视化表现,控制器(Controller)用于控制对各种事件、组件做出怎样的相应。当模型发生改变时,它会通知所有依赖于它的视图,视图会根据模型数据来更新自己。Swing使用UI代理来包装视图和控制器,还有另一个模型对象来维护该租金啊的状态。例如,按钮JButton 有一个维护其状态信息的模型ButtonModel对象。Swing组件的模型是自动设置的,因此一般都使用JButton,而无须关心ButtonModel对象。因此,Swing的MVC实现也被称为Model-Delegate(模型-代理)。

对于一些简单的Swing组件通常无须关心它对应的Model对象,但对于一些高级的Swing组件,如JTree、JTabel等需要维护复杂的数据,这些数据就是由该组件对应的Model来维护的。另外,通过创建Model类的子类或通过实现适当的接口,可以为组件建立自己的模型,然后用setModel()方法把模型与组件关联起来。

  • Swing在不同的平台上表现一致,并且有能力提供本地平台不支持的显示外观。由于Swing组件采用MVC模式来维护各组件,所以当组件的外观被改变时,对组件的信息状态信息(由模型维护)没有任何影响。因此,Swing可以使用插拔式外观感觉(Pluggable Look And Feel,PLAF)来控制组件外观,使用Swing图形界面在同一个平台上运行时能拥有不同的外观,用户可以选择自己喜欢的外观。相比之下,在AWT图形界面中,由于控制组件外观的对等类与具体平台相关,因此AWT组件总是具有与本地平台相同的外观。

Swing提供了多种独立于各种平台的LAF(Look And Feel),默认是一种名为Metal的LAF,这种LAF吸收了Macintosh平台的风格,因此显得比较漂亮。Java7则提供了一种名为Nimbus的LAF,这种LAF更加漂亮。
为了获取到当前JRE所支持的LAF,可以借助与UIManager的getInstalledLookAndFeels()方法,如下程序所示。

import javax.swing.*;public class Demo{public static void main(String[] args) {System.out.println("当前系统可用的所有LAF:");for(var info : UIManager.getInstalledLookAndFeels()){System.out.println(info.getName()+"--->"+info);}}
}
"C:\Program Files\Java\jdk-11.0.11\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:57332,suspend=y,server=n -javaagent:C:\Users\YueDie\AppData\Local\JetBrains\IntelliJIdea2020.1\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "D:\DemoProject\out\production\DemoProject;D:\IntelliJ IDEA 2020.1.1\lib\idea_rt.jar" Demo
Connected to the target VM, address: '127.0.0.1:57332', transport: 'socket'
当前系统可用的所有LAF:
Metal--->javax.swing.UIManager$LookAndFeelInfo[Metal javax.swing.plaf.metal.MetalLookAndFeel]
Nimbus--->javax.swing.UIManager$LookAndFeelInfo[Nimbus javax.swing.plaf.nimbus.NimbusLookAndFeel]
CDE/Motif--->javax.swing.UIManager$LookAndFeelInfo[CDE/Motif com.sun.java.swing.plaf.motif.MotifLookAndFeel]
Windows--->javax.swing.UIManager$LookAndFeelInfo[Windows com.sun.java.swing.plaf.windows.WindowsLookAndFeel]
Windows Classic--->javax.swing.UIManager$LookAndFeelInfo[Windows Classic com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel]
Disconnected from the target VM, address: '127.0.0.1:57332', transport: 'socket'Process finished with exit code 0

除可以使用Java默认提供的数量不多的几种LAF之外,还由大量的Java爱好者提供了各种开源的LAF,有兴趣可以去自行下载、体验各种LAF,使用不同的LAF可以让Swing应用程序更加美观。

Swing基本组件的用法

前面已经提到过,Swing为所有的AWT组件提供了对应的实现(除Canvas组件除外,因为在Swing中无须继承Canvas组件),通常在AWT组件的组件名前添加“J”就变成了对应的Swing组件。

Swing组件层次

大部分Swing组件都是JComponent抽象类的直接或间接子类(并不是全部的Swing组件),JComponent类定义了所有子类组件的通用方法,JComponent类是AWT里java.awt.Container类的子类,这也是AWT和Swing的联系之一。绝大部分Swing组件类继承了Container类,所以Swing组件都可作为容器使用(JFrame继承了Frame类),下图显示了Swing组件继承层次图。

  • JComboBox:对应于AWT里的Choice组件,但比Choice组件功能更丰富。
  • JFileChooser:对应于AWT里的FileDialog组件。
  • JScrollBar:对应于AWT里的Scrollbar组件,注意两个组件类名中b字母的大小写差别。
  • JCheckBox:对应于AWT里的Checkbox组件,注意两个组件类名中b字母的大小写差别。
  • JCeckBoxMenuItem:对应于AWT里的CheckboxMenuItem组件,注意两个组件类名b字母的大小写差别。

上面JCeckBox和JCeckBoxMenuItem与Checkbox和CheckboxMenuItem的差别主要是由于早期Java命名不太规范造成的。

从上图可以看出,Swing中包含了4个组件直接继承了AWT组件,而不是从JComponent派生的,它们分别是:JFrame、JWindow、JDialog和JApplet,它们并不是轻量级组件,而是重量级组件(需要部分委托给运行平台上的GUI组件的对等体)。

将Swing组件按功能来分,又可分为如下几类。

  • 顶层容器:JFrame、JApplet、JDialog和JWindow。
  • 中间容器:JPanel、JScrollPane、JSplitPane、JToolBar等。
  • 特殊容器:在用户界面上具有特殊作用的中间容器,如JInternalFrame、JRootPane、JLayeredPane和JDestopPane等。
  • 基本组件:实现人机交互的组件,如JButton、JComboBox、JList、JMenu、JSlider等。
  • 不可编辑信息的显示组件:向用户显示不可编辑星系的组件,如JLabel、JProgressBar和JToolTip等。
  • 可编辑信息的显示组件:向用户显示能被编辑的格式化信息的组件,如JTable、JTextArea和JTextField等。
  • 特殊对话框组件:可以直接产生特殊对话框的组件,如JColorChooser和JFileChooser等。

下面将会依次详细介绍各种Swing组件的用法。

AWT组件的Swing实现

从上图可以看出,Swing为除Canvas之外的所有AWT组件提供了相应的实现,Swing组件比AWT组件的功能更为强大。相对于AWT组件,Swing组件具有如下4个额外的功能。

  • 可以为Swing组件设置提示信息。使用setToolTipText()方法,为组件设置对用户又帮助的提示信息。
  • 很多Swing组件如按钮、标签、菜单项等,除使用文字外,还可以使用图标修饰自己。为了允许在Swing组件中使用图标,Swing为Icon接口提供了一个实现类:ImageIcon,该实现类代表一个图像图标。
  • 支持插拔式的外观风格。每个JComponent对象都又一个相应的ComponentUI对象,为它完成所有的绘画、事件处理、决定尺寸大小等工作。ComponentUI对象依赖于当前使用的PLAF,使用UIManager.setLookAndFeel()方法可以改变图形界面的外观风格。
  • 支持设置边框。Swing组件可以设置一个或多个边框。Swing中提供了各式各样的边框供用户选用,也能建立组合边框或自己设计的边框。一种空白边框可以用于增大组件,同时协助布局管理器对容器中的组件进行合理的布局。

每个Swing组件都又一个对应的UI类,例如JButton组件就有一个对应的ButtonUI类来作为UI代理。每个Swing组件的UI代理的类名总是将该Swing组件类目的J去掉,然后再后面添加UI后缀。
UI代理通常式一个抽象基类,不同的PLAF会有不同的UI代理实现类。Swing类库中包含了几套UI代理,每套UI代理都几乎包含了所有Swing组件的ComponentUI实现,每套这样的实现都被称为一种PLAF实现。以JButton为例,其UI代理的继承层次如下图所示。

如果需要改变程序的外观风格,则可以使用如下代码。

try{//设置使用Windows风格UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowLookAndFeel");SwingUtilities.updateComponentTreeUI(f);}catch (Exception e){e.printStackTrace();}

下main程序示范了使用Swing组件来创建窗口应用,该窗口里包含了菜单、右键菜单以及基本AWT组件的Swing实现。


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;public class SwingComponent{JFrame f = new JFrame("测试");//这里我们定义按钮但是不指定图标因为文章的限制,加上icon的麻烦JButton ok = new JButton("确认");//定义一个单选按钮,初始处于选中状态JRadioButton male = new JRadioButton("男",true);//定义一个单选按钮,初始处于没有选中状态JRadioButton female = new JRadioButton("女",false);//定义一个ButtonGroup,用于将上面两个JRadioButton组合在一起ButtonGroup bg = new ButtonGroup();//定义一个复选框,初始处于没有选中状态。JCheckBox married = new JCheckBox("是否已婚?",false);String[]colors = new String[]{"红色","绿色","蓝色"};//定义一个下拉选择框JComboBox<String>colorChooser = new JComboBox<>(colors);//定义一个列表选择框JList<String>colorList = new JList<>(colors);//定义一个8行、20列的多行文本域JTextArea ta = new JTextArea(8,20);//定义一个40列的单行问北路JTextField name = new JTextField(40);JMenuBar mb = new JMenuBar();JMenu file = new JMenu("文件");JMenu edit = new JMenu("编辑");//创建“新建”菜单项JMenuItem newItem = new JMenuItem("新建");//创建“保存”菜单项JMenuItem saveItem = new JMenuItem("保存");//创建“退出”菜单项JMenuItem exitItem = new JMenuItem("退出");JCheckBoxMenuItem autoWrap = new JCheckBoxMenuItem("自动换行");//创建“复制”菜单项JMenuItem copyItem = new JMenuItem("复制");//创建“粘贴”菜单项JMenuItem pasteItem = new JMenuItem("粘贴");JMenu format = new JMenu("格式");JMenuItem commentItem = new JMenuItem("注释");JMenuItem cancelItem = new JMenuItem("取消注释");//定义一个右键菜单用于设置程序风格JPopupMenu pop = new JPopupMenu();//用于组合3个风格菜单项的ButtonGroupButtonGroup flavorGroup = new ButtonGroup();JRadioButtonMenuItem metalItem = new JRadioButtonMenuItem("Metal风格",true);JRadioButtonMenuItem nimbusItem = new JRadioButtonMenuItem("Nimbus风格");JRadioButtonMenuItem windowsItem = new JRadioButtonMenuItem("Windows风格");JRadioButtonMenuItem classicItem = new JRadioButtonMenuItem("Windows经典风格");JRadioButtonMenuItem motifItem = new JRadioButtonMenuItem("Motif风格");//------------------------------用于执行界面初始化的init方法------------------------------public void init(){//创建以一个装在了文本框、按钮的JPanelvar bottom = new JPanel();bottom.add(name);bottom.add(ok);f.add(bottom, BorderLayout.SOUTH);//创建一个装在了下拉框、三个JCheckBox的JPanelvar checkPanel = new JPanel();checkPanel.add(colorChooser);bg.add(male);bg.add(female);checkPanel.add(male);checkPanel.add(female);checkPanel.add(married);//创建一个垂直排列组件的Box、盛装多行文本域JPanelvar topLeft = Box.createVerticalBox();//使用JScrollPane作为普通组件的JViewPortvar taJsp = new JScrollPane(ta);topLeft.add(taJsp);topLeft.add(checkPanel);//创建一个水平排列组件的Box,盛装topLeft、colorListvar top = Box.createHorizontalBox();top.add(topLeft);top.add(colorList);//将top Box容器 添加到窗口的中间f.add(top);//----------下面开始组合菜单,并为菜单添加监听器----------//为newItem设置快捷键,设置亏啊借鉴时要求使用大写字母newItem.setAccelerator(KeyStroke.getKeyStroke('N', InputEvent.CTRL_DOWN_MASK));newItem.addActionListener(e -> ta.append("用户单击了“新建”菜单\n"));//为file菜单添加菜单项file.add(newItem);file.add(saveItem);file.add(exitItem);//为edit菜单项添加菜单项edit.add(autoWrap);//使用addSeparator方法添加菜单分隔符edit.addSeparator();edit.add(copyItem);edit.add(pasteItem);//为commentItem组件添加提示信息commentItem.setToolTipText("将程序代码注释起来!");//为format菜单添加菜单项format.add(commentItem);format.add(cancelItem);//使用new JMenuItem("-")的方式不能添加菜单分隔符edit.add(new JMenuItem("-"));//将format菜单组合到edit菜单中,从而形成二级菜单edit.add(format);//将file、edit菜单添加到mb菜单条中mb.add(file);mb.add(edit);//为f窗口设置菜单条f.setJMenuBar(mb);//----------下面开始组合右键菜单,并安装右键菜单----------flavorGroup.add(metalItem);flavorGroup.add(nimbusItem);flavorGroup.add(windowsItem);flavorGroup.add(classicItem);flavorGroup.add(motifItem);pop.add(metalItem);pop.add(nimbusItem);pop.add(windowsItem);pop.add(classicItem);pop.add(motifItem);//为5个风格菜单创建事件监听器ActionListener flavorListener = e->{try{switch (e.getActionCommand()){case "Metal风格":changeFlavor(1);break;case "Nimbus风格":changeFlavor(2);break;case "Windows风格":changeFlavor(3);break;case "Windows经典风格":changeFlavor(4);break;case "Motif风格":changeFlavor(5);break;}}catch (Exception ee){ee.printStackTrace();}};//为5个风格菜单项添加事件监听器metalItem.addActionListener(flavorListener);nimbusItem.addActionListener(flavorListener);windowsItem.addActionListener(flavorListener);classicItem.addActionListener(flavorListener);motifItem.addActionListener(flavorListener);//调用该方法即可设置右键菜单,无须使用事件机制ta.setComponentPopupMenu(pop);//设置关闭关闭窗口式,退出程序f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);f.pack();f.setVisible(true);}//定义一个方法,用于改变界面风格private void changeFlavor(int flavor)throws Exception{switch (flavor){//设置Metal风格case 1:UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");System.out.println("1");break;//设置Nimbus风格case 2:UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");System.out.println("2");break;//设置Window风格case 3:UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");System.out.println("3");break;//设置Windows经典风格case 4:UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");System.out.println("4");break;//设置Motif风格case 5:UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");System.out.println("5");break;}}public static void main(String[] args) {//设置Swing窗口使用Java风格//JFrame.setDefaultLookAndFeelDecorated(true);new SwingComponent().init();}
}

上面代码无法通过 new JMenuItem(“-”)的方式添加菜单分隔符,只能通过addSeparator()方法来添加菜单分隔符。
Swing专门为菜单项、工具按钮之间的分隔符提供了一个JSeparator类,通常使用JMenu或者JPopupMenu的addSeparator()方法来创建并添加JSeparator对象,而不是直接使用JSeparator。实际上,JSeparator可以用在任何需要使用分隔符的地方。
上面程序为newItem菜单项增加了快捷键,为Swing菜单项指定与为AWT菜单项指定快捷键的方式有所不同——创建AWT菜单对象时可以直接传入KeyShortcut对象为其指定快捷键;但为Swing菜单项指定快捷键时必须通过setAccelerator(KeyStroke ks)方法来设置,其中KeyStroke代表一次击键动作,可以直接通过按键对应字母在指定该击键动作。
为菜单项指定快捷键时应该使用大写字母来代表按键,例如KeyStroke.getKeyStroke(‘N’,InputEvent.CTRL_DOWN_MASK)代表"Ctrl+N",但KeyStroke.getKeyStroke(‘n’,InputEvent.CTRL_DOWN_MASK)则不代表“Ctrl+N”。
除此之外,上面程序中代码所定义的changeFlavor()方便面用于改变程序外观风格,当用户点击多行文本域里的右键菜单时将会触发该方法。该方法设置Swing组件的外观风格后,再次调用SwingUtilties类的updateComponentTreeUI()方法来更新指定容器,以及该容器内所有组件的UI。注意此处更新的是JFrame对象getContentPane()方法的返回值,而不是直接更新JFrame本身。这是因为如果直接更新JFrame本身,将会导致JFrame也被更新,JFrame是一个特殊的容器,JFrame依然依赖于本地平台的图形组件。尤其是取消最后一行的代码注释后,JFrame将会使用Java风格的标题栏、边框,如果强制JFrame更新成Windows或Motif风格,则会导致该窗口失去标题和边框。
JFrame提供了一个getContentPane()方法,这个方法用于返回该JFrame的顶级容器(即JRootPane对象),这个顶级容器会包含JFrame所显示的所有非菜单组件,可以这样理解:所有看似放在JFrame中的Swing组件,除菜单之外,其实都是放在JFrame对应的顶级容器中的,而JFrame容器里提供了getContentPane()方法返回了顶级容器。在Java5以前,Java甚至不允许向JFrame中添加组件,必须先调用JFrame的getContentPane()方法获得该窗口的顶级容器,然后将所有的组件添加到该顶级容器中。从Java5以后,Java改写了JFrame的add()和setLayout()方法,当程序调用JFrame的add()和setLayout()等方法时,实际上是对JFrame的顶级容器进行操作。
从上面代码可以看出,为Swing组件添加右键菜单无须像AWT那样繁琐,只需要简单地调用setComponentPopupMenu()方法来设置右键菜单即可,无须编写事件监听器,由此可见,使用Swing组件编写图形界面程序更加简单。
除此之外,如果程序希望用户单击窗口右上角的“X”按钮时,程序退出,也无须使用事件机制,只要调用setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)方法即可,Swing提供的这种方式也是为了简化界面编程。
JScrollPane组件时一个特殊的组件,它不同于JFrame、JPanel等普通的容器,它甚至不能指定自己的布局管理器,它主要用于为其他的Swing组件提供滚动条支持,JScrollPane通常由普通的Swing组件,可选的垂直、水平滚动条以及可选的行、列标题组成。
简而言之,如果希望让JTextArea、JTable等组件鞥由滚动条支持,值需要将该组件放入JScrollPane中,再将JScrollPane容器添加到窗口中即可。
为什么点击Swing多行菜单文本域时不是弹出像AWT多行文本域中的右键菜单?
这是由Swing组件和AWT组件实现机制不同决定的。前面已经指出,AWT的多行文本域实际上依赖于本地平台的多行文本域。简单地说,当我们在程序中防止一个AWT多行文本域,且该程序在Windows平台上运行时,该文本域组件将和记事本工具编辑区具有相同的行为方式,因为该文本域组件和记事本编辑区的底层实现时一样的。但Swing的多行文本域是纯Java的,它无须任何本地平台GUI的支持,它在热呢平台上都具有相同的行为方式,所以Swing多行文本域组件默认是没有右键菜单的,必须由程序员显式为它分配右键菜单。而且,Swing提供的JTextArea组件默认没有滚动滚动条(AWT的TextArea是否由滚动条取决于底层平台的实现),为了让该多行文本域具有滚动条,可以将该多行文本域放到JScrollPane容器中。
JScrollPane对于JTable组件尤其重要,通常需要把JTable放在JScrollPane容器中才可以显示出JTable组件的标题栏。

为组件设置边框

可以调用JComponent提供的setBorder(Border b)方法为Swing组件设置边框,其中Border是Swing提供的一个接口,用于代表组件的边框。该接口由数量众多的实现类,如LineBorder、MatteBorder。BevelBorder等,这些Border实现类都提供了相应的构造器用于创建Border对象,一旦获取了Border对象之后,就可以调用JComponent的setBordr(Border b)方法为指定组件设置边框。
TitleBorder和CompoundBorder比较独特,其中TitledBorder的作用并不是为它组件添加边框,二十位其他边框设置标题,当创建TitledBorder对象时,需要传入一个已经存在的Border对象, 新创建的TitledBorder对象会位原有的Border对象添加标题;而CompoundBorder用于组合两个边框,因此创建CompoundBorder对象时需要传入两个Border对象,一个用作组件的内边框,一个用作组件的外边框。
除此之外,Swing还提供了一个BorderFactory静态工厂类,该类提供了大量的静态工厂方法用于返回Border实例,这些静态方法的参数于各Border实现类的构造器参数基本一致。
Border不仅提供了上面所提供的一些Border实现类,还提供了MetalBorders.oolBarBorder、MetalBorders.TextFieldBorder等Border实现类,这些实现类用作Swing组件的默认边框,程序中通常无须使用这些系统边框。
为Swing组件添加边框可按如下步骤进行。

  1. 调用BorderFactory或XxxBorder创建XxxBorder实例。
  2. 调用Swing组件的setBorder(Border b)方法为该组件设置边框。

下图显示了系统可用边框之间的继承层次。

下面的例子程序示范了为Panel容器分别添加如图所示的几种边框。

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;public class BorderTest{private JFrame jf = new JFrame("测试边框");public void init(){jf.setLayout(new GridLayout(2,4));//使用静态工厂方法创建BevelBorderBorder bb = BorderFactory.createBevelBorder(BevelBorder.RAISED,Color.RED,Color.GREEN,Color.BLUE,Color.GRAY);jf.add(getPanelWithBorder(bb,"BevelBorder"));//使用静态工厂方法创建LineBorderBorder lb = BorderFactory.createLineBorder(Color.ORANGE,10);jf.add(getPanelWithBorder(lb,"LineBorder"));//使用静态工厂方法创建EtchedBorderBorder eb = BorderFactory.createEmptyBorder(20,5,10,30);jf.add(getPanelWithBorder(eb,"EmptyBorder"));//使用静态工厂方法创建EtchedBorderBorder etb = BorderFactory.createEtchedBorder(EtchedBorder.RAISED,Color.RED,Color.GREEN);jf.add(getPanelWithBorder(etb,"EtchedBorder"));//直接创建TitledBorder,TitledBorder就是为原有的边框增加标题var tb = new TitledBorder(lb,"测试标题",TitledBorder.LEFT,TitledBorder.BOTTOM,new Font("StSong",Font.BOLD,18),Color.BLUE);//直接创建MatterBorder,MatteBorder时EmptyBorder的子类,//它可以指定留空区域的颜色或背景,此处时指定颜色var mb = new MatteBorder(20,5,10,30,Color.GREEN);jf.add(getPanelWithBorder(mb,"MatteBorder"));//直接创建CompoundBorder,CompoundBorder将两个边框组合成新边框var cb  = new CompoundBorder(new LineBorder(Color.RED,8),tb);jf.add(getPanelWithBorder(cb,"CompoundBorder"));jf.pack();jf.setVisible(true);}public static void main(String[] args) {new BorderTest().init();}public JPanel getPanelWithBorder(Border b,String BorderName){var p = new JPanel();p.add(new JLabel(BorderName));//为Panel组件设置边框p.setBorder(b);return p;}
}

Swing组件的双重缓冲和键盘驱动

除此之外,Swing组件还有如下两个功能。

  • 所有的Swing组件都默认启用双缓冲绘图技术。
  • 所有的Swing组件都提供了简单的键盘驱动。

Swing组件默认启用双缓冲绘图技术,使用双缓冲绘图技术能改进频繁重回GUI组件的显示效果(避免闪烁现象)。JComponent组件默认启用双缓冲,无须自己实现双缓冲。如果想关闭双缓冲,可以在组件上调用setDoubleBuffered(false)方法。
JComponet类提供了getInputMap()和getActionMap()两个方法,其中getInputMap()返回一个InputMap对象,该对象用于将KeyStroke对象(代表键盘或其他类似输入设备的一次输入事件)和名字关联:getActionMap()返回一个ActionMap对象,该对象用于将指定名字和Action(Action接口时ActionListener接口的子接口,可作为一个事件监听器使用)关联,从而可以允许用户通过键盘操作来代替鼠标驱动GUI上的Swing组件,相当于为GUI组件提供快捷键。典型用法如下:

//把一次键盘事件和一个aCommand对象关联
compoent.getInputMap().put(aKeyStroke,aCommand);
//将aCommand对象和anAction事件响应关联
componet.getActionMap().put(aCommand,anAction);

下面程序实现这样一个功能:用户在单行文本框内输入内容,当输入完成后,单击后面的“发送”按钮即可将文本框的内容添加到一个多行文本域中;或者输入完成后再文本框内按“Ctrl+Enter”键也可以将文件框的内容添加到一个多行文本域中。

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;public class BindKeyTest{JFrame jf = new JFrame("测试键盘绑定");JTextArea jta = new JTextArea(5,30);JButton jb = new JButton("发送");JTextField jtf = new JTextField(15);public void init(){jf.add(jta);var jp = new JPanel();jp.add(jtf);jp.add(jb);jf.add(jp, BorderLayout.SOUTH);//发送消息的Action,Action是ActionListener的子接口Action sendMsg = new AbstractAction() {@Overridepublic void actionPerformed(ActionEvent e) {jta.append(jtf.getText()+"\n");jtf.setText("");}};//添加事件监听器jb.addActionListener(sendMsg);//将Ctrl+Enter键和"send"关联jtf.getInputMap().put(KeyStroke.getKeyStroke('\n', InputEvent.CTRL_DOWN_MASK),"send");jtf.getActionMap().put("send",sendMsg);jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);jf.pack();jf.setVisible(true);}public static void main(String[] args) {new BindKeyTest().init();}
}

使用JToolBar创建工具条

Swing提供了JToolBar类来创建工具条,创建JToolBar对象时可以指定如下两个参数。

  • name:该参数指定该工具条的名称。
  • orientation:该参数指定该工具条的方向。

一旦创建了JToolBar对象之后,JToolBar对象还有如下几个常用方法。

  • Jbutton add(Action a):通过Action对象为JToolBar添加对应的工具按钮。
  • void addSeparator(Dimension size):向工具条中添加指定大小的分隔符,Java允许不指定size参数,则添加一个默认大小的分隔符。
  • void setFloatable(boolean b):设置该工具条是否可浮动,即该工具条是否可以拖动。
  • void setMargin(Insets m):设置工具条边框和工具按钮之间的页边距。
  • void setOrientation(int o):设置工具条的方向。
  • void setRollover(boolean rollover):设置此工具条的rollover状态。

上面大多数方法都比较容易理解,比较难以理解的就是add(Action a)方法,系统如何为工具条添加Action对应的按钮呢?
Action接口时ActionListener接口的子类,它除包含ActionListener接口的actionPerformed()方法之外,还包含name和icon两个属性,其中name用于指定按钮或菜单项中的文本,而icon则用于指定按钮的图标或菜单项中的图标。也就是说,Action本身并不是按钮,也不是菜单项,只是把Action对象添加到某些容器(也可直接使用Action来创建按钮),如菜单和工具栏中时,这些容器会为该Action对象创建对应的组件(菜单项和按钮)。也就是说,这些容器需要完成如下事情。

  • 创建一个使用于该容器的组件(例如,在工具栏中创建一个工具按钮)。
  • 从Action对象中获得对应的属性来设置该组件(例如,通过name设置文本,通过icon来设置图标)。
  • 检查Action对象的初始状态,确定它是否处于激活状态,并根据该Action的状态来决定其对应所有组件的行为。只有处于激活状态的Action所对应的Swing组件才可以响应用户动作。
  • 通过Action对象为对应组件注册事件监听器,系统将为该Action所创建的所有组件注册同一个事件监听器(事件处理器就是Action对象里的actionPerformed()方法)。

例如,程序中有一个菜单项、一个工具按钮,还有一个普通按钮都需要完成某个“复制”动作,程序就可以将该复制动作定义成Action,并为之指定name和icon属性,然后通过该Action来创建菜单项、工具按钮和普通按钮,就可以让这三个组件具有相同的功能。另一个“粘贴”按钮也大致相似,而且“粘贴”组件默认不可用,只有当复制组件被触发后,且剪贴板中有内容时才可用。




上面程序中创建了pasteAction、copyAction两个Action,然后根据这两个Action分别创建了按钮、工具按钮、菜单项组件(程序中的序列标注部分(①~②)),开始时pasteAction处于非激活状态,则该Action对应的按钮、工具按钮、菜单项都处于不可用状态。

使用JFileChooser和JColorChooser

JColorChooser用于创建颜色选择对话框,该类的用法非常简单,该类主要提供了如下两个静态方法。

  • showDialog(Component component,String title,Color initalColor):显示一个模式的颜色选择器对话框,该方法返回用户所选颜色。其中component指定该对话框的parent组件,而title指定该对话框的标题,大部分时候都使用该方法来让用户选择颜色。
  • creatDialog(Component c,String title,boolean modal,JColorChooser chooserPane,ActionListener okListener,ActionListener cancelListener):该方法返回一个对话框,该对话框内包含指定的颜色选择器,该方法可以指定该对话框时模式的还时非模式的(通过modal参数指定),还可以指定该对话框内“确定”按钮的事件监听器(通过okListener参数指定)和“取消”按钮的事件监听器(通过cancelListener参数指定)。

Java7为JColorChooser增加了一个HSV标签页,允许用户通过HSV模式来选择颜色。
下面程序改写了前一章的HandDraw程序,该为使用JPanel作为绘图组件,而且使用JColorChooser来弹出颜色选择器对话框。



import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;public class HandDraw{//画图区的宽度private final int AREA_WIDTH = 500;//画图区的高度private final int AREA_HEIGHT = 400;//下面的preX、preY保存了上一次鼠标拖动事件的鼠标坐标private int preX = -1;private int preY = -1;//定义一个右键菜单用于设置画笔的颜色JPopupMenu pop = new JPopupMenu();JMenuItem chooseColor = new JMenuItem("选择颜色");//定义一个BufferedImage对象BufferedImage image = new BufferedImage(AREA_WIDTH,AREA_HEIGHT,BufferedImage.TYPE_INT_RGB);//获取image对象的GraphicsGraphics g = image.getGraphics();private JFrame f = new JFrame("简单手绘程序");private DrawCanvas drawArea = new DrawCanvas();private Color foreColor = new Color(255,0,0);public void init(){chooseColor.addActionListener(ae->{//下面代码直接弹出一个模式的颜色选择对话框,并返回用户选择的颜色//foreColor = JColorChooser.showDialog(f,"选择画笔颜色",foreColor);//①//下面代码则弹出一个非模式的颜色选择对话框//并可以分别为“确定”按钮“取消”按钮指定事件监听器final var colorPane = new JColorChooser(foreColor);var jd = JColorChooser.createDialog(f,"选择画笔颜色",false,colorPane,e->foreColor=colorPane.getColor(),null);jd.setVisible(true);});//将菜单项组合成右键菜单pop.add(chooseColor);//将右键菜单添加到drawArea对象中drawArea.setComponentPopupMenu(pop);//将image对象的背景色填充成白色g.fillRect(0,0,AREA_WIDTH,AREA_HEIGHT);drawArea.addMouseMotionListener(new MouseMotionAdapter() {@Overridepublic void mouseDragged(MouseEvent e) {//如果preX和preY大于0if(preX>0&&preY>0){//设置当前颜色g.setColor(foreColor);//绘制从上一次鼠标拖动事件点到本次鼠标拖动事件点的线段g.drawLine(preX,preY,e.getX(),e.getY());}//将当前鼠标事件点的X、Y坐标保存起来preX = e.getX();preY = e.getY();//重回drawArea对象drawArea.repaint();}});//监听鼠标事件drawArea.addMouseListener(new MouseAdapter() {@Overridepublic void mouseReleased(MouseEvent e) {//松开鼠标时,把上一次鼠标拖动X、Y坐标设为-1preX = -1;preY = -1;}});f.add(drawArea);//f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);f.validate();f.setBounds(400,150,AREA_WIDTH,AREA_HEIGHT);f.setVisible(true);System.out.println("----------程序已开始执行----------\n");f.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {System.out.println("----------程序已结束----------");System.exit(0);}});}public static void main(String[] args) {new HandDraw().init();}class DrawCanvas extends JPanel{@Overridepublic void paint(Graphics g) {//将image绘制到该组件上g.drawImage(image,0,0,null);}}
}

在Java6时,JcolorChooser只提供了三种颜色选择方式,上面程序的HSV、CMYK两种颜色选择方式都是新增的。
JFileChooser的功能与AWT中的FileDialog基本相似,也是用于生成“打开文件”、“保存文件”对话框;与FileDialog不同的是,JFileChooser无须依赖本地平台的GUI,它由100%纯Java实现,在所有平台上具有完全相同的行为,并可以在所有平台上具有相同的外观风格。
为了调用JFileChooser来打开一个文件对话框,必须先创建该对话框的实例,JFileChooser提供了多个构造器来创建JFileChooser对象,它的构造器总共包含两个参数。

  • currentDirectory:指定所创建文件对话框的当前路径,该参数既可以是一个String类型的路径,也可以是一个File对象所代表的路径。
  • FileSystemView:用于指定基于该文件系统外观来创建对话框,如果没有指定该参数,则默认以当前文件系统外观创建文件对话框。

JFileChooser并不是JDialog的子类,所以不能使用setVisible(true)方法来显示该文件对话框,这意味着在多个窗口中公用该JFileChooser对象。创建JFileChooser对象时可以指定初始化路径,如下代码所示:

//以当前路径创建文件选择器
var chooser = new JFileChooser(".");

调用JFileChooser的一系列可选的方法对JFileChooser执行初始化操作。JFileChooser大致由如下几个常用方法。

  • setSelectedFile/setSelectedFiles:指定该文件选择器默认的文件(也可以默认选择多个文件)。
//默认选择当前路径下的123.jpg文件
chooser.setSelectedFile(new File("jpg"));
  • setMultiSelectionEnabled(boolean b):在默认情况下,该文件选择器只能选择一个文件,通过调用该方法可以设置允许选择多个文件(设置参数之为true即可)。
  • setFileSelectionMode(int mode):在默认情况下,该文件选择器只能选择文件,通过调用该方法可以设置允许选择文件、路径、文件于路径,设置参数之为:JFileChooser.FILES_ONLY、JFileChooser.DIRECTORLES_ONLY、JFileChooser.FILES_AND_DIRECTORIES。
//设置既可以选择文件,也可以选择路径
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

JFileChooser还提供了一些改变对话框标题、改变按钮标签、改变按钮的提示文本等功能的方法,可以查阅API文档来了解它们。
如果让文件对话框实现文件过滤功能,则需要结合FileFilter类来进行文件过滤。JFileChooser提供了两个方法来安装文件过滤器。

  • addChoosableFileFilter(FileFilter filter):添加文件过滤器。通过该方法允许该文件对话框由多个文件过滤器。
//为文件对话框添加一个文件过滤器
chooser.addChoosableFileFilter(filter);
  • setFileFilter(FileFilter filter):设置文件过滤器。一旦调用了该方法,将导致该文件对话框只有一个文件过滤器。

如果需要改变文件对话框中的视图外观,则可以结合FileView类来改变对话框中文件的视图外观。
调用showXxxDialog方法可以打开文件对话框,通常如下三个方法可用。

  • int showDialog(Component parent,String approveButtonText):弹出文件对话框,该对话框的标、“同意”按钮的文本(默认是“保存”或“取消”按钮)由approveButtonText来指定。
  • int showOpenDialog(Component parent):弹出文件对话框,该对话框具有默认标题,“同意”按钮的文本是“保存”。
    当用户单击“同意”、“取消”按钮,或者直接关闭文件对话框时才可以关闭该文件对话框,关闭该对话框时返回一个int类型的值,分别是:JFileChooser.APPROVE_OPTION、JFileChooser.CANCEL_OPTION和JFileChooser.ERROR_OPTION。如果希望获得用户选择的文件,则通常应该先判断对话框的返回值是否为JFileChooser.APPROVE_OPTION,该选项表明用户单击“打开”或者“保存”按钮。
    JFileChooser提供了如下两个方法来获取用户选择的文件或文件集。
  • File getSelectedFile():返回用户选择的文件
  • File[]getSelectedFiles():返回用户选择的多个文件。
    按上面的步骤,就可以正常地创建一个“打开文件”、保存文件对话框,整个过程非常简单。如果要使用FileFilter类进行文件过滤,或者使用FileView类改变文件的视图风格,则有一点麻烦。
    先看使用FileFilter类来进行文件过滤。Java在java.io包下提供了一个FileFilter接口,该接口主要用于作为File类的listFiles(FileFilter)方法的参数,也是一个进行文件过滤的接口。但此处需要使用位于javax.swing.filechooser包下的FileFilter抽象类,该抽象类包含两个抽象方法。
  • boolean accept(File f):判断该过滤器是否接受给定的文件,只有被该过滤器接受的文件才可以在对应的文件对话框中显示出来。
  • String getDescription():返回该过滤器的描述性文本。

如果程序要使用FileFilter类进行文件过滤,则通常需要扩展该FileFilter类,并重写该类的两个抽象方法,重写accept()方法时就可以指定自己的业务规则,指定该文件过滤器可以接受哪些文件。例如,如下代码:

public boolean accept(File f){//如果该文件是路径,则接受该文件if(f.isDirectory()) return true;//只接受以.gif作为后缀的文件if(name.endsWith(".gif")){return true;}return false;
}

在默认情况下,JFileChooser总会在文件对话框的“文件类型”下拉列表中增加“所有文件”选项,但可以调用JFileChooser的setAcceptAllFileFilterUsed(false)来取消显示该选项。
FileView类用于改变文件对话框中文件的视图风格,FileView类也是一个抽象类,通常程序需要扩展该抽象类,并由选择性地重写它所有包含的如下几个抽象方法。

  • String getDescription(File f):返回指定文件的描述。
  • Icon getIcon(File f):返回指定文件在JFileChooser对话框中的图标。
  • String getName(File f):返回指定文件的文件名。
  • String getTypeDescription(File f):返回指定文件所属文件类型的描述。
  • Boolean isTraversable(File f):当文件是目录时,返回该目录是否可遍历的。
    与重写FileFilter抽象方法类似的是,重写这些方法实际上就是为文件选择器对话框指定自定义的外观风格。通常可以通过重写getIcon()方法来改变文件对话框中的文件图标。
    下面程序是一个简单的图片查看工具程序,该程序综合使用了上面所介绍的各知识点。

import javax.imageio.plugins.tiff.FaxTIFFTagSet;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.io.File;
import java.security.cert.Extension;
import java.util.ArrayList;public class ImageViewer{//定义图片预览组件的大小final int PREVIEW_SIZE = 1920;final int PREVIEW_SIZE2 = 1080;JFrame jf = new JFrame("简单图片查看器");JMenuBar menuBar = new JMenuBar();//该label用于显示图片JLabel label = new JLabel();//以当前路径创建文件选择器JFileChooser chooser = new JFileChooser(".");JLabel accessory = new JLabel();//定义文件过滤器ExtensionFileFilter filter = new ExtensionFileFilter();public void init(){//----------下面开始初始化JFileChooser的相关属性----------//创建一个FileFilterfilter.addExtension("jpg");filter.addExtension("jpeg");filter.addExtension("gif");filter.addExtension("png");filter.setDescription("图片文件(*.jpg,*.jpeg*.gif,*.png");chooser.addChoosableFileFilter(filter);//精致“文件类型”下拉列表中显示“所有文件”的选项chooser.setAcceptAllFileFilterUsed(false);//为文件选择器指定一个预览图片的附件chooser.setAccessory(accessory);//设置预览图片组件的大小和边框accessory.setPreferredSize(new Dimension(PREVIEW_SIZE,PREVIEW_SIZE2));accessory.setBorder(BorderFactory.createEtchedBorder());//用于检测被选择文件的改变事件chooser.addPropertyChangeListener(event->{//JFileChooser的被选中文件已经发生了改变if(event.getPropertyName()==JFileChooser.SELECTED_FILE_CHANGED_PROPERTY){//获取用户选择的新文件var f = (File) event.getNewValue();if(f==null){accessory.setIcon(null);return;}//将所有文件导入ImageIcon对象中var icon = new ImageIcon((f.getPath()));//如果图像太大,则缩小它if(icon.getIconWidth()>PREVIEW_SIZE){icon = new ImageIcon(icon.getImage().getScaledInstance(PREVIEW_SIZE,-1,Image.SCALE_DEFAULT));}//改变accessory Label的图标accessory.setIcon(icon);}});//----------下面代码开始为该窗口安装菜单----------var menu = new JMenuItem("文件");menuBar.add(menu);var openItem = new JMenuItem("打开");menu.add(openItem);//单击openItem菜单项显示“打开文件”对话框openItem.addActionListener(event->{//设置文件对话框的当前路径//chooser.setCurrentDirectory(new File("."));//显示文件对话框int result = chooser.showDialog(jf,"打开图片文件");//如果用户选择了APPROVE(同意)按钮,即打开,保存的等效按钮if(result==JFileChooser.APPROVE_OPTION){String name = chooser.getSelectedFile().getPath();//显示指定图片label.setIcon(new ImageIcon(name));}});var exitItem = new JMenuItem("Exit");menu.add(exitItem);//为退出菜单绑定事件监听器exitItem.addActionListener(e->System.exit(0));jf.setJMenuBar(menuBar);//添加用于显示图片的JLabel组件jf.add(new JScrollPane(label));jf.setBounds(0,0,1920,1080);jf.setVisible(true);jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {//这里忽略了图标的设置,所以程序并不完整可以成功打开对话框即可new ImageViewer().init();}class ExtensionFileFilter extends FileFilter{private String description;private ArrayList<String> extensions = new ArrayList<>();//自定义方法,用于添加文件扩展名public void addExtension(String extension){if(!extension.startsWith(".")){extension="."+extension;extensions.add(extension.toLowerCase());}}//用于设置该文件过滤器的描述文本public void setDescription(String aDescription){description = aDescription;}@Override//继承FileFilter类必须实现的抽象方法,判断文件过滤器是否接受该文件public boolean accept(File f) {//如果该文件是路径,则接受该文件if(f.isDirectory())return true;//将文件名转为小写(全部转为小写后比较,用于忽略文件名大小写)String name = f.getName().toLowerCase();//遍历所有可接受的扩展名,如果扩展名相同,该文件就可接受for(var extendsion:extensions){if(name.endsWith(extendsion)){return true;}}return false;}@Override//继承FileFilter类必须实现的抽象方法,返回该文件过滤器的描述文本public String getDescription() {return description;}}
}

这里代码并不全,看个大概就行。

使用JOptionPane

通过JOptionPane可以非常方便地创建一些简单的对话框,Swing已经为这些对话框添加了相应的组件,无须程序员手动添加组件,无须程序员手动添加组件。JOptionPane提供了如下4个方法来创建对话框。

  • showMessageDiglog/showInternalMessageDialog:消息对话框,告知用户某事已发生,用户只能单击“确定”按钮,类似于JavaScrpit的alert函数。
  • showConfirmDialog/showInternalConfirmDialog:确定对话框,向用户确定某个问题,用户可以选择yes、no、cancel等选项、类似于JavaScrpit的comfirm函数。该方法返回用户点击了哪个按钮。
  • showInputDialog/showInternalInputDialog:输入对话框,提示要求输入某些信息,类似于JavaScript的prompt函数。该方法返回用户输入的字符串。
  • showOptionDialog/showInternalOptionDialog:自定义选择对话框,允许使用自定义选项,可以取代showConfirmDialog所产生的对话框,只是用起来更复杂。

JOptionPane产生的所有对话框都是模式的,在用户完成与对话框的交互之前,showXxxDialog方法都将一直阻塞当前线程。
JOptionPane所产生的对话框总是具有如同所示的布局。

上面这些方法都提供了相应的showInternalXxxDialog版本,这种方法以InternalFrame的方式打开对话框。关于什么是InternalFrame方式,参考下一节JInternalFrame的介绍。

  1. 输入区
    如果创建的对话框无须接受用户输入,则输入区不存在。输入区组件可以是普通文本组件,也可以是下拉列表组件。
    如果调用上面的showInternalXxxDialog()方法时指定了一个数组类型的selectionValues参数,则输入区包含一个下拉列表框组件。
  2. 图标区
    左上角的图标会随创建的对话框所包含消息类型的不同而不同,JOptionPane可以提供如下5种消息类型
  • ERROP_MESSAGE:错误信息,其图标时一个红色的X图标。
  • INFORMATION_MESSAGE:普通消息,其默认图标是蓝色的感叹号。
  • WARNING_MESSAGE:警告消息,其默认图标是黄色感叹号。
  • QUESTION_MESSAGE:普通消息,没有默认图标。

实际上,JOptionPane的所有showXxxDialog()方法都可以提供一个可选的icon参数,用于指定该对话框的图标。
调用showXxxDialog方法时还可以指定一个可选的title参数,该参数指定所创建对话框的标题。

  1. 消息区
    不管是哪种对话框,其消息区总是存在的,消息区的内容通过message参数来指定,根据message参数的类型不同,消息区显示的内容也是不同的。该message参数可以是如下几种类型。
  • String类型:系统将该字符串对象包装成JLabel对象,然后显示在对话框中。
  • Icon:该Icon被包装成JLabel后作为对话框的消息。
  • Component:将该Component在对话框的消息区中显示出来。
  • Object[]:对象数组被解释为在纵向排列的一系列message对象,每个message对象根据其实际类型又可以是字符串、图标、组件、对象数组等。
  • 其他类型:系统调用该对象的toString()方法返回一个字符串,并将该字符串对象保证成JLabel对象,然后显示在对话框中。

大部分时候对话框的消息区都是普通字符串,但使用Component作为消息区组件则更加灵活,因为该Component参数几乎可以是任何对象,从而可以让对话框的消息区包含任何内容。
如果用户希望消息区的普通字符串能换行,则可以使用"\n"字符来实现换行。

  1. 按钮区
    对话框底部的按钮区也是一定存在的,但所包含的按钮则会随对话框的类型、选项类型而改变。对于调用showInputDialog()和showMessageDialog()方法得到的对话框,底部总是包含“确定”和“取消”两个标准按钮。
    对于showConfirmDialog()所打开的确认对话框,则可以指定一个整数的类型和optionType参数,该参数可以取如下几个值。
  • DEFAULT_OPTION:按钮区只包含一个“确定”按钮。
  • YES_NO_OPTION:按钮区包含“是”、“否”两个按钮。
  • YES_NO_CANCEL_POTION:按钮区包含“是”、“否”、“取消”三个按钮。
  • OK_CANCEL_OPTION:按钮区包含“确定”、“取消”两个按钮。

如果使用showPtionDialog方法来创建选项对话框,则可以通过指定一个Object[]类型的options参数来设置按钮区能使用的选项按钮。与前面的message参数类似的是,options数组的数组元素可以是如下几种类型。

  • String类型:使用该字符串来创建一个JButton,并将其显示在按钮区。
  • Icon:使用该Icon来创建一个JButton,并将显示在按钮区。
  • Component:直接将该组件显示在按钮区。
  • 其他类型:系统调用该对象的toString()方法返回一个字符串,并使用该字符串来创建一个JButton,并将其显示在按钮区。
    当用户在对话框交互结束后,不同类型对话框的返回值如下。
  • showMessageDialog:无返回值。
  • showInputDialog:返回用户输入或选择的字符串。
  • showConfirmDialog:返回一个整数代表用户选择的选项。
  • showOptionDialog:返回一个整数代表用户选择的选项,如果用户选择第一项,则返回0,如果选择第二项,则返回1…以此类推。
    对showConfirmDialog所产生的对话框,有如下几个返回值。
  • YES_OPTION:用户单击了“是”按钮后返回。
  • NO_OPTION:用户单击了“否”按钮后返回。
  • CANCEL_OPTION:用户单击了“取消”按钮后返回。
  • OK_OPTION:用户单击了“确定”按钮后返回。
  • CLOSED_OPTION:用户单击了对话框右上角的“X”按钮后返回。

对于showOptionDialog方法所产生的对话框,也可能返回一个CLOSED_OPTION值,当用户单击了对话框右上角的“X”按钮后将返回该值。

下面程序允许使用JOptionPane来弹出各种对话框。

import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;public class JOptionPaneTest{JFrame jf = new JFrame("测试JOptionPane");private ButtonPanel messagePane;private ButtonPanel messageTypePanel;private ButtonPanel msgPanel;private ButtonPanel confirmPanel;private ButtonPanel optionsPanel;private ButtonPanel inputPanel;private String messageString = "消息区内容";private Icon messageIcon = new ImageIcon("C:\\Users\\YueDie\\Pictures\\Saved Pictures\\依睐2021.jpg");private Object messageObject = new Date();private Component messageComponent = new JButton("组件信息");private JButton msgBn = new JButton("消息对话框");private  JButton confirmBn = new JButton("确定对话框");private JButton inputBn = new JButton("输入对话框");private JButton optionBN = new JButton("选项对话框");public void init(){var top = new JPanel();top.setBorder(new TitledBorder(new EtchedBorder(),"对话框的通用选项",TitledBorder.CENTER,TitledBorder.TOP));top.setLayout(new GridLayout(1,2));//消息类型Panel,该Panel中的选项决定对话框的图标messageTypePanel = new ButtonPanel("选择消息的类型",new String[]{"ERROR_MESSAGE","information_message","WARNING_MESSAGE","QUESTION_MESSAGE","PLAIN_MESSAGE"});//消息内容类型Panel,该Panel中的选项决定对话框消息区的内容messagePane = new ButtonPanel("选择消息内容的类型",new String[]{"字符串消息","图标消息","组件信息","普通对象消息","Object[]消息"});top.add(messageTypePanel);top.add(messagePane);var bottm = new JPanel();bottm.setBorder(new TitledBorder(new EtchedBorder(),"弹出不同的对话框",TitledBorder.CENTER,TitledBorder.TOP));bottm.setLayout(new GridLayout(1,4));//创建用于弹出确认对话框的PanelconfirmPanel = new ButtonPanel("消息对话框",null);msgBn.addActionListener(new ShowAction());msgPanel.add(msgBn);//创建用于弹出确定对话框的PanelconfirmPanel = new ButtonPanel("确定对话框",new String[]{"DEFAULT_OPTION","YES_NO_OPTION","YES_ON_CANCEL_OPTION","OK_CEANCEL_OPTION"});confirmBn.addActionListener(new ShowAction());inputPanel.add(inputBn);//创建用于弹出输出对话框的PanelinputPanel = new ButtonPanel("输入对话框",new String[]{"单行文本框","下拉列表选择框"});optionBN.addActionListener(new ShowAction());inputPanel.add(inputBn);//创建用于弹出选项对话框的PaneloptionsPanel = new ButtonPanel("选项对话框",new String[]{"字符串选项","图标选项","对象选项"});optionBN.addActionListener(new ShowAction());optionsPanel.add(optionBN);bottm.add(msgPanel);bottm.add(confirmPanel);bottm.add(inputPanel);bottm.add(optionsPanel);var box = new Box(BoxLayout.Y_AXIS);box.add(top);box.add(bottm);jf.add(box);jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);jf.pack();jf.setVisible(true);}//根据用户选择返回消息private Object getMessage(){switch (messagePane.getSelection()){case "字符串消息":return messageString;case "图标消息":return messageIcon;case "组件消息":return messageComponent;case "普通对象消息":return messageObject;default:return new Object[]{messageString,messageIcon,messageObject,messageComponent};}}//根据用户选择返回会选项类型private int getOptionType(){switch (confirmPanel.getSelection()){case"DEFAULT_OPTION":return JOptionPane.DEFAULT_OPTION;case"YES_NO_OPTION":return JOptionPane.YES_NO_OPTION;case"YES_NO_CANCEL_OPTION":return JOptionPane.YES_NO_CANCEL_OPTION;default:return JOptionPane.OK_CANCEL_OPTION;}}private int getDialogType(){switch (messageTypePanel.getSelection()){case "ERROR_MESSAGE":return JOptionPane.ERROR_MESSAGE;case "INFORMATTON_MESSAGE":return JOptionPane.INFORMATION_MESSAGE;case "WARNING_MESSAGE":return JOptionPane.WARNING_MESSAGE;case "QUESTION_MESSAGE":return JOptionPane.QUESTION_MESSAGE;default:return JOptionPane.PLAIN_MESSAGE;}}private class ShowAction implements ActionListener{@Overridepublic void actionPerformed(ActionEvent e) {switch (e.getActionCommand()){case"确认对话框":JOptionPane.showConfirmDialog(jf,getMessage(),"确定对话框",getOptionType(),getDialogType());break;case"输入对话框":if(inputPanel.getSelection().equals("单行文本框")){JOptionPane.showInputDialog(jf,getMessage(),"输入对话框",getDialogType());}else{JOptionPane.showInputDialog(jf,getMessage(),"输入对话框",getDialogType(),null,new String[]{"轻量级Java EE企业应用实战","疯狂Java讲义"},"疯狂Java讲义");}break;case "消息对话框":JOptionPane.showMessageDialog(jf,getMessage(),"消息对话框",getDialogType());break;case "选项对话框":JOptionPane.showOptionDialog(jf,getMessage(),"选择对话框",getOptionType(),getDialogType(),null,null,"a");break;}}}class ButtonPanel extends JPanel{private ButtonGroup group;public ButtonPanel(String title,String[]options){setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),title));setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));group = new ButtonGroup();for(var i =0;options!=null&&i<options.length;i++){var b = new JRadioButton(options[i]);b.setActionCommand(options[i]);add(b);group.add(b);b.setSelected(i==0);}}//定义一个方法,用于返回用户选择的选项public String getSelection(){return group.getSelection().getActionCommand();}}public static void main(String[] args) {new JOptionPaneTest().init();}
}

Swing中的特殊容器

Swing提供了一些具有特殊功能的容器,这些特殊容器可以用于创建一些更复杂的用户界面。下面将依次介绍这些特殊容器。

使用JSplitPane

JSplitPane用于创建一个分割面板,它可以将一个组件(通常是一个容器)分割成两个部分,并提供一个风割条,用户可以拖动该分割条来调整大小。

new JSplitPane(方向,左/上组件,右/下组件);

JSplitPane默认关闭连续布局特性,因为使用连续布局需要不断重回两边组件,因此运行效率很低。如果需要打开指定JSplitPane面板的连续布局特性,则可以使用如下代码:

//打开JSplitPane的连续布局特性
jsp.setContinuouseLayout(true);

上下分割面板的分割条中还有两个三角箭头,这两个箭头为被称为“一触即展”键,当用户单击某个三角箭头时,将看到箭头所指的组件慢慢缩小到没有,而另一个组件则扩大到占据整个面板。如果需要打开“一触即展”特性,使用如下代码即可。

//打开“一触即展”特性
jsp.setOneTouchExpandable(true);

JSplitPane分割还有如下几个可用方法来设置该面板的相关特性。

  • setDividerLocation(double proportionalLocation):设置分割条的位置为JSplitPane的某个百分比。
  • setDividerLocation(int location):通过像素设置分割条的位置。
  • setDividerSize(int newSize):通过像素值设置分割条的大小。
  • setLeftComponent(Component comp)/setBottomComponent(Component comp):将指定组件放置到分割面板的右边或者下面。

使用JTabbedPane

JTabbedPane可以很方便地在窗口上放置多个标签页,每个标签页相当于获得了一个与外部容器具有相同大小的组件摆放区域。通过这种方式,就可以在一个容器里放置更多的组件,例如右键桌面上的“我的电脑”图标,在弹出的快捷菜单里单击“属性”菜单项,就可以看到一个“系统属性”对话框。
这个对话框里包含了7个标签页
如果需要使用JTabbedPane在窗口上创建标签页,则可以按如下步骤进行。
创建一个JTabbedPane在窗口上创建标签页,则可以按如下步骤进行。

  • tabPlacement:该参数指定标签页标题的放置位置,例如前面介绍的“系统属性”对话框里标签也的标题放在窗口顶部。Swing支持将标签页标题放在窗口中的4个方位:TOP(顶部)、LEFT(左边)、BOTTOM(下部)和RIGHT(右边)。
  • tabLayoutPolicy:指定标签页的布局策略。当窗口不足以在同一行摆放所有的标签页标题时,Swing有两种处理方式——将标签页标题换行(JTabbedPane.WRAP_TAB_LAYOUT)排列,或者使用滚动条来控制标签页标题的显示(SCROLL_TAB_LAYOUT)。

即使创建JTabbedPane时没有指定者两个参数,程序也可以在后面改变JTabbedPane的者两个属性。例如,通过setTabLayoutPolicy()方法改变标签页标题的布局策略,使用setTabPlacement()方法设置标签页标题的放置位置。
例如,下面代码创建一个JTabbedPane对象,该JTabbedPane的标签页标题位于窗口左侧,当窗口的一行不能摆放所有的标签页标题时,JTabbedPane将采用换行方式来排列标签页标题。

var tabPane = new JTabbedPane(JTabbedPane.LEFT,JTabbedPane.WRAP_TAB_LAYOUT);

调用JTabbedPane对象的addTab()、insertTab()、setComponentAt()、removeTabAt()方法来增加、插入、修改和删除标签页。其中addTab()方法总是在最前面增加标签页,而insertTab()、setComponentAt()、removeTabAt()方法都可以使用一个index参数,表示在指定位置插入标签页,修改指定位置的标签页,删除指定位置的标签页。
添加标签页时可以指定该标签页的标题(title)、图标(icon),以及该Tab页面的组件(component)及提示信息(tip),这4个参数都可以是null;如果某个参数是null,则对应的内容为空。
不管使用增加、插入、修改哪种操作来改变JTabbedPane中的标签页,都是传入一个Component组件作为标签页。也就是说,如果希望在某个标签页内放置更多的组件,则必须先将这些组件放置到一个容器(例如JPanel)里,然后将该容器设置为JTabbedPane指定位置的组件。

不要使用JTabbedPane的add()方法来添加组件,该方法是JTabbedPane重写Container容器中的add()方法,如果使用该add()方法来添加Tab页面,每次添加的标签页都会直接覆盖原有的标签页。

如果需要让某个标签页显示出来,则可以通过调用JTabbedPane的setSelectdIndex()方法来实现。例如如下代码:

//设置第三个Tab页面处于显示状态
tabPane.setSelectedIndex(2);
//设置最后一个Tab页面处于显示状态
tabPane.setSelectedIndex(tabPanel.getTabCount()-1);

这里因为时间特殊原因,所以Swing就此完结,Swing学懂本文章的内容即可,后续内容必要不大,可针对性学习,所以我们直接进入下一章!

第十二章 Swing编程相关推荐

  1. Boost 第十二章 并发编程

    本文章所有内容源于<BOOST程序库完全开发指南:深入C++"准"标准库(第3版)>第十二章 本章内容包括Boost库中的三个并发编程组件.atomic,它实现了C++ ...

  2. 深入理解计算机系统 第十二章 并发编程

    如果逻辑控制流在时间上重叠,那么它们就是并发的(concurrent) 这种常见的现象称为并发(concurrency),出现在计算机系统的许多不同层面上. 并发不仅仅局限于内核,它也可以在应用程序中 ...

  3. Java 第十二章.网络编程

    网络编程 1.概述 2.网络通信要素概述 3.通信要素1:IP和端口号 3.1IP 3.2端口号 4.通信要素2:网络协议 5.TCP网络编程 6.UDP网络编程 7.URL编程 1.概述 Java是 ...

  4. 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条

    http://blog.csdn.net/terryzero/article/details/3797782 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条 标签: swing编程 ...

  5. 高级shell编程笔记(第十二章 外部过滤器,程序和命令)

    第十二章 外部过滤器,程序和命令 标准的UNIX命令使得脚本更加灵活.通过简单的编程结构把shell指令和系统命令结合起来,这才是脚本能力的所在. 12.1 基本命令 新手必须掌握的初级命令 ls 基 ...

  6. 最优控制问题matlab编程实例,第十二章用matlab解最优控制问题及应用实例.ppt

    第十二章用matlab解最优控制问题及应用实例 第十二章 用MATLAB解最优控制问题及应用实例 第十二章 用MATLAB解最优控制问题及应用实例 12.1 MATLAB工具简介 12.2 用MATL ...

  7. linux脚本求命令行上整数和,《Linux命令行与shell脚本编程大全》 第二十二章 学习札记...

    <Linux命令行与shell脚本编程大全> 第二十二章 学习笔记 第二十二章:使用其他shell 什么是dash shell Debian的dash shell是ash shell的直系 ...

  8. 程序员编程艺术第一 二十二章集锦与总结(教你如何编程)

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 程序员编 ...

  9. 程序员编程艺术第一~二十二章集锦与总结(教你如何编程)

    程序员编程艺术第一~二十二章集锦与总结(教你如何编程) 作者:July.编程艺术室. 出处:http://blog.csdn.net/v_JULY_v . 题记 好久没更新博客了,虽只有一个月,但对我 ...

最新文章

  1. python导入csv文件是如何预览后10行-使用python中的csv reader只读取前N行csv文件
  2. linux 校验文件生成,在Linux中了解如何使用MD5校验和生成和验证文件
  3. leetcode 46. 全排列 思考分析
  4. Tyche 2147 旅行
  5. bzoj4974 字符串大师
  6. 2个阶乘什么意思_两个阶乘号是什么意思,-双阶乘-数学-滕诓芳同学
  7. python自动生成和文件同名的文件夹,并将文件移动到同名文件夹中
  8. 博科光纤交换机配置及管理 CLI篇
  9. Lodop,前端自定义打印
  10. Flink学习笔记之DataStream API 简介
  11. Windows10实用技巧-固定快捷方式到磁贴菜单方式
  12. 14款超时尚的HTML5时钟动画
  13. Android黄油刀插件使用记录
  14. Android RSA加密解密
  15. Docker定制化Python基础镜像
  16. Mapper未生成impl
  17. python3中 operator模块用法介绍
  18. java apk签名证书_Android签名证书的生成
  19. UNOVO联永羲和开源项目
  20. (四)Bug的生命周期

热门文章

  1. python中文词云图代码_Python文本处理NLP:分词与词云图
  2. mc用什么版本的java_MC的1.16版本最大更新是什么?不是下界内容,是JAVA和基岩版同步...
  3. 【Go语言刷题篇】Go完结篇|函数、结构体、接口、错误入门学习
  4. LED可见光通信——实现声音信号的传输
  5. 感染病毒WannaCry? 信息安全有华胜信泰
  6. AOI自动光学检测仪在各工序中的应用
  7. 项目文章|原核转录组测序分析揭示微藻对镉胁迫的短期和长期响应分子机制
  8. 开好餐饮店,“引拓留升新”一个不能少
  9. android脱出游戏,脱出游戏Mimic游戏(攻略)
  10. V2G背景201707051926