Java8 API 入门手册(二)

原文:Beginning Java 8 APIs

协议:CC BY-NC-SA 4.0

二、Swing 组件

在本章中,您将学习

  • 什么是秋千组件
  • 不同类型的秋千组件
  • 如何验证文本组件中的输入
  • 如何使用菜单和工具栏
  • 如何使用 JTable 和 JTree 组件编辑表格和分层数据
  • 如何使用自定义和标准对话框
  • 如何自定义组件的属性,如颜色、边框、字体等。
  • 如何绘制组件以及如何绘制形状
  • 立即绘画和双缓冲

什么是 Swing 组件?

Swing 提供了大量组件来构建 GUI。在 Java 程序中,Swing 组件是类的一个实例。JComponent类在javax.swing包中,它是所有 Swing 组件的基类。其类层次如图 2-1 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-1。

The class hierarchy for the JComponent class

该类继承自java.awt.Container类,而后者又继承自java.awt.Component类。JComponent是一个抽象类。您不能直接实例化它。你必须使用它的一个子类,比如JButtonJTextField等。

由于JComponent类继承自Container类,每个JComponent也可以作为一个容器。例如,一个JButton可以充当另一个JButton或其他JComponent的容器。除非 Swing 库已经提供了一个JComponentJPanel作为容器使用,否则你不会使用(或需要)一个JComponent作为容器。但是,这种层次结构允许您编写如下代码:

JButton btn = new JButton("Container JButton");

btn.setLayout(new FlowLayout());

btn.add(new JButton("Container JButton. Do not use."));

作为所有 Swing 组件的基类,JComponent类提供了以下基本功能,这些功能由所有 Swing 组件继承。我将在本章后面详细讨论这些特性。

  • 它为工具提示提供支持。工具提示是当鼠标指针在组件上停留一定时间时显示的简短文本。
  • 它支持可插拔的外观。与组件外观(绘画和布局)和感觉(响应用户与组件的交互,如事件处理)相关的所有方面都由 UI delegate 对象处理。像JComponent类一样,javax.swing.plaf包中的ComponentUI是用作 UI 委托对象的基类。JComponent的每个后代使用不同种类的 UI 委托对象,该对象是从ComponentUI类派生的。比如 a JButton使用ButtonUI,a JLabel使用LabelUI,a JToolTip使用ToolTipUI作为 UI 委托。
  • 它支持在 Swing 组件周围添加边框。边界可以是任何一种预定义的类型(LineBevelTitledEtched等)。)或自定义边框类型。
  • 它为可访问性提供支持。应用的可访问性是指不同能力和残疾的人可以使用它的程度。例如,它可以为视力受损的用户以更大的字体显示文本。这本书不包括 Java 可访问性 API。
  • 它支持双缓冲,有助于屏幕上的平滑绘画。当在屏幕上擦除和绘制组件时,可能会出现闪烁。为了避免任何闪烁,它提供了一个离屏缓冲区。擦除和重画(更新组件)在离屏缓冲区中完成,离屏缓冲区被复制到屏幕上。
  • 它将键盘上的一个键绑定到一个 Swing 组件。您可以用一个ActionListener对象将键盘上的任何键绑定到一个组件。当那个键被按下时,关联的ActionListeneractionPerformed()方法被调用。
  • 当使用布局管理器时,它为布局组件提供支持。它包含获取和设置组件的最小、首选和最大大小的方法。一个JComponent的三种不同的字体大小设置为布局管理器决定JComponent的大小提供了一个提示。

它允许将多个任意属性(key-value对)关联到一个 Swing 组件,并检索这些属性。JComponent 的putClientProperty()getClientProperty()方法允许处理组件属性。

表 2-1 列出了JComponent类的一些常用方法,这些方法可用于所有 Swing 组件。

表 2-1。

Commonly Used Methods of the JComponent Class and Their Descriptions

| 方法名 | 描述 | | --- | --- | | `Border getBorder()` | 返回组件的边框,如果组件没有边框,则返回`null`。 | | `void setBorder(Border border)` | 设置组件的边框。 | | `Object getClientProperty(Object key)` | 返回与指定键关联的值。该值必须使用`putClientProperty (Object key, Object value)`方法设置。 | | `void putClientProperty(Object key, Object value)` | 向组件添加任意键-值对。 | | `Graphics getGraphics()` | 返回组件的图形上下文对象,该对象可用于在组件上绘图。 | | `Dimension getMaximumSize()``Dimension getMinimumSize()``Dimension getPreferredSize()``Dimension getSize(Dimension d)``void setMaximumSize(Dimension d)``void setMinimumSize(Dimension d)``void setPreferredSize(Dimension d)``void setSize(Dimension d)``void setSize(int width, int height)` | 获取/设置组件的最大、最小、首选和实际大小。当您调用`getSize()`方法时,您可以传递一个`Dimension`对象,大小将存储在其中,并返回相同的对象。这样,该方法可以避免创建新的`Dimension`对象。如果您传递了`null`,它将创建一个`Dimension`对象,在其中存储实际大小,并返回该对象。 | | `String getToolTipText()` | 返回此组件的工具提示文本。 | | `void setToolTipText(String text)` | 设置工具提示文本,当鼠标指针在组件上暂停一段指定的时间后,将显示该文本。 | | `boolean isDoubleBuffered()` | 如果组件使用双缓冲,则返回`true`。否则返回`false`。 | | `void setDoubleBuffered(boolean db)` | 设置组件是否应该使用双缓冲来绘制。 | | `boolean isFocusable()` | 如果组件可以获得焦点,则返回`true`。否则返回`false`。 | | `void setFocusable(boolean focusable)` | 设置组件是否可以获得焦点。 | | `boolean isVisible()` | 如果组件可见,则返回`true`。否则返回`false`。 | | `void setVisible(boolean v)` | 将组件设置为可见或不可见。 | | `boolean isEnabled()` | 如果组件被启用,则返回`true`。否则返回`false`。 | | `void setEnabled(boolean e)` | 启用或禁用组件。默认情况下,组件处于启用状态。启用的组件响应用户输入并生成事件。 | | `boolean requestFocus(boolean temporary)``boolean requestFocusInWindow()` | `requestFocus()`和`requestFocusInWindow()`方法都要求组件获得输入焦点。您应该使用`requestFocusInWindow()`方法而不是`requestFocus()`方法,因为它的行为在所有平台上都是一致的。布尔参数指示请求是否是临时的。如果请求肯定会失败,这些方法将返回`false`。如果请求成功,除非被否决,否则它们返回`true`。 | | `boolean isOpaque()` | 如果`JComponent`不透明,则返回`true`。否则,它返回`false`。 | | `void setOpaque(boolean opaque)` | 设置`JComponent`的不透明度。如果一个`JComponent`是不透明的,它将绘制其边界内的每个像素。如果它不是不透明的,它可能会在其边界内绘制一些像素或不绘制像素,从而允许其后面的像素显示出来。默认情况下,`JComponent`类将该值设置为`false`,使其透明。但是,其子类别的不透明度默认值取决于外观和感觉以及特定的组件。 |

表 2-2 列出了可用于所有 Swing 组件的一些常用事件。每个 Swing 组件还支持一些专门的事件。当我讨论这些组件时,我将解释那些专门的事件。注意,表中列出的所有事件都遵循XxxEvent类、XxxListener接口、XxxAdapter抽象类和addXxxListener()方法命名约定,除非另有说明。也就是说,要处理组件的Xxx事件,需要调用它的addXxxListener(XxxListener l)方法,并传递实现XxxListener接口的类的对象。一个XxxListener接口中的所有方法都接受一个XxxEvent类型的参数。如果XxxListener中有不止一个方法,则有一个对应的XxxAdapter抽象类实现XxxListener接口,并为XxxListener方法提供空实现。

表 2-2。

Some Commonly Used Events Available for All Swing Components

| 事件类别名称 | 事件监听器接口 | 描述 | | --- | --- | --- | | `ComponentEvent` | `ComponentListener`方法:`componentShown()` `componentHidden()` `componentResized()` `componentMoved()` | 当组件的可见性、大小或位置更改时,会发生事件。 | | `FocusEvent` | `FocusListener`方法:`focusGained()` `focusLost()` | 当组件获得或失去焦点时,会发生事件。 | | `KeyEvent` | `KeyListener`方法:`keyPressed()` `keyReleased()` `keyTyped()` | 当组件获得焦点并且按下、释放或键入键盘上的某个键时,会发生事件。当您按下或释放键盘上的任何键时,都会触发按键按下和释放事件。仅当键入 Unicode 字符时,才会触发 key typed 事件。例如,当您在键盘上键入字符“a”时,按下一个键、键入一个键和释放一个键事件将按顺序触发。 | | `MouseEvent` | `MouseListener`方法:`mousePressed()` `mouseReleased()` `mouseClicked()` `mouseEntered()` `mouseExited()` | 当在组件上按下、释放和单击鼠标时,会触发鼠标按下、释放和单击事件。当鼠标进入组件的边界时,会触发鼠标进入事件。当鼠标离开组件边界时,触发鼠标退出事件。注意,`MouseAdapter`类实现了三个接口:`MouseListener`、`MouseMotionListener`和`MouseWheelListener`(参见下面的两个鼠标事件)。 | | `MouseEvent` | `MouseMotionListener`方法:`mouseDragged()` `mouseMoved()`注意:它在事件方法中使用一个`MouseEvent`对象作为参数。没有相应的`MouseMotionEvent`类。 | 当您通过按下鼠标按钮将鼠标拖动到组件上时,会触发鼠标拖动事件。即使鼠标离开组件,鼠标拖动事件也会继续触发,直到松开鼠标按钮。当您在组件上移动鼠标,但没有按下鼠标按钮时,会触发鼠标移动事件。您可以使用`MouseAdapter`或`MouseMotionAdapter`抽象类为该事件编写监听器对象。 | | `MouseWheelEvent` | `MouseWheelListener`方法:`mouseWheelMoved()` | 当组件处于焦点时,如果旋转鼠标滚轮,则会触发鼠标滚轮移动事件。如果鼠标没有滚轮,则不会触发此事件。 |

一开始,Java 提供了 AWT(抽象窗口工具)来构建 GUI。所有 AWT 组件都在java.awt包中,它们使用对等体来处理它们的工作方式。如果使用 AWT 创建按钮,操作系统会创建一个相应的按钮,称为,用于处理 AWT 按钮的大部分工作方式。因为每个 AWT 组件都有一个对等体,所以 AWT 组件被称为组件。

在 JDK 1.2 中,Swing 作为 AWT 的替代成为 Java 类库的一部分。大多数 Swing 组件不使用对等体,因此,它们被称为组件。对于每个 AWT 组件,您都会找到相应的 Swing 组件。Swing 提供了一些 AWT 中没有的附加组件,比如JTabbedPane。Swing 组件的名称带有前缀J。例如,为了使用按钮组件,AWT 提供了一个Button类,Swing 提供了一个JButton类。为了显示装饰窗口,AWT 提供了一个Frame类,Swing 提供了一个JFrame类。Swing 中有些组件还是重量级组件。毕竟,基本的 GUI 功能总是由操作系统提供的。Swing 中的所有顶层容器(JFrameJDialogJWindowJApplet)都是重量级组件,它们都有对等体。除了顶级容器,Swing 组件是轻量级组件。Swing 的轻量级组件使用它们的重量级容器区域进行绘制。Swing 的轻量级组件是用 Java 编写的。

AWT 的主要缺点是 GUI 在不同的操作系统上可能看起来不同。AWT 支持在所有平台上都可用的特性。由于对操作系统对等体的依赖,AWT 只能提供矩形组件。Swing 轻量级组件不存在这些限制。在 Swing 中,您可以拥有任何形状的组件,因为 Swing 使用 Java 代码绘制轻量级组件。Swing 提供了可插拔的外观和感觉,因此您不会局限于只看到操作系统绘制的 GUI 组件。虽然允许在同一个应用中混合使用 Swing 和 AWT 组件,但这是不可取的。混合使用它们可能会导致难以调试的问题。这本书只涉及秋千。

在接下来的部分中,我将详细讨论几个 Swing 组件。

JButton

JButton也称为按钮或命令按钮。用户按下或点击一个JButton来执行一个动作。通常,它会显示描述单击时所执行操作的文本。文本也称为标签。一个JButton也支持显示图标。你可以使用表 2-3 中列出的一个构造函数来创建一个。

表 2-3。

Constructors of the JButton Class

| 构造器 | 描述 | | --- | --- | | `JButton()` | 创建一个没有任何标签或图标的`JButton`。 | | `JButton(String text)` | 创建一个`JButton`并将指定的文本设置为其标签。 | | `JButton(Icon icon)` | 创建一个带有图标但没有标签的`JButton`。 | | `JButton(String text, Icon icon)` | 用指定的标签和图标创建一个`JButton`。 | | `JButton(Action action)` | 用一个`Action`对象创建一个`JButton`。在本节的后面,您将会看到一个使用`Action`对象作为`JButton`的例子。 |

您可以创建一个文本为CloseJButton,如下所示:

JButton closeButton = new JButton("Close");

要创建带有图标的JButton,您需要一个图像文件。图标是固定大小的图像。实现javax.swing.Icon接口的类的对象代表一个图标。Swing 提供了一个非常有用的ImageIcon类,它实现了Icon接口。您可以在程序中使用ImageIcon类从图像文件或包含 GIF、JPEG 或 PNG 图像的 URL 创建图标。以下代码片段显示了如何创建带有图标的按钮:

// Create icons

Icon previousIcon = new ImageIcon("C:/img/previous.gif");

Icon nextIcon = new ImageIcon("C:/img/next.gif");

// Create buttons with icons

JButton previousButton = new JButton("Previous", previousIcon);

JButton nextButton = new JButton("Next", nextIcon);

建议您在ImageIcon类的构造函数中的文件路径中使用正斜杠(/)。您指定的文件路径被转换为 URL,并且正斜杠在所有平台上都有效。这个文件路径示例(C:/img/next.gif)是针对 Windows 平台的。图 2-2 显示了一个带有三个按钮的JFrame。两个按钮有图标,一个只有文本。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-2。

Buttons with an icon and text, and with only text

对于一个JButton只有一个事件,你将在你的 Java 程序中使用大部分时间。它被称为ActionEvent。点击JButton时触发。该接口是一个函数接口,它只包含一个名为actionPerformed(ActionEvent e)的方法。你可以用一个 lambda 表达式来表示一个ActionListener。下面是如何使用 lambda 表达式为ActionEventcloseButton添加代码:

closeButton.addActionListener(() -> {

// The code to handle the action event goes here

});

支持键盘助记键,也称为或。如果焦点在包含JButton的窗口中,按下该键会激活JButton。助记键通常与修饰键(如Alt键)一起按下。修饰键是平台相关的;但是,通常是一个Alt键。例如,假设您将 C 键设置为Close JButton的助记键。当您按下Alt + C时,Close JButton被点击。如果在JButton文本中找到由助记键表示的字符,其第一次出现时会加下划线。

以下代码片段将 C 设置为Close JButton的助记键:

// Set the 'C' key as mnemonic key for closeButton

closeButton.setMnemonic('C');

// You can also use the following code to set a mnemonic key.

// The KeyEvent class is in the java.awt.event package.

closeButton.setMnemonic(KeyEvent.VK_C);

该代码显示了设置助记键的两种方法。当您不使用字符键作为助记键时,可以使用第二种方法。例如,如果您想将F3键设置为助记键,您可以使用第二种方法使用KeyEvent.VK_F3常量。图 2-3 显示了Close按钮,其中文本的第一个字符带有下划线。当您按下Alt + C时,Close JButton被激活(就像您用鼠标点击它一样)。表 2-4 显示了类中常用的方法。

表 2-4。

Commonly Used Methods of the JButton Class

| 方法 | 描述 | | --- | --- | | `Action getAction()` | 返回与`JButton`相关联的`Action`对象。 | | `void setAction(Action a)` | 为`JButton`设置一个`Action`对象。当这个方法被调用时,`JButton`的所有属性都从指定的`Action`对象中刷新。如果已经设置了一个`Action`对象,新的将替换旧的。新的`Action`对象被注册为`ActionListener`。使用`addActionListener()`方法向`JButton`注册的任何其他`ActionListener`保持注册状态。 | | `Icon getIcon()` | 返回与`JButton`相关联的`Icon`对象。 | | `void setIcon(Icon icon)` | 为`JButton`设置图标。 | | `int getMnemonic()` | 返回此`JButton`的键盘助记符。 | | `void setMnemonic(int n)` `void setMnemonic(char c)` | 设置`JButton`的键盘助记符。 | | `String getText()` | 返回`JButton`的文本。 | | `void setText()` | 设置`JButton`的文本。 |

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-3。

A Close button with C as its keyboard mnemonic

让我们用一个对象来创建一个JButton。到目前为止,您已经看到一个JButton只有四个常用属性:文本、图标、助记符和动作监听器。使用JButton的这些属性既简单又直接。使用一个Action物体如何帮助你对付一个JButton?让我们举一个例子,你有一个按钮,比如说Close,放在窗口的不同区域,比如说不同的标签页。如果按钮在一个窗口上放置四次,并且所有按钮的外观和行为都必须相同,那么一个Action对象将帮助你只为Close按钮编写一次代码,并多次使用它。

一个Action对象封装了按钮的状态和行为。您在一个Action对象中设置文本、图标、助记符、工具提示文本、其他属性和ActionListener,并使用同一个Action对象创建JButton的所有实例。这样做的一个明显好处是,如果您想要启用/禁用所有四个 JButtons,您不需要单独启用/禁用它们。相反,您在Action对象中设置enabled属性,它将启用/禁用所有这些属性。让我们将这种用法扩展到菜单项和工具栏。通常在窗口中提供菜单项、工具栏项和按钮来执行相同的操作。在这种情况下,您使用同一个Action对象来创建它们(一个菜单项、一个工具栏项和一个按钮)以保持它们的状态同步。现在你可以意识到Action对象的好处是重用代码和保持多个组件的状态同步。

Action是一个接口。该类为Action接口提供了默认实现。AbstractAction是一个抽象类。你需要从它继承你的类。清单 2-1 定义了一个CloseAction内部类,它继承自AbstractAction类。

清单 2-1。使用 Action 对象创建和配置 JButton

// ActionJButtonTest.java

package com.jdojo.swing;

import java.awt.FlowLayout;

import javax.swing.JFrame;

import javax.swing.JButton;

import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;

import javax.swing.Action;

import java.awt.Container;

public class ActionJButtonTest extends JFrame {

// Inner Class starts here

public class CloseAction extends AbstractAction {

public CloseAction() {

super("Close");

}

@Override

public void actionPerformed(ActionEvent event) {

System.exit(0);

}

} // Inner Class ends here

JButton closeButton1;

JButton closeButton2;

Action closeAction = new CloseAction(); // See inner class above

public ActionJButtonTest() {

super("Using Action object with JButton");

this.initFrame();

}

private void initFrame() {

this.setDefaultCloseOperation(EXIT_ON_CLOSE);

this.setLayout(new FlowLayout());

Container contentPane = this.getContentPane();

// Use the same closeAction object to create both Close buttons

closeButton1 = new JButton(closeAction);

closeButton2 = new JButton(closeAction);

contentPane.add(closeButton1);

contentPane.add(closeButton2);

}

public static void main(String[] args) {

ActionJButtonTest frame = new ActionJButtonTest();

frame.pack();

frame.setVisible(true);

}

}

ActionJButtonTest类创建了一个Action对象,它的类型是CloseAction,并用它来创建两个按钮closeButton1closeButton2CloseAction类将文本设置为Close,在其方法中,它简单地退出应用。图 2-4 显示了运行程序时得到的JFrame。它显示了两个Close按钮。单击它们中的任何一个都会调用Action对象的actionPerformed()方法,这将退出应用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-4。

Two Close buttons created using the same Action object

如果您想在使用Action对象时为JButton设置任何属性,您可以通过使用Action接口的putValue(String key, Object value)方法来实现。例如,下面的代码片段为对象closeAction设置了工具提示文本和助记键:

// Set the tool tip text for the Action object

closeAction.putValue(Action.SHORT_DESCRIPTION, "Closes the application");

// Set the mneminic key for the Action object

closeAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_C);

Tip

如果您使用一个Action对象来配置一个JButton,然后直接更改JButton的属性,则更改后的属性将一直有效,直到您再次在Action对象中更改该属性。假设你已经用一个CloseAction对象创建了两个Close按钮。如果调用closeButton1.setText("Exit"),第一个按钮会将文本显示为Exit。如果调用closeAction.putValue(Action.NAME, "Close/Exit"),两个按钮都会显示文本为Close/Exit

jpanel(jpanel)

一个JPanel是一个可以包含其他组件的容器。您可以设置其布局管理器、边框和背景颜色。通常,您使用一个JPanel来分组相关的组件,并将其添加到另一个容器中,比如添加到一个JFrame的内容窗格中。注意,JPanel是一个容器,但不是顶级容器,而JFrame是一个顶级容器。因此,您不能在 Swing 应用中单独显示一个JPanel,除非您将它添加到顶级容器中。有时,在两个组件之间插入一个JPanel来产生一个间隙。您也可以使用JPanel作为画布进行绘制,例如绘制直线、矩形、圆形等。

JPanel的默认布局管理器是。注意,JFrame的内容窗格的默认布局管理器是一个BorderLayout。您可以选择在JPanel类的构造函数中指定它的布局管理器。您可以在创建它之后通过使用它的setLayout()方法来改变它的布局管理器。表 2-5 列出了JPanel类的构造函数。

表 2-5。

Constructors for the JPanel Class

| 构造器 | 描述 | | --- | --- | | `JPanel()` | 用`FlowLayout`和双缓冲创建一个`JPanel`。 | | `JPanel(boolean isDoubleBuffered)` | 用`FlowLayout`和指定的双缓冲标志创建一个`JPanel`。 | | `JPanel(LayoutManager layout)` | 用指定的布局管理器和双缓冲创建一个`JPanel`。 | | `JPanel(LayoutManager layout, boolean isDoubleBuffered)` | 用指定的布局管理器和双缓冲标志创建一个`JPanel`。 |

下面的代码片段展示了如何创建一个带有BorderLayoutJPanel并向其添加四个按钮。注意,按钮被添加到JPanel,后者又被添加到JFrame的内容窗格。您还可以将一个JPanel添加到另一个JPanel来创建嵌套的复杂组件布局。

// Create a JPanel and four buttons

JPanel buttonPanel = new JPanel(new BorderLayout());

JButton northButton = new JButton("North");

JButton southButton = new JButton("South");

JButton eastButton = new JButton("East");

JButton westButton = new JButton("west");

// Add buttons to the JPanel

buttonPanel.add(northButton, BorderLayout.NORTH);

buttonPanel.add(southButton, BorderLayout.SOUTH);

buttonPanel.add(eastButton, BorderLayout.EAST);

buttonPanel.add(westButton, BorderLayout.WEST);

// Add the buttonPanel to the JFrame's content pane assuming that

// the content's pane layout is set to a BorderLayout

contentPane.add(buttonPanel, BorderLayout.SOUTH);

JLabel

顾名思义,JLabel是一个标签,用于识别或描述屏幕上的另一个组件。它可以显示文本和/或图标。通常,JLabel被放置在它所描述的组件的旁边(右边或左边)或顶部。图 2-5 显示了一个文本设置为Name:JLabel,这是一个指示符,用户应该在它旁边的字段中输入一个名字。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-5。

A JLabel component with the text Name: and the mnemonic set to N

JLabel的另一个常见用途是显示图像。Swing 不包含像JImage这样的组件来显示图像。你需要使用一个带有IconJLabel来显示图像。表 2-6 列出了该类的构造函数。

表 2-6。

Constructors of the JLabel Class

| 构造器 | 描述 | | --- | --- | | `JLabel()` | 创建一个空字符串作为文本并且没有图标的`JLabel`。 | | `JLabel(Icon icon)` | 创建一个带有图标和空字符串作为文本的`JLabel`。 | | `JLabel(Icon icon, int horizontalAlignment)` | 创建一个带有图标和指定水平对齐方式的`JLabel`。一个`JLabel`垂直排列在其显示区域的中心。您可以将其显示区域中的水平对齐指定为`SwingConstants`类中定义的以下常量之一:`LEFT`、`CENTER`、`RIGHT`、`LEADING`或`TRAILING`。 | | `JLabel(String text)` | 用指定的`text`创建一个`JLabel`。这是最常用的构造函数。它垂直居中对齐,并在其显示区域内与前缘水平对齐。前缘由组件的方向决定。 | | `JLabel(String text, Icon icon, int horizontalAlignment)` | 用指定的`text`、`icon`和水平对齐创建一个`JLabel`。 | | `JLabel(String text, int horizontalAlignment)` | 用指定的`text`和水平对齐创建一个`JLabel`。 |

下面的代码片段展示了一些如何创建JLabel的例子:

// Create a JLabel with a Name: text

JLabel nameLabel = new JLabel("Name:");

// Display an image warning.gif in a JLabel

JLabel warningImage = new JLabel(new Icon("C:/img/warning.gif"));

一个JLabel不会产生任何有趣的事件。但是,它有一些有用的方法,您可以用来定制它。你会非常频繁地使用其中的三种方法:setText()setDisplayedMnemonic()setLabelFor()setText()方法用于设置。setDisplayedMnemonic()方法用于设置JLabel的键盘助记符。如果键盘助记符是出现在JLabel文本中的字符,该字符会加下划线以提示用户。setLabelFor()方法接受对另一个组件的引用,并指出这个JLabel描述了那个组件。两种方法- setDisplayedMnemonic()setLabelFor()协同工作。当按下JLabel的助记键时,焦点被设置到在setLabelFor()方法中使用的组件。图 2-5 所示的JLabel的助记符设置为字符N,你可以看到文本中的字符N带有下划线。当用户按下Alt + N时,焦点将被设置到显示在JLabel右侧的JTextField上。以下代码片段显示了如何创建如图 2-5 所示的组件排列:

// Create a JTextField where the user can enter a name

JTextField nameTextField = new JTextField("Please enter your name...");

// Create a JLabel with N as its mnemonic and nameTextField as its label-for component

JLabel nameLabel = new JLabel("Name:");

nameLabel.setDisplayedMnemonic('N');

nameLabel.setLabelFor(nameTextField);

// Add name label and field to a container, say a contentPane

contentPane.add(nameLabel);

contentPane.add(nameTextField);

JLabel类中还定义了其他方法,允许您设置/获取显示区域内的对齐方式和边界内的文本。如果你观察一个JLabel组件的特性,你会发现它的存在只是为了描述另一个组件——一个真正利他的组件!

文本组件

简单地说,您可以将文本定义为一系列字符。Swing 提供了一组丰富的功能来处理文本。图 2-6 显示了代表 Swing 中文本组件的类的类图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-6。

A class diagram for text-related components in Swing

Swing 提供了如此多与文本相关的特性,以至于它有一个单独的包,java x .swing.text,其中包含了所有与文本相关的类。JTextComponent级在javax.swing.text包里。其余的类都在javax.swing包里。

有不同的 Swing 组件来处理不同类型的文本。我们可以根据两个标准对文本组件进行分类:文本中的行数和它们可以处理的文本类型。根据文本组件可以处理的文本行数,您可以将它们进一步分类如下:

  • 单行文本组件
  • 多行文本组件

单行文本组件设计用于处理一行文本,例如用户名、密码、出生日期等。JTextFieldJPasswordFieldJFormattedTextField类的实例代表单行文本组件。

多行文本组件旨在处理多行文本,例如,注释、商店中某个商品的描述、文档等。JTextAreaJEditorPaneJTextPane类的实例代表多行文本组件。

根据文本组件可以处理的文本类型,可以对文本组件进行如下分类:

  • 纯文本组件
  • 样式文本组件

文本(或部分文本)的样式是文本显示的方式,如粗体、斜体、下划线等。、字体和颜色。在文本组件的上下文中,纯文本意味着文本组件中包含的整个文本只使用一种样式显示。JTextFieldJPasswordFieldJFormattedTextFieldJTextArea是纯文本组件的例子。也就是说,您不能在一个JTextArea中显示多行文本,其中文本的某些部分是粗体,而其他部分不是。您可以用粗体显示JTextArea中的整个文本,也可以用普通字体显示整个文本。注意,纯文本并不意味着文本不能有样式。这意味着只有一种样式适用于整个文本(组成文本的所有字符)。

在样式文本中,您可以将不同的样式应用于文本的不同部分。在样式文本中,文本的某些部分可以是粗体(或斜体,更大的字体大小,下划线等。)和一些不是黑体的部分。JEditorPaneJTextPane是样式化组件的例子。

所有 Swing 组件,包括 Swing 文本组件,都基于模型-视图-控制器(MVC)模式。MVC 模式使用三个组件:模型、视图和控制器。模型负责存储内容(文本)。视图负责显示内容。控制器负责响应用户操作。Swing 将视图和控制器组合成一个名为 UI 的对象,负责显示内容并对用户的动作做出反应。它保持了模型的独立性,并由Document接口的一个实例来表示,该实例在javax.swing.text包中。文本组件的模型有时也被称为它的文档。图 2-7 描述了一个 Swing 文本组件的不同部分。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-7。

Components of the model-view-controller pattern for Swing text components

请注意,视图可能不总是显示文本组件的全部内容。在图 2-7 中,模型包含了威廉·华兹华斯的一首诗的四行,而视图只显示了第一行的一些单词。

Swing 提供了一个默认的Document接口实现,这使得开发人员可以轻松处理常用的文本类型。当您使用文本组件时,它会为您创建一个合适的模型(有时我会在讨论中将其称为文档),该模型适合存储文本组件的内容。图 2-8 显示了Document接口的类图,以及相关的类和接口。图中显示的所有类和接口都在javax.swing.text包中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-8。

A class diagram for the document interface and related interfaces and classes

您可以使用setDocument(Document doc)方法为文本组件设置模型。getDocument()方法返回文本组件的模型。

默认情况下,JTextFieldJPasswordFieldJFormattedTextFieldJTextArea使用PlainDocument类的一个实例作为它们的模型。如果您想要为这些文本组件定制模型,您需要创建一个从PlainDocument类继承的类,并覆盖一些方法。

JEditorPaneJTextPane的模型取决于正在编辑和/或显示的内容类型。文本组件中字符的位置使用从零开始的索引。即,文本中的第一个字符出现在索引 0 处。

文本组件

JTextComponent是一个abstract类。它是所有 Swing 文本组件的祖先。它包括所有文本组件都可用的通用功能。表 2-7 列出了JTextComponent类中包含的文本组件的一些常用方法。

表 2-7。

Commonly Used Methods in the JTextComponent Class

| 方法 | 描述 | | --- | --- | | `Keymap addKeymap(String name, Keymap parentKeymap)` | 将新的键映射添加到组件的键映射层次结构中。 | | `void copy()` | 将选定的文本复制到系统剪贴板。 | | `void cut()` | 将选定的文本移动到系统剪贴板。 | | `Action[] getActions()` | 返回文本编辑器的命令列表。 | | `Document getDocument()` | 返回文本组件的模型。 | | `Keymap getKeymap()` | 返回文本组件的当前活动键映射。 | | `static Keymap getKeymap (String keymapName)` | 返回与名为`keymapName`的文档相关联的键映射。 | | `String getSelectedText()` | 返回组件中选定的文本。如果没有选择的文本或者文档是空的,它返回`null`。 | | `int getSelectionEnd()` | 返回选定文本的结束位置。 | | `int getselectionStart()` | 返回选定文本的起始位置。 | | `String getText()` | 返回此文本组件中包含的文本。它返回组件模型中包含的文本,而不是视图显示的内容。 | | `String getText(int offset, int length) throws BadLocationException` | 返回文本组件中包含的一部分文本,从`offset`位置开始,字符数等于`length`。如果`offset`或`length`无效,则抛出`BadLocationException`。例如,如果一个文本组件包含`Hello`作为其文本,`getText(1,3)`将返回`ell`。 | | `TextUI getUI()` | 返回文本组件的用户界面工厂。 | | `boolean isEditable()` | 如果文本组件是可编辑的,则返回`true`。否则,返回`false`。 | | `void paste()` | 将系统剪贴板的内容传输到文本组件模型。如果在组件中选择了文本,则选定的文本将被替换。如果没有选择,内容将插入到当前位置之前。如果系统剪贴板是空的,它什么也不做。 | | `void print()` | 它显示一个打印对话框,让您打印不带页眉和页脚的文本组件的内容。此方法被重载。此方法的其他版本提供了更多打印文本组件内容的功能。 | | `void read(Reader source, Object description) throws IOException` | 将内容从`source`流读入文本组件,丢弃组件的旧内容。`description`是一个描述`source`流的对象。例如,要将文件`test.txt`的文本读入名为`ta`的`JTextArea`中,您可以编写`FileReader fr =` `new FileReader("test.txt");` `ta.read(fr, "Hello");` `fr.close();` | | `void replaceSelection(String newContent)` | 用`newContent`替换所选内容。如果没有选定的内容,它会插入`newContent`。如果`newContent`为`null`或空字符串,则删除选中的内容。 | | `void select(int start, int end)` | 选择`start`和`end`位置之间的文本。 | | `void selectAll()` | 选择文本组件中的所有文本 | | `void setDocument(Document doc)` | 为文本组件设置文档(即模型)。 | | `void setEditable(boolean editable)` | 如果`editable`为`true`,则将文本组件设置为可编辑。如果`editable`为`false`,则将文本组件设置为不可编辑。 | | `void setKeymap(Keymap keymap)` | 设置文本组件的键映射。 | | `void setSelectionEnd(int end)` | 设置选择的结束位置。 | | `void setSelectionStart(int start)` | 设置选择的开始位置。 | | `void setText(String newText)` | 设置文本组件的文本。 | | `void setUI(TextUI newUI)` | 为文本组件设置新的用户界面。 | | `void updateUI()` | 重新加载文本组件的可插入用户界面。 | | `void write(Writer output)` | 将文本组件的内容写入由`output`定义的流。例如,要将名为`ta`的`JTextArea`的文本写入名为`test.txt`的文件,您应该编写`FileWriter wr = new FileWriter("test.txt");` `ta.write(wr);` `wr.close();` |

文本组件最常用的方法是getText()setText(String text)getText()方法将文本组件的内容作为String返回,setText(String text)方法设置参数中指定的文本组件的内容。

jtextfield(jtextfield)

一个JTextFiel d 可以处理(显示和/或编辑)一行纯文本。您可以使用构造函数以多种不同的方式创建一个JTextField。它的构造函数接受

  • 一根绳子
  • 列数
  • 一个Document物体

该字符串指定初始文本。列数指定了宽度。Document对象指定了模型。初始文本的默认值是null,列数为零,文档(或模型)是PlainDocument类的一个实例。

如果不指定列数,其宽度由初始文本决定。它的首选宽度将足以显示整个文本。如果您指定列数,其首选宽度将足够宽,以在JTextField的当前字体中显示指定列数的m个字符。表 2-8 列出了该类的构造函数。

表 2-8。

Constructors of the JTextField Class

| 构造器 | 描述 | | --- | --- | | `JTextField()` | 用初始文本、列数和文档的默认值创建一个`JTextField`。 | | `JTextField(Document document, String text, int columns)` | 创建一个`JTextField`,将指定的`document`作为其模型,`text`作为其初始文本,`columns`作为其列数。 | | `JTextField(int columns)` | 创建一个将指定的`columns`作为列数的`JTextField`。 | | `JTextField(String text)` | 用指定的`text`创建一个`JTextField`作为它的初始文本。 | | `JTextField(String text, int columns)` | 创建一个`JTextField`,将指定的`text`作为其初始文本,将`columns`作为其列数。 |

以下代码片段使用不同的构造函数创建了许多JTextField实例:

// Create an empty JTextField

JTextField emptyTextField = new JTextField();

// Create a JTextField with an initial text of Hello

JTextField helloTextField = new JTextField("Hello");

// Create a JTextField with the number of columns of 20

JTextField nameTextField = new JTextField(20);

一个JTextField可以输入多少个字符?您可以在JTextField中输入的字符数量没有限制。如果你想限制一个JTextField中的人物数量,你需要定制它的模型。请注意,JTextField的型号存储其内容。在看到运行中的定制模型之前,让我们看看在 Swing 中将文本组件的模型和视图分开的强大功能。

让我们创建两个JTextField的实例。您将设置mirroredName的型号与name的型号相同。你正在做一件非常简单的事情。两个文本字段使用相同的模型。这使得两个字段成为彼此的镜像字段。如果您在其中一个窗口中输入文本,相同的文本会自动显示在另一个窗口中。这是怎么发生的?当您在JTextField中输入文本时,它的模型被更新。它的模型中的任何更新都向它的视图(在这种情况下,这两个组件充当视图)发送通知来更新它们自己。由于两个文本字段是具有相同模型的两个视图,模型中的任何更新(通过任一文本字段)都将向两个文本字段发送通知,并且两个文本字段都将更新它们的视图以显示相同的文本。

清单 2-2 展示了如何在两个文本字段之间共享一个模型。运行该程序,并在任一文本字段中输入一些文本。您将看到另一个文本字段与相同的文本同时更新。

清单 2-2。通过与另一个 JTextField 共享模型来镜像 JTextField

// MirroredTextField.java

package com.jdojo.swing;

import javax.swing.JFrame;

import javax.swing.JTextField;

import javax.swing.JLabel;

import java.awt.GridLayout;

import java.awt.Container;

import javax.swing.text.Document;

public class MirroredTextField extends JFrame {

JLabel nameLabel = new JLabel("Name:") ;

JLabel mirroredNameLabel = new JLabel("Mirrored Name:") ;

JTextField name = new JTextField(20);

JTextField mirroredName = new JTextField(20);

public MirroredTextField() {

super("Mirrored JTextField");

this.initFrame();

}

private void initFrame() {

this.setDefaultCloseOperation(EXIT_ON_CLOSE);

this.setLayout(new GridLayout(2, 0));

Container contentPane = this.getContentPane();

contentPane.add(nameLabel);

contentPane.add(name);

contentPane.add(mirroredNameLabel);

contentPane.add(mirroredName);

// Set the model for mirroredName to be the same

// as name's model, so they share their content's storage.

Document nameModel = name.getDocument();

mirroredName.setDocument(nameModel);

}

public static void main(String[] args) {

MirroredTextField frame = new MirroredTextField();

frame.pack();

frame.setVisible(true);

}

}

为了拥有自己的JTextField模型,你需要创建一个新的类。新类既可以实现Document接口,也可以从该类继承。后一种方法更简单,也是最常用的。清单 2-3 包含了一个LimitedCharDocument类的代码,它继承自PlainDocument类。当你想限制一个JTextField中的字符数量时,你可以使用这个类作为一个JTextField的模型。默认情况下,它允许用户输入不限数量的字符。您可以在其构造函数中设置允许的字符数。

清单 2-3。表示具有有限数量字符的普通文档的类

// LimitedCharDocument.java

package com.jdojo.swing;

import javax.swing.text.PlainDocument;

import javax.swing.text.BadLocationException;

import javax.swing.text.AttributeSet;

public class LimitedCharDocument extends PlainDocument {

private int limit = -1; // < 0 means an unlimited characters

public LimitedCharDocument() {

}

public LimitedCharDocument(int limit) {

this.limit = limit;

}

@Override

public void insertString(int offset, String str, AttributeSet a)

throws BadLocationException {

String newString = str;

if (limit >=0 && str != null) {

// Check for the limit

int currentLength = this.getLength() ;

int newTextLength = str.length();

if (currentLength + newTextLength > limit) {

newString = str.substring(0, limit - currentLength);

}

}

super.insertString(offset, newString, a);

}

}

LimitedCharDocument类中感兴趣的代码是insertString()方法。Document接口声明了一个方法。PlainDocument类提供了默认的实现。LimitedCharDocument类覆盖默认实现,并检查插入的字符串是否会超过允许的字符数。如果插入的字符串超过了允许的最大字符数,它会砍掉多余的字符。如果将限制设置为负数,则允许无限数量的字符。最后,该方法简单地调用它在PlainDocument类中的实现来执行真正的动作。

每次将文本插入到JTextField中时,都会调用模型的insertString()。此方法获取以下三个参数:

  • int offset:这是琴弦插入JTextField的位置。第一个字符在偏移量 0 处插入,第二个字符在偏移量 1 处插入,依此类推。
  • String str:插入JTextField的是字符串。当您在JTextField中输入文本时,对于您输入的每个字符都会调用insertString()方法,并且该参数将只包含一个字符。但是,当您将文本粘贴到JTextField中或使用其setText()方法时,该参数可能包含多个字符。
  • AttributeSet a:必须与插入文本相关联的属性。

您可以在代码中使用LimitedCharDocument,如下所示:

// Create a JTextField, which will only allow 10 characters

Document tenCharDoc = new LimitedCharDocument(10);

JTextField t1 = new JTextField(tenCharDoc, "your name", 10);

还有另一种方法为一个JTextField设置文档。您需要创建一个继承自JTextField的新类,并覆盖它的createDefaultModel()方法。它在JTextField类中声明为protected,默认情况下,它返回一个PlainDocument。您可以从此方法返回自定义文档类的实例。您的自定义代码JTextField如下所示:

public class TenCharTextField extends JTextField {

@Override

protected Document createDefaultModel() {

// Return a document object that allows maximum 10 characters

return new LimitedCharDocument(10);

}

// Other code goes here

}

只要需要一个容量为 10 个字符的JTextField,就可以使用TenCharTextField类的实例。

JTextField类中的构造函数调用createDefaultModel()方法。因此,您不应该向您的客户JTextField传递一个参数,并使用该参数的值在您的类的createDefaultModel()方法中构建模型。例如,以下代码片段不会产生预期的结果:

static class LimitedCharTextField extends JTextField {

private int maxChars = -1;

public LimitedCharTextField(int maxChars) {

this.maxChars = maxChars;

}

protected Document createDefaultModel() {

/* Wrong use of maxChars!!! By the time this method is called,

maxChars will have its default value of zero. This method will be

called from the constructor of the JTextField class and at that time

the constructor for this class would not start executing.

*/

return new LimitedCharDocument(maxChars);

}

}

有时,您可能希望强制用户以特定格式在文本字段中输入文本,例如以 mm/dd/yyyy 格式输入日期或仅输入数字。这可以通过为JTextField组件使用定制模型来实现。Swing 包含另一个名为JFormattedTextField的文本组件,它允许您设置文本字段的格式。如果您需要一个允许用户以特定格式添加文本的组件,那么JFormattedTextField会使这项工作变得容易得多。我将很快讨论JFormattedTextField

jpassword field(jpassword 字段)

一个JPasswordField是一个JTextField,除了它允许隐藏字段中显示的实际字符。例如,当您使用登录表单输入密码时,您不希望别人越过您的肩膀看到您在屏幕上的密码。默认情况下,它为字段中的每个实际字符显示一个星号(*)字符。这被称为回声字符。默认的回显字符也取决于应用的外观。您可以通过使用它的 s etEchoChar(char newEchoChar)方法来设置自己的 echo 字符。

JPasswordField类与JTextField类具有相同的构造函数集。您可以使用初始文本、列数和一个Document对象的组合来创建一个JPasswordField对象。

// Create a password field 10 characters wide

JPasswordField passwordField = new JPasswordField(10);

出于安全原因,JPasswordFieldgetText()方法已被否决。您应该使用它的getPassword()方法,该方法返回一个数组char。在你使用完这个char数组后,你应该将它的所有元素重置为零。下面的代码片段显示了如何验证在JPasswordField中输入的密码:

// Get the password entered in the field

char c[] = passwordField.getPassword();

// Suppose you have the correct password in a string.

// Usually, you will get it from a file or database

String correctPass = "Hello";

// Do not convert your password in c[] to a String. Rather, convert the correctPass

// to a char array. Or, better you would have correctPass as char array in the first place.

char[] cp = correctPass.toCharArray();

// Use the equals() method of the java.util.Arrays class to compare c and cp for equality

if (Arrays.equals(c, cp)) {

// The password is correct

}

else {

// The password is incorrect

}

// Null out the password that you have in the char arrays

Arrays.fill(c, (char)0);

Arrays.fill(cp, (char)0);

您可以使用setEchoChar()方法设置您选择的回显字符,如下所示:

// Set # as the echo character

password.setEchoChar(‘#');

您可以将JPasswordField作为JTextField使用,方法是将其回显字符设置为零,如下所示:

// Set the echo character to 0, so the actual password characters are visible.

passwordField.setEchoChar((char)0);

Tip

您需要将JPasswordField的回送字符设置为 ASCII 值为零的字符值,这样JPasswordField将显示实际的字符。如果您将回送字符设置为'0' (ASCII 值为 48),实际的密码将不会显示。相反,将为每个实际字符回显一个'0'字符。

JFormattedTextField

一个JFormattedTextField是一个JTextField,具有以下两个附加功能:

  • 它允许您指定编辑和/或显示文本的格式。
  • 当字段中的值为null时,它还允许您指定一种格式。

除了让您获取和设置字段中的文本的getText()setText()方法之外,JFormattedTextField还提供了两个新方法,分别叫做getValue()etValue(),让您可以处理任何类型的数据,而不仅仅是文本。

JFormattedTextField预配置为处理三种数据:数字、日期和字符串。但是,您可以格式化要在该字段中显示的任何对象。您可以使用不同的构造函数以多种方式设置JFormattedTextField的格式,这些构造函数在表 2-9 中列出。

表 2-9。

Constructors of the JFormattedTextField Class

| 构造器 | 描述 | | --- | --- | | `JFormattedTextField()` | 创建一个没有格式化程序的`JFormattedTextField`。你需要使用它的`setFormatterFactory()`或`setValue()`方法来设置一个格式化程序。 | | `JFormattedTextField(Format format)` | 创建一个`JFormattedTextField`,它将使用指定的`format`来格式化字段中的文本。 | | `JFormattedTextField(` `JFormattedTextField.AbstractFormatter formatter)` | 用指定的格式化程序创建一个`JFormattedTextField`。 | | `JFormattedTextField(JFormattedTextField.AbstractFormatterFactory` `factory)` | 用指定的工厂创建一个`JFormattedTextField`。 | | `JFormattedTextField(``JFormattedTextField.AbstractFormatterFactory` | 用指定的工厂和指定的初始值创建一个`JFormattedTextField`。 | | `JFormattedTextField(Object value)` | 用指定的值创建一个`JFormattedTextField`。该字段将根据值的类别自行配置值的格式。如果将一个`null`作为值传递,该字段就无法知道它需要格式化哪种类型的值,并且它根本不会尝试格式化该值。 |

有必要了解格式、格式化程序和格式化程序工厂之间的区别。java.text.Format对象以字符串形式定义了对象的格式。也就是说,它定义了一个对象作为字符串的外观;例如,mm/dd/yyyy格式的日期对象看起来像07/09/2008

格式化程序由一个JFormattedTextField.AbstractFormatter对象表示,它使用一个java.text.Format对象来格式化一个对象。它的工作是将对象转换成字符串,并将字符串转换回对象。

格式化程序工厂是格式化程序的集合。一个JFormattedTextField使用一个格式化程序工厂来获得一个特定类型的格式化程序。格式化程序工厂对象由JFormattedTextField.AbstractFormatterFactory类的一个实例表示。

以下代码片段将dobField配置为将其中的文本格式化为当前区域设置格式的日期:

JFormattedTextField dobField = new JFormattedTextField();

dobField.setValue(new Date());

下面的代码片段配置了一个salaryField来以当前语言环境格式显示一个数字:

JFormattedTextField salaryField = new JFormattedTextField();

salaryField.setValue(new Double(11233.98));

也可以用格式化程序创建一个JFormattedTextField。您需要使用DateFormatterNumberFormatterMaskFormatter类来分别格式化日期、数字和字符串。这些类都在javax.swing.text包里。

// Have a field to format a date in mm/dd/yyyy format

DateFormat dateFormat = new SimpleDateFormat("mm/dd/yyyy");

DateFormatter dateFormatter = new DateFormatter(dateFormat);

dobField = new JFormattedTextField(dateFormatter);

// Have field to format a number in $#0,000.00 format

NumberFormat numFormat = new DecimalFormat("$#0,000.00");

NumberFormatter numFormatter = new NumberFormatter(numFormat);

salaryField = new JFormattedTextField(numFormatter);

您需要使用掩码格式化程序来格式化字符串。掩码格式化程序使用表 2-10 中列出的特殊字符来指定掩码。

表 2-10。

Special Characters Used to Specify a Mask

| 性格;角色;字母 | 描述 | | --- | --- | | `#` | 一个数字 | | `?` | 一封信 | | `A` | 一个字母或一个数字 | | `*` | 任何事 | | `U` | 一个字母,小写字符映射成大写字符 | | `L` | 一个字母,大写字符映射成小写字母 | | `H` | 十六进制数字(a-f,A-F,0-9) | | `'` | 一句引言。它是一个转义字符,用于转义任何特殊格式的字符。 |

为了让用户输入一个###-##-####格式的社会保险号,您创建一个JFormattedTextField,如下所示。注意,构造函数MaskFormatter(String mask)抛出了一个ParseException

MaskFormatter ssnFormatter = null;

JFormattedTextField ssnField = null;

try {

ssnFormatter = new MaskFormatter("###-##-####");

ssnField = new JFormattedTextField(ssnFormatter);

}

catch (ParseException e) {

e.printStackTrace();

}

当使用掩码格式化程序时,您只能使用您在掩码中指定的字符数。所有非特殊字符(见表 2-10 中的特殊字符列表)显示在屏蔽中。掩码中的每个特殊字符都会显示一个占位符(默认为空格)。例如,如果您将遮罩指定为"###-##-####",则JFormattedTextField会将" - - "显示为占位符。您还可以使用MaskFormatter类的setPlaceHolderCharacter(char placeholder)方法为特殊字符指定一个占位符。要在 SNN 字段中显示000-00-0000,您需要使用“0”作为主格式化程序的占位符,如下所示:

ssnFormatter = new MaskFormatter("###-##-####");

ssnFormatter.setPlaceholderCharacter('0');

创建组件后,您可以使用JFormattedTextField的方法来更改格式化程序。例如,要为名为payDateJFormattedTextField设置日期格式,在创建它之后,您可以编写

DateFormatter df = new DateFormatter(new SimpleDateFormat("mm/dd/yyyy"));

DefaultFormatterFactory dff = new DefaultFormatterFactory(df, df, df, df); dobField.setFormatterFactory(dff);

JFormattedTextField让您指定四种类型的格式化程序:

  • 答:当字段中的值为null时使用。
  • 安:当字段有焦点时使用。
  • 答:当字段没有焦点并且有一个非空值时使用。
  • 答:在以上三种格式化程序都不存在的情况下使用。

您可以通过在JFormattedTextField类的构造函数中使用格式化程序工厂或者调用它的setFormatterFactory()方法来指定所有四个格式化程序。JFormattedTextField.AbstractFormatterFactory抽象类的一个实例代表一个格式化程序工厂。javax.swing.text.DefaultFormatterFactory类是JFormattedTextField.AbstractFormatterFactory类的一个实现。当指定格式化程序时,使用同一个格式化程序来代替四个格式化程序。当指定格式化程序工厂时,您可以为四种不同的情况指定不同的格式化程序。

假设您有一个名为dobFieldJFormattedTextField来显示日期。当该字段获得焦点时,您希望让用户以mm/dd/yyyy的格式编辑日期(例如07/07/2008)。当它没有焦点时,您希望以 mmmm dd, yyyy(例如July 07, 2008)格式显示日期。下面的代码片段将完成这项工作:

DateFormatter df = new DateFormatter(new SimpleDateFormat("mmmm dd, yyyy"));

DateFormatter edf = new DateFormatter(new SimpleDateFormat("mm/dd/yyyy"));

DefaultFormatterFactory ddf = new DefaultFormatterFactory(df, df, edf, df);

dobField.setFormatterFactory(ddf);

如果您已经配置了JFormattedTextField来格式化日期,那么您可以使用它的getValue()方法来获得一个Date对象。getValue()方法的返回类型是Object,您需要将返回值转换为类型Date。您可以将光标放在字段中日期值的月、日、年、小时、分钟和秒部分,并使用上/下箭头键更改该特定部分。如果您想在键入时覆盖字段中的值,您需要使用方法setOverwriteMode(true)将格式化程序设置为覆盖模式。

使用JFormattedTextField的另一个好处是可以限制一个字段中可以输入的字符数。回想一下,在上一节中,您是通过为JTextField使用定制文档来实现这一点的。您可以通过设置掩码格式化程序来达到同样的目的。假设您想让用户在一个字段中最多输入两个字符。您可以按如下方式完成此操作:

JFormattedTextField twoCharField = new JFormattedTextField(new MaskFormatter("**"));

JTextArea(人名)

一个JTextArea可以处理多行纯文本。大多数情况下,当您在一个JTextArea中有多行文本时,您将需要滚动功能。一个JTextArea本身不提供滚动。相反,当您需要任何 Swing 组件的滚动功能时,您需要从另一个名为JScrollPane的 Swing 组件获得帮助。

您可以指定用于确定其首选大小的JTextArea的行数和列数。行数用于确定其首选高度。如果您将行数设置为N,这意味着它的首选高度将被设置为显示当前字体设置中文本的N行数。列数用于确定其首选宽度。如果将列数设置为M,则意味着其首选宽度被设置为当前字体设置中字符m(小写 M)宽度的M倍。

一个JTextArea提供了许多构造函数来创建一个JTextArea组件,使用初始文本、模型、行数和列数的组合作为参数,如表 2-11 所示。

表 2-11。

Constructors of the JTextArea Class

| 构造器 | 描述 | | --- | --- | | `JTextArea()` | 用默认模型创建一个`JTextArea`,初始字符串为`null`,行/列为零。 | | `JTextArea(Document doc)` | 用指定的`doc`创建一个`JTextArea`作为它的模型。它的初始字符串被设置为`null`,行/列被设置为零。 | | `JTextArea(Document doc, String text, int rows, int columns)` | 创建一个`JTextArea`,它的所有属性(模型、初始文本、行和列)都在它的参数中指定。 | | `JTextArea(int rows, int columns)` | 用默认模型创建一个`JTextArea`,初始字符串为`null`,指定行/列。 | | `JTextArea(String text)` | 用指定的初始文本创建一个`JTextArea`。设置默认模型,并将行/列设置为零。 | | `JTextArea(String text, int rows, int columns)` | 用指定的文本、行和列创建一个`JTextArea`。使用默认模型。 |

以下代码片段使用不同的初始值创建了许多JTextArea实例:

// Create a blank JTextArea

JTextArea emptyTextArea = new JTextArea();

// Create a JTextArea with 10 rows and 50 columns

JTextArea commentsTextArea = new JTextArea(10, 50);

// Create a JTextArea with 10 rows and 50 columns with an initial text of "Enter resume here"

JTextArea resumeTextArea = new JTextArea("Enter resume here", 10, 50);

非常重要的是要记住,当你使用JTextArea时,通常你的文本尺寸会比它在屏幕上的尺寸大,你需要一个滚动功能。要给一个JTextArea添加滚动功能,您需要将它添加到一个JScrollPane,并将JScrollPane添加到容器,而不是JTextArea。下面的代码片段演示了这个概念。假设您有一个名为myFrameJFrame,其内容窗格的布局设置为BorderLayout,并且您想要在中心区域添加一个可滚动的JTextArea

// Create JTextArea

JTextArea resumeTextArea = new JTextArea("Enter resume here", 10, 50);

// Add JTextArea to a JScrollPane

JScrollPane sp = new JScrollPane(resumeTextArea);

// Get the reference of the content pane of the JFrame

Container contentPane = myFrame.getContentPane();

// Add the JScrollPane (sp) to the content pane, not the JTextArea

contentPane.add(sp, BorderLayout.CENTER);

表 2-12 中有一些JTextArea的常用方法。大多数时候,你会使用它的setText()getText()append()方法。

表 2-12。

Commonly Used Methods of JTextArea

| 方法 | 描述 | | --- | --- | | `void append(String text)` | 将指定的`text`追加到`JTextArea`的末尾。 | | `int getLineCount()` | 返回`JTextArea`中的行数。 | | `int getLineStartOffset(int line) throws BadLocationException` `int getLineEndOffset(int line) throws BadLocationException` | 返回指定`line`数字的开始和结束偏移量(也称为位置,从零开始)。如果`line`号超出范围,抛出异常。当你把这个方法和`getLineCount()`方法结合起来时,它是有用的。您可以在一个循环中使用这三种方法逐行解析包含在`JTextArea`中的文本。 | | `int getLineOfOffset(int offset) throws BadLocationException` | 返回指定的`offset`出现的行号。 | | `boolean getLineWrap()` | 如果设置了换行,返回`true`。否则返回`false`。 | | `int getTabSize()` | 返回用于制表符的字符数。默认情况下,它返回 8。 | | `boolean getWrapStyleWord()` | 如果自动换行设置为`true`,则返回`true`。否则,它返回`false`。 | | `void insert(String text, int offset)` | 在指定的`offset`处插入指定的`text`。如果模型是`null`或者指定的`text`是空的或者是`null`,调用这个方法没有效果。 | | `void replaceRange(String text, int start, int end)` | 用指定的`text`替换`start`和`end`位置之间的文本。 | | `void setLineWrap(boolean wrap)` | 为`JTextArea`设置换行策略。如果换行设置为`true`,如果一行不适合`JTextArea`的宽度,则换行。如果设置为`false`,即使比`JTextArea`的宽度长,也不会换行。默认设置为`false`。 | | `void setTabSize(int size)` | 设置制表符将扩展到指定大小的字符数。 | | `void setWrapStyleWord(boolean word)` | 当换行设置为`true`时,设置自动换行样式。当设置为`true`时,该行在字边界换行。否则,该行将在字符边界处换行。默认情况下,它被设置为`false`。 |

JTextArea使用可配置的策略在其可显示区域换行。如果换行设置为true,并且一条线比元件的宽度长,该线将被换行。默认情况下,换行设置为false。使用setLineWrap(boolean lineWrap)方法设置换行。

一行可以在单词边界或字符边界换行,这由单词换行策略决定。使用setWrapStyleWord(boolean wordWrap)方法设置单词换行策略。只有当调用了setLineWrap(true)时,调用这个方法才会生效。也就是说,换行策略定义了换行策略的细节。图 2-9 显示了一个JFrame中显示的三个JTextArea组件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-9。

The effects of line and word wrapping in a JTextArea

对于图中的三个JTextArea组件(从左到右),换行和换行设置分别为(truetrue)、(truefalse)、(falsetrue)。第一个在单词边界处换行。第二个在字符边界换行。第三个没有换行,你不能看到整个文本的宽度。请注意,三个JTextArea组件中的每一个都添加到了JFrame中,而没有添加到 a 中。

编辑器面板

一个JEditorPane是一个文本组件,被设计用来处理不同种类的文本。默认情况下,它知道如何处理纯文本、HTML 和富文本格式(RTF)。虽然它是为编辑和显示多种类型的内容而设计的,但它主要用于显示 HTML 文档,其中只包含基本的 HTML 元素。对 RTF 内容的支持是非常基本的。

JEditorPane使用特定的对象处理特定类型的内容。如果您想在这个组件中处理新类型的内容,您将需要创建一个定制的EditorKit类,它是javax.swing.text.EditorKit类的一个子类。如果你只是使用这个组件来显示 HTML 内容,你不需要担心一个EditorKit;该组件将为您处理相关的功能。使用JEditorPane显示一个 HTML 页面只需要一行代码,如下所示:

// Create a JEditorPane to display yahoo.com 网页

JEditorPane htmlPane = new JEditorPane("http://www.yahoo.com

注意,JEditorPane类的一些构造函数抛出了一个IOException。指定 URL 时,必须使用 URL 的完整形式,以协议开头。您可以通过以下三种不同的方式让JEditorPane知道它需要安装哪种类型的EditorKit来处理它的内容:

  • 通过调用方法
  • 通过调用setPage(URL url)setPage(String url)方法
  • 通过调用方法

JEditorPane预配置为理解三种类型的内容:文本/纯文本、文本/html 和文本/rtf。您可以使用下面的代码显示文本Hello,在 HTML 中使用

标签:

htmlPane.setContentType("text/html");

htmlPane.setText("<html><body><h1>Hello</h1></body></html>");

当您调用它的setPage()方法时,它使用适当的EditorKit来处理 URL 提供的内容。在下面的代码片段中,JEditorPane根据内容类型使用了EditorKit:

// Handle an HTML Page

editorPane.setPage("http://www.yahoo.com

// Handle an RTF file. When you use a file protocol, you may use three slashes instead of one

editorPane.setPage("file:///C:/test.rtf");

JEditorPane将流中的内容读入编辑器窗格。如果它的编辑器套件已经设置为处理 HTML 内容,并且指定的描述是类型javax.swing.text.html.HTMLDocument,则内容将被读取为 HTML。否则,内容将作为纯文本读取。

当您处理 HTML 文档时,您可能希望在单击超链接时导航到不同的页面。为了使用超链接,您需要向添加一个超链接侦听器,并在事件侦听器的hyperlinkUpdate()方法中,使用setPage()方法导航到新页面。超链接上的三种动作之一ENTEREDEXITEDACTIVATED触发hyperlinkUpdate()方法。当鼠标进入超链接区域时发生ENTERED事件,当鼠标离开超链接区域时发生EXITED事件,当单击超链接时发生ACTIVATED事件。当您想使用超链接导航到另一个页面时,请确保在超链接监听器的hyperlinkUpdate()方法中检查了ACTIVATED事件。以下代码片段使用 lambda 表达式将HyperlinkListener添加到JEditorPane:

editorPane.addHyperlinkListener((HyperlinkEvent event) -> {

if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {

try {

editorPane.setPage(event.getURL());

}

catch (IOException e) {

e.printStackTrace();

}

}

});

如果您想知道新页面何时被加载到JEditorPane中,您需要添加一个属性更改监听器来监听它的属性更改事件,并检查名为page的属性是否已经更改。清单 2-4 包含了使用JEditorPane作为浏览器浏览网页的完整代码。当您运行该程序时,您可以在 URL 字段中输入一个网页地址,然后按 enter 键(或按 Go 按钮),浏览器将显示新 URL 的内容。您也可以单击内容中的超链接导航到另一个网页。代码很简单,包含足够的注释来帮助你理解程序逻辑。

清单 2-4。使用 JEditorPane 组件的 HTML 浏览器

// HTMLBrowser.java

package com.jdojo.swing;

import javax.swing.JFrame;

import java.awt.Container;

import javax.swing.JLabel;

import javax.swing.JScrollPane;

import javax.swing.Box;

import javax.swing.JEditorPane;

import javax.swing.JTextField;

import javax.swing.JButton;

import java.awt.BorderLayout;

import java.net.URL;

import javax.swing.event.HyperlinkEvent;

import java.beans.PropertyChangeEvent;

import java.net.MalformedURLException;

import java.io.IOException;

public class HTMLBrowser extends JFrame {

JLabel urlLabel = new JLabel("URL:");

JTextField urlTextField = new JTextField(40);

JButton urlGoButton = new JButton("Go");

JEditorPane editorPane = new JEditorPane();

JLabel statusLabel = new JLabel("Ready");

public HTMLBrowser(String title) {

super(title);

initFrame();

}

// Initialize the JFrame and add components to it

private void initFrame() {

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Container contentPane = this.getContentPane();

Box urlBox = this.getURLBox();

Box editorPaneBox = this.getEditPaneBox();

contentPane.add(urlBox, BorderLayout.NORTH);

contentPane.add(editorPaneBox, BorderLayout.CENTER);

contentPane.add(statusLabel, BorderLayout.SOUTH);

}

private Box getURLBox() {

// URL Box consists of a JLabel, a JTextField and a JButton

Box urlBox = Box.createHorizontalBox();

urlBox.add(urlLabel);

urlBox.add(urlTextField);

urlBox.add(urlGoButton);

// Add an action listener to urlTextField, so when the user enters a url

// and presses the enter key, the appplication navigates to the new URL.

urlTextField.addActionListener(e -> {

String urlString = urlTextField.getText();

go(urlString);

});

// Add an action listener to the Go button

urlGoButton.addActionListener(e -> go());

return urlBox;

}

private Box getEditPaneBox() {

// To display HTML, you must make the editor pane non-editable.

// Otherwise, you will see an editable HTML page that doesnot look nice.

editorPane.setEditable(false);

// URL Box consists of a JLabel, a JTextField and a JButton

Box editorBox = Box.createHorizontalBox();

// Add a JEditorPane inside a JScrollPane to provide scolling

editorBox.add(new JScrollPane(editorPane));

// Add a hyperlink listener to the editor pane, so that it

// navigates to a new page, when the user clicks a hyperlink

editorPane.addHyperlinkListener((HyperlinkEvent event) -> {

if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {

go(event.getURL());

} else if (event.getEventType() == HyperlinkEvent.EventType.ENTERED) {

statusLabel.setText("Please click this link to visit the page");

} else if (event.getEventType() == HyperlinkEvent.EventType.EXITED) {

statusLabel.setText("Ready");

}

});

// Add a property change listener, so we can update

// the URL text field with url of the new page

editorPane.addPropertyChangeListener((PropertyChangeEvent e) -> {

String propertyName = e.getPropertyName();

if (propertyName.equalsIgnoreCase("page")) {

URL url = editorPane.getPage();

urlTextField.setText(url.toExternalForm());

}

});

return editorBox;

}

// Navigates to the url entered in the URL JTextField

public void go() {

try {

URL url = new URL(urlTextField.getText());

this.go(url);

}

catch (MalformedURLException e) {

setStatus(e.getMessage());

}

}

// Navigates to the specified URL

public void go(URL url) {

try {

editorPane.setPage(url);

urlTextField.setText(url.toExternalForm());

setStatus("Ready");

}

catch (IOException e) {

setStatus(e.getMessage());

}

}

// Navigates to the specified URL specified as a string

public void go(String urlString) {

try {

URL url = new URL(urlString);

go(url);

}

catch (IOException e) {

setStatus(e.getMessage());

}

}

private void setStatus(String status) {

statusLabel.setText(status);

}

public static void main(String[] args) {

HTMLBrowser browser = new HTMLBrowser("HTML Browser");

browser.setSize(700, 500);

browser.setVisible(true);

// Let us visit yahoo.com

browser.go("http://www.yahoo.com

}

}

以下是该计划的重要部分:

  • 该方法将一个JLabel、一个JTextField和一个JButton打包在一个水平框中,并将其添加到框架的北部区域。它向JTextFieldJButton添加了一个动作监听器,这样当用户在输入新的 URL 后按回车键或 Go 按钮时,浏览器就会导航到新的 URL。
  • 该方法将一个JEditorPane封装在一个JScrollPane中,并将其添加到帧的中心区域。它还向JEditorPane添加了一个超链接监听器和一个属性更改监听器。超链接侦听器用于在用户单击超链接时导航到 URL。当鼠标进入和退出超链接区域时,它还会在状态栏中显示相应的帮助消息。
  • 一个JLabel用于在框架的南部区域显示一条简短信息。
  • 该方法已被重载,它的主要工作是使用setPage()方法导航到一个新页面。
  • main()方法用于测试。它在浏览器中显示雅虎的主页。

作为一项任务,您可以在浏览器中添加BackForward按钮,让用户在已经访问过的网页之间来回导航。

Tip

为了以良好的格式显示 HTML 页面,您需要通过调用setEditable(false)方法使JEditorPane不可编辑。你不应该使用一个JEditorPane来显示所有类型的 HTML 页面,因为它不能处理所有可以嵌入到 HTML 页面中的不同内容。相反,您应该只使用它来显示包含基本 HTML 内容的 HTML 页面,例如应用的 HTML 帮助文件。

耶文本字符串

JTextPane类是JEditorPane类的子类。它是一个专门的组件,用于处理带有嵌入图像和组件的样式化文档。您可以设置字符和段落的属性。如果你想显示一个 HTML,RTF,或者普通文档,JEditorPane是你最好的选择。但是,如果您需要文字处理器提供的丰富功能来编辑/显示样式化的文本,您需要使用JTextPane。这是一台小型文字处理机。它总是适用于样式化的文档,即使其内容是纯文本。本节不可能讨论它的所有特性;它本身就配得上一本小书。我将谈到它的特性,比如设置样式文本、嵌入图像和组件。

一个JTextPane使用一个样式化的文档,它是接口的一个实例。StyledDocument接口继承了Document接口。DefaultStyledDocumentStyledDocument接口的实现类。A JTextPane使用 a DefaultStyledDocument作为其默认型号。Swing 文本组件中的文档由以树状结构组织的元素组成。顶部元素称为根元素。文档中的一个元素是javax.swing.text.Element接口的一个实例。

普通文档有一个根元素。根元素可以有多个子元素。每个子元素由一行文本组成。请注意,在普通文档中,文档中的所有字符都具有相同的属性(或格式样式)。

样式化文档有一个根元素,也称为节。根元素有分支元素,也称为段落。一个段落有一连串的字符。一个字符串是一组共享相同属性的连续字符。例如,“Hello world”字符串定义了一个字符串。然而,“Hello world”字符串定义了两个字符串。注意单词“world”是粗体,而“Hello”不是。这就是为什么他们定义了两个不同的字符运行。在一个样式化的文档中,一个段落以一个换行符结束,除非是最后一个段落,它不需要以换行符结束。您可以在段落级别定义属性,如缩进、行距、文本对齐等。您可以在字符运行级别定义属性,如字体大小、字体系列、粗体、斜体等。图 2-10 和图 2-11 分别显示了普通文档和样式化文档的结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-11。

Structure of a styled document

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-10。

Structure of a plain document

清单 2-5 中的程序使用一个JTextPane开发了一个基本的文字处理器。它允许您编辑文本,并对文本应用粗体、斜体、颜色和对齐等样式。

清单 2-5。使用 JTextPane 和 JButtons 的简单文字处理器

// WordProcessor.java

package com.jdojo.swing;

import javax.swing.JFrame;

import java.awt.Container;

import javax.swing.JTextPane;

import javax.swing.JButton;

import java.awt.BorderLayout;

import javax.swing.JPanel;

import javax.swing.text.StyledDocument;

import javax.swing.text.BadLocationException;

import javax.swing.text.Style;

import javax.swing.text.StyleContext;

import javax.swing.text.StyleConstants;

import java.awt.Color;

public class WordProcessor extends JFrame {

JTextPane textPane = new JTextPane();

JButton normalBtn = new JButton("Normal");

JButton boldBtn = new JButton("Bold");

JButton italicBtn = new JButton("Italic");

JButton underlineBtn = new JButton("Underline");

JButton superscriptBtn = new JButton("Superscript");

JButton blueBtn = new JButton("Blue");

JButton leftBtn = new JButton("Left Align");

JButton rightBtn = new JButton("Right Align");

public WordProcessor(String title) {

super(title);

initFrame();

}

private void initFrame() {

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Container contentPane = this.getContentPane();

JPanel buttonPanel = this.getButtonPanel();

contentPane.add(buttonPanel, BorderLayout.NORTH);

contentPane.add(textPane, BorderLayout.CENTER);

this.addStyles(); // Add styles to the text pane for later use

insertTestStrings(); // Insert some texts to the text pane

}

private JPanel getButtonPanel() {

JPanel buttonPanel = new JPanel();

buttonPanel.add(normalBtn);

buttonPanel.add(boldBtn);

buttonPanel.add(italicBtn);

buttonPanel.add(underlineBtn);

buttonPanel.add(superscriptBtn);

buttonPanel.add(blueBtn);

buttonPanel.add(leftBtn);

buttonPanel.add(rightBtn);

// Add ation event listeners to buttons

normalBtn.addActionListener(e -> setNewStyle("normal", true));

boldBtn.addActionListener(e -> setNewStyle("bold", true));

italicBtn.addActionListener(e -> setNewStyle("italic", true));

underlineBtn.addActionListener(e -> setNewStyle("underline", true));

superscriptBtn.addActionListener(e -> setNewStyle("superscript", true));

blueBtn.addActionListener(e -> setNewStyle("blue", true));

leftBtn.addActionListener(e -> setNewStyle("left", false));

rightBtn.addActionListener(e -> setNewStyle("right", false));

return buttonPanel;

}

private void addStyles() {

// Get the default style

StyleContext sc = StyleContext.getDefaultStyleContext();

Style defaultContextStyle = sc.getStyle(StyleContext.DEFAULT_STYLE);

// Add some styles to the document, to retrieve and use later

StyledDocument document = textPane.getStyledDocument();

Style normalStyle = document.addStyle("normal", defaultContextStyle);

// Create a bold style

Style boldStyle = document.addStyle("bold", normalStyle);

StyleConstants.setBold(boldStyle, true);

// Create an italic style

Style italicStyle = document.addStyle("italic", normalStyle);

StyleConstants.setItalic(italicStyle, true);

// Create an underline style

Style underlineStyle = document.addStyle("underline", normalStyle);

StyleConstants.setUnderline(underlineStyle, true);

// Create a superscript style

Style superscriptStyle = document.addStyle("superscript", normalStyle);

StyleConstants.setSuperscript(superscriptStyle, true);

// Create a blue color style

Style blueColorStyle = document.addStyle("blue", normalStyle);

StyleConstants.setForeground(blueColorStyle, Color.BLUE);

// Create a left alignment paragraph style

Style leftStyle = document.addStyle("left", normalStyle);

StyleConstants.setAlignment(leftStyle, StyleConstants.ALIGN_LEFT);

// Create a right alignment paragraph style

Style rightStyle = document.addStyle("right", normalStyle);

StyleConstants.setAlignment(rightStyle, StyleConstants.ALIGN_RIGHT);

}

private void setNewStyle(String styleName, boolean isCharacterStyle) {

StyledDocument document = textPane.getStyledDocument();

Style newStyle = document.getStyle(styleName);

int start = textPane.getSelectionStart();

int end = textPane.getSelectionEnd();

if (isCharacterStyle) {

boolean replaceOld = styleName.equals("normal");

document.setCharacterAttributes(start, end - start,

newStyle, replaceOld);

}

else {

document.setParagraphAttributes(start, end - start, newStyle, false);

}

}

private void insertTestStrings() {

StyledDocument document = textPane.getStyledDocument();

try {

document.insertString(0, "Hello JTextPane\n", null);

}

catch (BadLocationException e) {

e.printStackTrace();

}

}

public static void main(String[] args) {

WordProcessor frame = new WordProcessor("Word Processor");

frame.setSize(700, 500);

frame.setVisible(true);

}

}

文字处理程序有点冗长。然而,它做简单、重复的事情。为了更容易理解,我把程序的逻辑分解成了更小的部分。这个程序的目的是展示一个JTextPane,用户可以在这里编辑文本并使用一些按钮对文本应用样式

有八个按钮。其中五种用于格式化文本:普通、粗体、斜体、下划线和上标。Blue按钮用于将文本颜色设置为蓝色。最后两个按钮Left AlignRight Align,用于设置段落左右对齐。

什么是样式,如何为文本和段落设置样式?简单地说,样式是属性(名称-值对)的集合。设置样式很简单;但是,您需要编写几行代码来拥有该样式本身。您可以向JTextPane的文档和JTextPane本身添加样式。你需要使用StyledDocument类的addStyle(String styleName, Style parent)方法。它返回一个Style对象。parent自变量可以是null。如果不是null,未指定的属性将以parent样式解析。一旦有了样式对象,就可以使用StyleConstants类的setXxx()方法来设置该样式中的适当属性。如果你感到困惑,这里有一个回顾。

把一个样式想象成一个有两列的表格:namevalueStyledDocument类的addStyle()方法返回一个空样式(意味着一个空表)。通过使用StyleConstantssetXxx()方法,您正在向样式添加新行(也就是向表格)。一旦表格中至少有一行(即至少定义了一个样式属性),就可以根据样式类型将该样式应用于字符或段落。请注意,您可以使用空样式。空样式可用于从字符范围或段落中移除所有当前样式。下面的代码片段创建了两种样式:第一种是bold,第二种是bold + italic。如果将第一种样式应用于文本,它会将文本格式化为粗体。如果将第二种样式应用于文本,它会将文本格式化为粗体和斜体。注意,您正在将parent样式设置为null

// Get the styled document from the text pane

StyledDocument document = textPane.getStyledDocument();

// Add an empty style named "bold" to the document

Style bold = document.addStyle("bold", null);

// Add bold attribute to this style

StyleConstants.setBold(bold, true);

// From this point on, you can use the bold style

// Let's create a bold + italic style called boldItalic.

// Add an empty style named boldItalic to the document

Style boldItalic = document.addStyle("boldItalic", null);

// Add bold and italic attributes to the boldItalic style

StyleConstants.setBold(boldItalic, true);

StyleConstants.setItalic(boldItalic, true);

// From this point on, you can use the boldItalic style

将样式对象添加到StyledDocument后,您可能需要它的引用。您可以通过使用它的getStyle(String styleName)方法来检索相同样式的引用。

// Get the bold style from document

Style myBoldStyle = document.getStyle("bold");

一旦有了Style对象,就可以使用StyledDocument类的setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace)setParagraphAttributes (int offset, int length, AttributeSet s, boolean replace)方法将样式设置为字符范围或段落。如果 replace 参数被指定为true,该区域的任何旧样式都将被新样式替换。否则,新样式将与旧样式合并。

// Suppose a text pane has more than five characters in it.

// Make the first three characters bold

document.setCharacterAttributes(0, 3, bold, false);

一个StyleContext对象为它们的有效使用定义了一个样式池。您可以获取默认的样式集合,如下所示:

StyleContext sc = StyleContext.getDefaultStyleContext();

Style defaultContextStyle = sc.getStyle(StyleContext.DEFAULT_STYLE);

// Let's add a default context style as normal style's parent.

// We do not add any extra attribute to normal styles

StyledDocument document = textPane.getStyledDocument();

Style normal = document.addStyle("normal", defaultContextStyle);

表 2-13 包含了一系列重要的方法及其描述,可以帮助你理解清单 2-5 中的代码。图 2-12 显示了在简单的文字处理器中输入 E = mc 2 后的样子。

表 2-13。

Methods of the WordProcessor Class With Their Descriptions

| 方法 | 描述 | | --- | --- | | `initFrame()` | 通过向框架添加组件并设置`JFrame`的默认行为来初始化框架。 | | `getButtonPanel()` | 返回一个`JPanel`,它包含所有用于格式化的`JButton`。它还为所有的`JButton`添加了动作监听器 | | `addStyles()` | 它向文档添加样式。默认的上下文样式名为“normal ”,它用作所有其他样式的父样式。粗体、斜体等样式。,是字符级样式,而左和右是段落级样式。这些样式是从文档中获取的,以便在`setNewStyle()`方法中使用。 | | `setNewStyle()` | 它将样式设置为字符范围或段落范围,如其参数`isCharacterStyle`所示。请注意,如果您设置了“正常”样式,您将使用此样式替换整个样式。否则,您将合并样式。这个逻辑由下面的语句决定:`boolean replaceOld = styleName.equals("normal");` | | `insertTestStrings()` | 使用`insertString()`方法将字符串插入到`JTextPane`的文档中。 | | `main()` | 创建并显示字处理器框架。 |

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-12。

A simple word processor using a JTextPane and JButtons

文字处理器没有保存功能。在真实的应用中,您会提示用户保存文件的位置和名称。下面的代码片段将JTextPane的内容保存到当前工作目录中名为test.rtf的文件中:

// Save the contents of the textPane to a file

FileWriter fw = new java.io.FileWriter("test.rtf");

textPane.write(fw);

fw.close();

JTextPanewrite()方法将包含在其文档中的文本写成纯文本。如果你想保存格式化的文本,你需要使用一个RTFEditorKit对象作为它的编辑器工具包,并使用该编辑器工具包的write()方法写入一个文件。下面的代码片段展示了如何使用一个RTFEditorKit对象在一个JTextPane中保存格式化的文本。注意,RTFEditorKit包含一个read()方法来将格式化的文本读回给JTextPane

// Set an RTFEditorKit to a JTextPane right after you create it

JTextPane textPane = new JTextPane();

textPane.setEditorKit(new RTFEditorKit());

// Other code goes here

// Save formatted text from the JTextPane to a file

String fileName = "test.rtf";

FileOutputStream fos = new FileOutputStream(fileName);

RTFEditorKit kit = (RTFEditorKit)textPane.getEditorKit();

StyledDocument doc = textPane.getStyledDocument();

int len = doc.getLength();

kit.write(fos, doc, 0, len);

fos.close();

Tip

如果你想保存添加到一个JTextPane的图标和组件,你需要将一个JTextPane的文档对象序列化到一个文件中,然后加载回来显示相同的内容。

您可以向JTextPane添加任何 Swing 组件和图标。它只是将一个组件或图标包装成一种样式,并在insertString()方法中使用该样式。下面的代码片段展示了如何将一个JButton和一个图标添加到一个:

// Add a Close button to our document

JButton closeButton = new JButton("Close");

closeButton.addActionListener(e -> System.exit(0));

Style cs = doc.addStyle("componentStyle", null);

StyleConstants.setComponent(cs, closeButton);

// Insert the component at the end of the text.

try {

document.insertString(doc.getLength(), "Close Button goes", cs);

}

catch (BadLocationException e) {

e.printStackTrace();

}

JTextPane添加图标类似于向其添加组件,只是您使用了StyleConstants类的setIcon()方法而不是setComponent()方法,并且使用了ImageIcon对象而不是组件,如图所示:

// Add an icon to a JTextPane

StyleConstants.setIcon(myIconStyle, new ImageIcon("myImageFile"));

Tip

你也可以使用JTextPaneinsertComponent(Component c)insertIcon(Icon g)方法来分别插入一个组件和一个图标。

您可以通过使用AbstractDocument类的dump(PrintStream p)方法来查看JTextPane文档的元素结构。以下代码片段在标准输出中显示转储:

// Display the document structure on the standard output

DefaultStyledDocument doc = (DefaultStyledDocument)textPane.getStyledDocument();

doc.dump(System.out);

下面是一个JTextPane的带文本的文档的转储,如图 2-12 所示。它让您对样式化文档的结构有所了解。

<section>

<paragraph

resolver=NamedStyle:default {bold=false,name=default,foreground=sun.swing.PrintColorUIResource[r=51,g=51,b=51],family=Dialog,FONT_ATTRIBUTE_KEY=javax.swing.plaf.FontUIResource[family=Dialog,name=Dialog,style=plain,size=12],size=12,italic=false,}

>

<content>

[0,16][Hello JTextPane

]

<paragraph

resolver=NamedStyle:default {bold=false,name=default,foreground=sun.swing.PrintColorUIResource[r=51,g=51,b=51],family=Dialog,FONT_ATTRIBUTE_KEY=javax.swing.plaf.FontUIResource[family=Dialog,name=Dialog,style=plain,size=12],size=12,italic=false,}

>

<content>

[16,17][

]

<paragraph

resolver=NamedStyle:default {bold=false,name=default,foreground=sun.swing.PrintColorUIResource[r=51,g=51,b=51],family=Dialog,FONT_ATTRIBUTE_KEY=javax.swing.plaf.FontUIResource[family=Dialog,name=Dialog,style=plain,size=12],size=12,italic=false,}

>

<content

bold=true

name=bold

resolver=NamedStyle:normal {name=normal,resolver=AttributeSet,}

>

[17,21][E=mc]

<content

bold=true

name=bold

resolver=NamedStyle:normal {name=normal,resolver=AttributeSet,}

superscript=true

>

[21,22][2]

<content>

[22,23][

]

<bidi root>

<bidi level

bidiLevel=0

>

[0,23][Hello JTextPane

E=mc2

]

验证文本输入

您已经看到了在文本组件中验证文本输入的例子:使用定制模型和使用JFormattedTextField。您可以将一个输入验证器对象附加到任何一个JComponent,包括一个文本组件。输入验证器对象只是一个类的对象,它继承自名为InputVerifier的抽象类。该类声明如下:

public abstract class InputVerifier {

public abstract boolean verify(JComponent input);

public boolean shouldYieldFocus(JComponent input) {

return verify(input);

}

}

您需要覆盖InputVerifier类的verify()方法。该方法包含验证文本字段中的输入的逻辑。如果文本字段中的值有效,则从此方法返回true。否则,你返回false。当文本字段将要失去焦点时,它的输入验证器的verify()方法被调用。只有当文本字段的输入验证器的verify()方法返回true时,文本字段才会失去焦点。文本组件的setInputVerifier()方法用于附加一个输入验证器。下面的代码片段将输入验证器设置为区号字段。它将在该字段中保持焦点,直到用户输入一个三位数的数字区号。如果字段为空,它允许用户导航到另一个字段。

// Create an area code JTextField

JTextField areaCodeField = new JTextField(3);

// Set an input verifier to the area code field

areaCodeField.setInputVerifier(new InputVerifier() {

public boolean verify(JComponent input) {

String areaCode = areaCodeField.getText();

if (areaCode.length() == 0) {

return true;

} else if (areaCode.length() != 3) {

return false;

}

try {

Integer.parseInt(areaCode);

return true;

}

catch(NumberFormatException e) {

return false;

}

}

});

您可以使用setInputVerifier()方法为任何JComponent设置输入验证器。通常,它仅用于文本字段。作为一个良好的 GUI 设计实践,您应该添加一些关于有效输入值的视觉提示,这样用户就可以理解字段中需要什么样的值。例如,您可能希望为“区号”字段添加一个带有文本“区号(三位数):”的标签,或者当用户在字段中输入无效值时显示一条错误消息。如果没有关于输入验证器字段的有效值的视觉线索,用户将被困在字段中,不知道输入哪种值。

做出选择

Swing 提供了以下组件,允许您从选项列表中进行选择:

  • JToggleButton
  • JCheckBox
  • JRadioButton
  • JComboBox
  • JList

可供从列表中选择的选项的数量可以从 2 到 N 变化,其中 N 是大于 2 的数。从选项列表中进行选择有不同的方法:

  • 该选择可以是互斥的。也就是说,用户只能从选项列表中做出一个选择。在互斥选择中,如果用户更改当前选择,则会自动取消选择之前的选择。例如,MaleFemaleUnknown三个选项的性别选择列表是互斥的。用户只能选择三个选项中的一个,而不能同时选择两个或更多。
  • 有一种特殊的选择情况,其中选择数 N 是 2。在这种情况下,选择类型为boolean : truefalse。有时它们也被称为Yes / No选择,或者On / Off选择。
  • 有时,用户可以从选项列表中进行多项选择。例如,您可以向用户提供一个爱好列表,用户可以从列表中选择一个以上的爱好。

Swing 组件使您能够向用户呈现不同种类的选择,并让用户选择零个、一个或多个选项。图 2-13 显示了四个季节名称的秋千组件:SpringSummerFallWinter。该图显示了可用于从列表中选择选项的五种不同类型的 Swing 组件的外观。此图中显示的某些组件可能不适合它所显示的选项。例如,尽管可以使用一组复选框来显示互斥选项的列表,但这不是一个好的 GUI 实践。当选项相互排斥时,一组单选按钮被认为比一组复选框更合适。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-13。

Swing components to make a selection from a list of choices

是一个双态按钮。这两种状态是选中和取消选中。当您按下切换按钮时,它会在按下和未按下之间切换。按下是其选中状态,未按下是其未选中状态。请注意,JButtonJToggleButton的工作方式和用法不同。一个JButton只有当鼠标按在它上面时才被按下,而一个JToggleButton在按下和未按下状态之间切换。一个JButton用于启动一个动作,而一个JToggleButton用于从可能的选项列表中选择一个选项。通常,一组JToggleButton用于让用户从互斥选项列表中选择一个选项。一个JToggleButton用于当用户有一个boolean选择时,他需要指示truefalse(或者,是或否)。按下状态表示选择了true,未按下状态表示选择了false

a 也有两种状态:选中和未选中。当用户可以从两个或更多选项的列表中选择零个或更多选项时,使用一组JCheckBox es。当用户有一个boolean选择来指示truefalse时,使用一个JCheckBox

a 也有两种状态:选中和未选中。当有两个或更多互斥选项的列表并且用户必须选择一个选项时,使用一组JRadioButton。一个JRadioButton永远不会作为一个独立的组件用于从两个boolean选项truefalse中做出选择。它总是以两个或两个以上为一组使用。当你必须让用户在两个布尔选择truefalse之间进行选择时,应该使用JCheckBox(不是JRadioButton)。

JToggleButtonJCheckBoxJRadioButton的构造函数允许您使用不同参数的组合来创建它们。您可以使用一个Action对象、一个字符串标签、一个图标和一个boolean标志(表示它是否被默认选中)的组合来创建它们。默认情况下,JToggleButtonJCheckBoxJRadioButton未选中。下面的代码片段展示了创建它们的一些方法:

// Create them with no label and no image

JToggleButton tb1 = new JToggleButton();

JCheckBox cb1 = new JCheckBox();

JRadioButton rb1 = new JRadioButton();

// Create them with text as "Multi-Lingual"

JToggleButton tb2 = new JToggleButton("Multi-Lingual");

JCheckBox cb2 = new JCheckBox("Multi-Lingual");

JRadioButton rb2 = new JRadioButton("Multi-Lingual");

// Create them with text as "Multi-Lingual" and selected by default

JToggleButton tb3 = new JToggleButton("Multi-Lingual", true);

JCheckBox cb3 = new JCheckBox("Multi-Lingual", true);

JRadioButton rb3 = new JRadioButton("Multi-Lingual", true);

要选择/取消选择一个JToggleButtonJCheckBoxJRadioButton,需要调用它们的setSelected()方法。要检查它们是否被选中,使用它们的isSelected()方法。以下代码片段显示了如何使用这些方法:

tb3.setSelected(true);        // Select tb3

boolean b1 = tb3.isSelected(); // will store true in b1

tb3.setSelected(false);        // Unselect tb3

boolean b2 = tb3.isSelected(); // will store false in b2

如果选择是互斥的,则必须将所有选择组合在一个按钮组中。在互斥的选项组中,如果您选择了一个选项,则所有其他选项都不会被选中。通常,您为一组互斥的JRadioButtonJToggleButton创建一个按钮组。理论上,您也可以为具有互斥选择的JCheckBox创建一个按钮组。但是,不建议在 GUI 中使用一组互斥的JCheckBox es。

类别的执行个体代表按钮群组。您可以分别通过使用按钮组的add()remove()方法来添加和移除按钮组的JRadioButtonJToggleButton。最初,按钮组的所有成员都是未选中的。要形成一个按钮组,需要将所有互斥的选择组件添加到一个ButtonGroup类的对象中。您不能向容器添加(事实上,您不能添加)一个ButtonGroup对象。您必须将所有选项组件添加到容器中。清单 2-6 包含了显示一组三个互斥JRadioButton的完整代码。

清单 2-6。由三个 JRadioButtons 表示的一组互斥的三个选项

// ButtonGroupFrame.java

package com.jdojo.swing;

import java.awt.BorderLayout;

import java.awt.Container;

import javax.swing.Box;

import javax.swing.ButtonGroup;

import javax.swing.JFrame;

import javax.swing.JRadioButton;

public class ButtonGroupFrame extends JFrame {

ButtonGroup genderGroup = new ButtonGroup();

JRadioButton genderMale = new JRadioButton("Male");

JRadioButton genderFemale = new JRadioButton("Female");

JRadioButton genderUnknown = new JRadioButton("Unknown");

public ButtonGroupFrame() {

this.initFrame();

}

private void initFrame() {

this.setTitle("Mutually Exclusive JRadioButtons Group");

this.setDefaultCloseOperation(EXIT_ON_CLOSE);

// Add three gender JRadioButtons to a ButtonGroup,

// so they become mutually exclusive choices

genderGroup.add(genderMale);

genderGroup.add(genderFemale);

genderGroup.add(genderUnknown);

// Add gender radio button to a vertical Box

Box b1 = Box.createVerticalBox();

b1.add(genderMale);

b1.add(genderFemale);

b1.add(genderUnknown);

// Add the vertical box to the center of the frame

Container contentPane = this.getContentPane();

contentPane.add(b1, BorderLayout.CENTER);

}

public static void main(String[] args) {

ButtonGroupFrame bf = new ButtonGroupFrame();

bf.pack();

bf.setVisible(true);

}

}

是另一种类型的 Swing 组件,它允许您从选项列表中进行选择。或者,它可以包含一个可编辑字段,允许您键入新的选择值。类型参数E是它包含的元素的类型。当屏幕空间有限时,你可以用一个JComboBox代替一组JToggleButtonJCheckBoxJRadioButton。使用JComboBox可以节省屏幕空间。然而,用户必须执行两次点击来进行选择。首先,用户必须点击箭头按钮来显示下拉列表中的选项列表,然后他必须点击列表中的一个选项。用户还可以使用键盘上的上/下箭头键来滚动选项列表,并在组件处于焦点时选择一个选项。您可以通过在一个构造函数中传递选择列表来创建一个JComboBox,如下所示:

// Use an array of String as the list of choices

String[] sList = new String[]{"Spring", "Summer", "Fall", "Winter"};

JComboBox<String> seasons = new JComboBox<>(sList);

// Use a Vector of String as the list of choices

Vector<String> sList2 = new Vector<>(4);

sList2.add("Spring");

sList2.add("Summer");

sList2.add("Fall");

sList2.add("Winter");

JComboBox<String> seasons2 = new JComboBox<>(sList2);

您可以创建一个没有选择的JComboBox,然后通过使用它的一个方法向它添加选择。它还包括从列表中移除选择并获取所选选择的值的方法。表 2-14 显示了JComboBox类的常用方法列表。

表 2-14。

Commonly Used Methods of the JComboBox class

| 方法 | 描述 | | --- | --- | | `void addItem(E item)` | 将项目作为选项添加到列表中。对添加的对象调用`toString()`方法,返回的字符串显示为一个选项。 | | `E getItemAt(int index)` | 从选择列表中返回指定`index`处的项目。索引从零开始,到列表大小减一结束。如果指定的`index`越界,则返回`null`。 | | `int getItemCount()` | 返回选项列表中的项数。 | | `int getSelectedIndex()` | 返回选定项的索引。如果选定的项目不在列表中,则返回–1。请注意,对于可编辑的`JComboBox`,您可以在字段中键入一个新值,该值可能不在选项列表中。在这种情况下,该方法将返回–1。如果没有选择,它也返回–1。 | | `Object getSelectedItem()` | 返回当前选定的项目。如果没有选择,则返回`null`。 | | `void insertItemAt(E item, int index)` | 在列表中指定的`index`处插入指定的`item`。 | | `boolean isEditable()` | 如果`JComboBox`可编辑,则返回`true`。否则,它返回`false`。默认情况下,`JComboBox`是不可编辑的。 | | `void removeAllItems()` | 从列表中移除所有项目。 | | `void removeItem(Object item)` | 从列表中删除指定的`item`。 | | `void removeItemAt(int index)` | 移除指定`index`处的项目。 | | `void setEditable(boolean editable)` | 如果指定的`editable`参数是`true`,则`JComboBox`是可编辑的。否则,它是不可编辑的。用户可以在可编辑的`JComboBox`中键入一个值,这个值不在选择列表中。请注意,新键入的值不会添加到选项列表中。 | | `void setSelectedIndex(int index)` | 选择列表中指定`index`处的项目。如果指定的`index`为–1,则清除选择。如果指定的`index`小于-1 或者大于列表的大小减 1,它抛出一个`IllegalArgumentException`。 | | `void setSelectedItem(Object item)` | 选择字段中的项目。如果指定的`item`存在于列表中,它总是被选中。如果列表中不存在指定的项目,则仅当`JComboBox`可编辑时,该项目才会在字段中被选中。 |

如果您想在JComboBox中选择或取消选择某个项目时得到通知,您可以为其添加一个项目监听器。每当选择或取消选择某个项目时,都会通知项目监听器。请注意,当您更改JComboBox中的选择时,它会触发取消选择的项目事件,随后是选择的事件。下面的代码片段展示了如何向JComboBox添加一个项目监听器。您可以使用ItemEvent类的getItem()方法来找出哪个项目被选中或取消选中。

String[] sList = new String[]{"Spring", "Summer", "Fall", "Winter"};

JComboBox<String> seasons = new JComboBox<>(sList);

// Add an item listener to the combobox

seasons.addItemListener((ItemEvent e) -> {

Object item = e.getItem();

if (e.getStateChange() == ItemEvent.SELECTED) {

// Item has been selected

System.out.println(item + " has been selected");

} else if (e.getStateChange() == ItemEvent.DESELECTED) {

// Item has been deselected

System.out.println(item + " has been deselected");

}

});

是另一个 Swing 组件,它显示一个选项列表,并允许您从该列表中选择一个或多个选项。类型参数T是它包含的元素的类型。一个JList与一个JComboBox的区别主要在于它显示选择列表的方式。一个JList可以在屏幕上显示多个选项,而一个JComboBox可以在你点击箭头按钮时显示选项列表。从这个意义上说,a JList是 a JComboBox的扩展版。一个JList可以在一列或多列中显示选择列表。您可以像创建JComboBox一样创建JList,如下所示:

// Create a JList using an array

String[] items = new String[]{"Spring", "Summer", "Fall", "Winter"};

JList<String> list = new JList<>(items);

// Create a JList using a Vector

Vector<String> items2 = new Vector<>(4);

items2.add("Spring");

items2.add("Summer");

items2.add("Fall");

items2.add("Winter");

JList<String> list2 = new JList<>(items2);

A JList不具备滚动能力。您必须将它添加到一个JScrollPane中,并将JScrollPane添加到容器中,以获得滚动功能,如下所示:

myContainer.add(new JScrollPane(myJList));

您可以配置JList的布局方向,以三种方式排列选项列表:

  • 垂直的
  • 水平环绕
  • 垂直环绕

在默认的垂直排列中,JList中的所有项目都使用一列多行显示。

在水平包装中,所有项目排列成一行和多列。但是,如果一行中容纳不下所有项目,则需要添加新行来显示这些项目。请注意,根据组件的方向,该项可以从左到右或从右到左水平排列。

在垂直包装中,所有项目都排列在一列和多行中。但是,如果一列中容纳不下所有项目,则需要添加新列来显示它们。

您可以使用JList类的setVisibleRowCount(int visibleRows)方法来设置您希望在列表中看到的不需要滚动的可见行数。当您将可见行数设置为零或更小时,JList将根据字段的宽度/高度及其布局方向决定可见行数。您可以使用其setLayoutOrientation(int orientation)方法设置其布局方向,其中方向值可以是在JList类中定义的三个常量之一:JList.VERTICALJList.HORIZONTAL_WRAPJList.VERTICAL_WRAP

您可以使用setSelectionMode(int mode)方法配置JList的选择模式。模式值可以是以下三个值之一。模式值在ListSelectionModel界面中被定义为常量。

  • SINGLE_SELECTION
  • SINGLE_INTERVAL_SELECTION
  • MUTIPLE_INTERVAL_SELECTION

在单一选择模式下,一次只能选择一个项目。如果您更改选择,以前选择的项目将被取消选择。

在单个间隔选择模式中,您可以选择多个项目。但是,选定的项目必须总是连续的。假设您在一个JList中有十个项目,并且您选择了第七个项目。现在,您可以选择列表中的第六个或第八个项目,但不能选择任何其他项目。您可以继续选择更多连续的项目。您可以使用Ctrl键或Shift键和鼠标的组合进行连续选择。

在多间隔部分,您可以不受任何限制地选择多个项目。您可以使用 Ctrl 键或 Shift 键和鼠标的组合来进行选择。

您可以在JList中添加一个列表选择监听器,当选择发生变化时,它会通知您。当选择改变时,调用ListSelectionListenervalueChanged()方法。在一次选择更改过程中,也可能会多次调用此方法。您需要使用ListSelectionEvent对象的getValueIsAdjusting()方法来确保选择更改已经完成,如下面的代码片段所示:

myJList.addListSelectionListener((ListSelectionEvent e) -> {

// Make sure selection change is final

if (!e.getValueIsAdjusting()) {

// The selection changed logic goes here

}

});

表 2-15 列出了JList类的常用方法。注意,JList没有一个直接的方法来给出列表的大小(?? 中选择的数量)。由于每个 Swing 组件都使用一个模型,所以JList也是如此。它的模型是JListModel接口的一个实例。要知道一个JList的选择列表的大小,您需要调用它的模型的getSize()方法,就像这样:

int size = myJList.getModel().getSize();

表 2-15。

Commonly Used Methods of the JList Class

| 方法 | 描述 | | --- | --- | | `void clearSelection()` | 清除`JList`中的选择。 | | `void ensureIndexIsVisible(int index)` | 确保指定`index`处的项目可见。注意,要使不可见的项目可见,必须将`JList`添加到`JScrollPane`中。 | | `int getFirstVisibleIndex()` | 返回最小的可见索引。如果没有可见项目或列表为空,则返回–1。 | | `int getLastVisibleIndex()` | 返回最大的可见索引。如果没有可见项目或列表为空,则返回–1。 | | `int getMaxSelectionIndex()` | 返回最大的选定索引。如果没有选择,则返回–1。 | | `int getMinSelectionIndex()` | 返回最小的选定索引。如果没有选择,则返回–1。 | | `int getSelectedIndex()` | 返回最小的选定索引。如果`JList`选择模式为单选,则返回选中的索引。如果没有选择,则返回–1。 | | `int[] getSelectedIndices()` | 返回一个`int`数组中所有选定项目的索引。如果没有选择,数组将没有元素。 | | `E getSelectedValue()` | 返回第一个选定的项目。如果`JList`为单选模式,则为所选项的值。如果在`JList`中没有选择,则返回`null`。 | | `List getSelectedValuesList()` | 根据列表中的索引以升序返回所有选定项目的列表。如果没有选定的项目,则返回一个空列表。 | | `boolean isSelectedIndex(int index)` | 如果选择了指定的`index`,则返回`true`。否则,它返回`false`。 | | `boolean isSelectionEmpty()` | 如果`JList`中没有选择,则返回`true`。否则,它返回`false`。 | | `void setListData(E[] listData)` `void setListData(Vector<?> listData)` | 在`JList`中设置新的选择列表。 | | `void setSelectedIndex(int index)` | 在指定的`index`选择一个项目。 | | `void setSelectedIndices(int[] indices)` | 选择指定数组中索引处的项目 | | `void setSelectedValue(Object item, boolean shouldScroll)` | 选择列表中存在的指定项目。如果第二个参数是`true`,则滚动到该项使其可见。 |

JSpinner

一个JSpinner组件结合了一个JFormattedTextField和一个可编辑的JComboBox的优点。它允许您在一个JComboBox中设置一个选择列表,同时,您还可以对显示的值应用一种格式。它一次只显示选项列表中的一个值。它允许您输入新值。“spinner”这个名字来源于这样一个事实,它允许您通过使用上下箭头按钮来上下旋转选项列表。在JSpinner中,选择列表的一个特别之处是它必须是一个有序列表。图 2-14 显示了三个用于选择数字、日期和季节值的 JSpinners。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-14。

JSpinner components in action

因为一个JSpinner为各种选择列表提供了旋转能力,所以它在很大程度上依赖于它的创建模型。事实上,您必须在其构造函数中为JSpinner提供一个模型,除非您想要一个只有整数列表的简单的JSpinner。它支持三种不同的有序选择列表:数字列表、日期列表和任何其他对象列表。它提供了三个类来创建三种不同列表的模型:

  • SpinnerNumberModel
  • SpinnerDateModel
  • SpinnerListModel

旋转器模型是接口的一个实例。它定义了使用JSpinner中的值的getValue()setValue()getPreviousValue()getNextValue()方法。所有这些方法都与Object类的对象一起工作。

这个类为一个JSpinner提供了一个模型,可以让你浏览一个有序的数字列表。您需要在列表中指定最小值、最大值和当前值。当您使用JSpinner的向上/向下按钮时,您还可以指定步进数值,用于步进数字列表。下面的代码片段创建了一个包含从 1 到 10 的数字列表的JSpinner。它让你一步一步地浏览列表。该字段的当前值设置为 5。SpinnerNumberModel类也有一些方法,可以让您在创建 spinner 模型后获取/设置不同的值。

int minValue = 1;

int maxValue = 10;

int currentValue = 5;

int steps = 1;

SpinnerNumberModel nModel = new SpinnerNumberModel(currentValue, minValue, maxValue, steps);

JSpinner numberSpinner = new JSpinner(nModel);

这个类为一个JSpinner提供了一个模型,可以让你浏览一个有序的日期列表。您需要指定开始日期、结束日期、当前值和步骤。下面的代码片段创建了一个JSpinner来一次一天地遍历从 1950 年 1 月 1 日到 2050 年 12 月 31 日的日期列表。当前系统日期被设置为字段的当前值。

Calendar calendar = Calendar.getInstance();

calendar.set(1950, 1, 1);

Date minValue = calendar.getTime();

calendar.set(2050, 12, 31);

Date maxValue = calendar.getTime();

Date currentValue = new Date();

int steps = Calendar.DAY_OF_MONTH; // Must be a Calendar field

SpinnerDateModel dModel = new SpinnerDateModel(currentValue, minValue, maxValue, steps);

dateSpinner = new JSpinner(dModel);

请注意,日期值将以默认的区域设置格式显示。当在模型上使用getNextValue()方法时,使用步长值。带有日期列表的JSpinner可让您通过突出显示日期字段的一部分并使用向上/向下按钮来浏览任何显示的日期字段。假设你的JSpinner使用的日期格式是mm/dd/yyyy。您可以将光标放在字段的年份部分(yyyy),并使用上/下按钮根据年份浏览列表。

这个类为一个JSpinner提供了一个模型,可以让你在一个有序的对象列表中旋转。您只需指定一个对象数组或一个List对象,JSpinner将让您在列表出现在数组或List中时旋转列表。列表中对象的toString()方法返回的String显示为JSpinner中的值。下面的代码片段创建了一个JSpinner来显示四个季节的列表:

String[] seasons = new String[] {"Spring", "Summer", "Fall", "Winter"};

SpinnerListModel sModel = new SpinnerListModel(seasons);

listSpinner = new JSpinner(sModel);

一个JSpinner使用一个编辑器对象来显示当前值。它有以下三个内部类来显示三种不同的有序列表:

  • JSpinner。数字编辑器
  • JSpinner.DateEditor
  • JSpinner。列表编辑器

如果您想以特定的格式显示数字或日期,您需要为JSpinner设置一个新的编辑器。数字和日期编辑器的编辑器类允许您指定格式。下面的代码片段将数字格式设置为“00”,因此数字 1 到 10 显示为01, 02, 03...10。它将日期格式设置为mm/dd/yyyy

// Set the number format to "00"

JSpinner.NumberEditor nEditor = new JSpinner.NumberEditor(numberSpinner, "00");

numberSpinner.setEditor(nEditor);

// Set the date format to mm/dd/yyyy

JSpinner.DateEditor dEditor = new JSpinner.DateEditor(dateSpinner, "mm/dd/yyyy");

dateSpinner.setEditor(dEditor);

Tip

您可以使用JSpinnerSpinnerModel定义的getValue()方法来获取JSpinner中的当前值作为Object. SpinnerNumberModel,而SpinnerDateModel定义分别返回NumberDate对象的getNumber()getDate()方法。

JScrollBar

如果您想要查看比可用空间更大的组件,您需要使用JScrollBarJScrollPane组件。我将在下一节讨论JScrollPane。一个JScrollBar有一个方向属性,决定它是水平显示还是垂直显示。图 2-15 描绘了一个水平JScrollBar

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-15。

A horizontal JScrollBar

一个JScrollBar由四部分组成:两个箭头按钮(每端一个)、一个旋钮(也称为拇指)和一个轨道。单击箭头按钮时,旋钮在轨道上向箭头按钮移动。你可以借助鼠标将旋钮向两端拖动。你也可以通过点击轨道来移动旋钮。

您可以定制一个JScrollBar的各种属性,方法是在构造函数中传递它们的值,或者在创建之后设置它们。表 2-16 列出了一些常用的属性和操作它们的方法。

表 2-16。

Commonly Used Properties of a JScrollBar and Methods to Get/Set Those Properties

| 财产 | 方法 | 描述 | | --- | --- | --- | | `Orientation` | `getOrientation()` `setOrientation()` | 确定`JScrollBar`是水平还是垂直。它的值可以是两个常量之一,`HORIZONTAL`或`VERTICAL`,它们在`JScrollBar`类中定义。 | | `Value` | `getValue()` `setValue()` | 旋钮的位置就是它的值。最初,它被设置为零。 | | `Extent` | `getVisibleAmount()` `setVisibleAmount()` | 这是旋钮的大小。它与轨道的大小成比例。例如,如果轨迹大小代表 150,而您将范围设置为 25,则旋钮大小将是轨迹大小的六分之一。其默认值为 10。 | | `Minimum Value` | `getMinimum()` `setMinimum()` | 它表示的最小值。默认值为零。 | | `Maximum Value` | `getMaximum()` `setMaximum()` | 它表示的最大值。默认值为 100。 |

以下代码片段演示了如何创建具有不同属性的JScrollBar:

// Create a JScrollBar with all default properties. Its orientation

// will be vertical, current value 0, extent 10, minimum 0, and maximum 100

JScrollBar sb1 = new JScrollBar();

// Create a horizontal JScrollBar with default values

JScrollBar sb2 = new JScrollBar(JScrollBar.HORIZONTAL);

// Create a horizontal JScrollBar with a current value of 50,

// extent 15, minimum 1 and maximum 150

JScrollBar sb3 = new JScrollBar(JScrollBar.HORIZONTAL, 50, 15, 1, 150);

JScrollBar的当前值只能设置在其最小值和(最大范围)值之间。一个JScrollBar本身不会给 GUI 增加任何价值。它只有一些属性。您可以将一个AdjustmentListener添加到一个JScrollBar中,当它的值改变时会得到通知。

// Add an AdjustmentListener to a JScrollBar named myScrollBar

myScrollBar.addAdjustmentListener((AdjustmentEvent e) -> {

if (!e.getValueIsAdjusting()) {

// The logic for value changed goes here

}

});

使用一个JScrollBar来滚动一个尺寸大于其显示区域的组件并不简单。如果你想单独使用一个JScrollBar,你需要写大量的代码来完成这个任务。一个JScrollPane让这个任务变得更容易。它负责滚动,无需编写任何额外的代码。

JScrollPane

一个JScrollPane是一个最多可以容纳和展示九个组件的容器,如图 2-16 所示。它使用自己的布局管理器,它是类JScrollPaneLayout的一个对象。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-16。

The components of a JScrollPane

一个JScrollPane管理的九个组件是两个JScrollBar、一个视口、一个行标题、一个列标题和四个角。

  • 二:在图中,两个滚动条被命名为 HSB 和 VSB。它们是JScrollBar类的两个实例:一个水平的,一个垂直的。一个JScrollPane将为您创建和管理两个JScrollBar。您不需要为此编写任何代码。你唯一需要指出的是你是否想要它们,以及你希望它们何时出现。
  • 答:viewport 是一个区域,在这里一个JScrollPane显示可滚动的组件,比如一个JTextArea。您可以将视口视为窥视孔,通过使用滚动条向上/向下和向右/向左滚动来查看组件。视口是一个 Swing 组件。一个JViewport类的对象代表一个视窗组件。一个JViewport只是一个 Swing 组件的包装器,用来实现该组件的可滚动视图。JScrollPane为组件创建一个JViewport对象,并在内部使用。
  • 行和列标题:图中的行标题缩写为 RH。行/列标题是您可以在JScrollPane中使用的两个可选视窗。使用水平滚动条时,列标题会随之水平滚动。使用垂直滚动条时,行标题会随之垂直滚动。行/列标题的一个很好的用途是在视口中显示图片或绘图的水平和垂直标尺。通常,不使用行/列标题。
  • :一个JScrollPane中可以存在四个角。当两个组件垂直相交时,存在一个角。图中的四个角分别是 C1、C2、C3 和 C4。这些不是JScrollPane给角取的名字。为了便于讨论,我给它们取了一个名字。如果添加行标题和列标题,则存在角 C1。如果添加列标题并且垂直滚动条可见,则角 C2 存在。如果添加行标题并且水平滚动条可见,则角 C3 存在。如果水平滚动条和垂直滚动条都可见,则存在 C4 角。您可以添加任何 Swing 组件作为角组件。唯一的限制是不能在多个角上添加相同的组件。请注意,添加角组件并不能保证它是可见的。仅当拐角根据所讨论的规则存在时,拐角组件才会在拐角中可见。例如,如果您为 C4 角添加了一个角组件,那么只有当水平和垂直滚动条都可见时,它才可见。如果滚动条中的一个或两个都不可见,则角 C4 不存在,并且为该角添加的组件将不可见。

当组件尺寸大于JScrollPane尺寸时,需要一个方向(水平或垂直)的滚动条来查看视窗中的组件。一个JScrollPane让你为垂直和水平滚动条设置滚动条策略。滚动条策略是控制滚动条何时出现的规则。您可以设置以下三种滚动条策略之一:

  • :这意味着JScrollPane应该在需要的时候显示滚动条。当视口中某个方向(水平或垂直)的组件大于其显示区域时,需要滚动条。由JScrollPane决定何时需要滚动条,如果需要,它将使滚动条可见。否则,它会使滚动条不可见。
  • :这意味着JScrollPane应该总是显示滚动条。
  • :这意味着JScrollPane不应该显示滚动条。

滚动条策略由ScrollPaneConstants界面中的六个常量定义。三个常量用于垂直滚动条,三个用于水平滚动条。JScrollPane类实现了ScrollPaneConstants接口。所以您也可以使用JScrollPane类来访问这些常量。定义滚动条策略的常量是XXX_SCROLLBAR_AS_NEEDEDXXX_SCROLLBAR_ALWAYSXXX_SCROLLBAR_NEVER,其中您需要用VERTICALHORIZONTAL替换XXX,这取决于您所指的滚动条策略。垂直滚动条和水平滚动条的滚动条策略的默认值都是“按需显示”。下面的代码片段演示了如何用不同的选项创建一个JScrollPane:

// Create a JScrollPane with no component as its viewport and

// with default scrollbars policy as "As Needed"

JScrollPane sp1 = new JScrollPane();

// Create a JScrollPane with a JTextArea as its viewport and

// with default scrollbars policy as "As Needed"

JTextArea description = new JTextArea(10, 60);

JScrollPane sp2 = new JScrollPane(description);

// Create a JScrollPane with a JTextArea as its viewport and

// both scrollbars policy set to "show always"

JTextArea comments = new JTextArea(10, 60);

JScrollPane sp3 = new JScrollPane(comments,

JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,                                  JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

如前所述,当您将组件添加到JScrollPane时,您将JScrollPane添加到容器,而不是组件。一个JScrollPane的视口保持对你添加到JScrollPane的组件的引用。通过查询其视窗,您可以在JScrollPane中获得组件的引用,如下所示:

// Get the reference to the viewport of the JScrollPane sp3

JViewport vp = sp3.getViewport();

// Get the reference to the comments JTextArea added

// to the JScrollPane, sp3, using its viewport reference

JTextArea comments1 = (JTextArea)vp.getView();

如果您创建一个JScrollPane而没有为其视口指定组件,您可以稍后使用其setViewportView()方法将组件添加到其视口,如下所示:

// Set a JTextPane as the viewport component for sp3

sp3.setViewportView(new JTextPane());

进程条

一个JProgressBar用于显示任务的进度。它有一个方向,可以是水平的也可以是垂直的。它有三个相关的值:当前值、最小值和最大值。您可以创建一个进度条,如下所示:

// Create a horizontal progress bar with current, minimum, and maximum values

// set to 0, 0, and 100, respectively.

JProgressBar hpBar1 = new JProgressBar();

// Create a horizontal progress bar with current, minimum, and maximum values

// set to 20, 20, and 200, respectively.

JProgressBar hpbar2 = new JProgressBar(SwingConstants.HORIZONTAL, 20, 200);

// Create a vertical progress bar with current, minimum, and maximum values

// set to 5, 5 and 50, respectively.

JProgressBar vpBar1 = new JProgressBar(SwingConstants.VERTICAL, 5, 50);

随着任务的进展,您需要使用进度条的setValue(int value)方法来设置进度条的当前值,以指示进度。组件将自动更新以反映新值。根据应用的外观和感觉,进度会有不同的反映。有时实心条用于显示进度,有时实心矩形用于显示进度。您可以使用getValue()方法来获取当前值。

您还可以使用setStringPainted()方法显示一个描述进度条当前值的字符串。向该方法传递true会显示字符串值,传递false不会显示字符串值。要绘制的字符串通过调用setString(String s)方法来指定。

有时任务进度的当前值是未知的或不确定的。在这种情况下,您不能设置进度条的当前值。相反,您可以向用户表明任务正在执行。您可以使用其setIndeterminate()方法在不确定模式下设置进度条。向该方法传递true会将进度条置于不确定的模式,传递false会将进度条置于确定的模式。一个JProgressBar组件显示一个动画来指示它的不确定状态。

图 2-17 显示了一个带有两个JProgressBarJFrame,水平JProgressBar处于确定模式,它显示一个字符串来描述进度。垂直JProgressBar已被置于不确定模式;请注意中间显示为动画的实心矩形条。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-17。

JProgressBars in action

杰利德

一个JSlider可以让你通过沿轨道滑动旋钮从两个整数之间的一组值中选择一个值。它有四个重要的属性:方向、最小值、最大值和当前值。方向决定了它是水平显示还是垂直显示。您可以使用SwingConstants.VERTICALSwingConstants.HORIZONTAL作为其方向的有效值。以下代码片段创建了一个水平JSlider,最小值为 0,最大值为 10,当前值设置为 5:

JSlider points = new JSlider(0, 10, 5);

您可以使用getValue()方法获得JSlider的当前值。通常,用户通过左右滑动水平JSlider旋钮和上下滑动垂直JSlider旋钮来设置JSlider的当前值。您也可以通过使用它的setValue(int value)方法以编程方式设置它的值。

您可以在JSlider上显示次要和主要刻度。您需要设置这些刻度需要显示的间隔,并调用其方法来启用刻度画,如下所示:

points.setMinorTickSpacing(1);

points.setMajorTickSpacing(2);

points.setPaintTicks(true);

您也可以在JSlider中显示显示轨道值的标签。您可以显示标准标签或自定义标签。标准标签将沿着轨迹显示整数值。您可以调用它的setPaintLabels(true)方法来显示主要刻度间距处的整数值。图 2-18 显示了一个带有刻度和标准标签的JSlider

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-18。

A JSlider component with minimum = 0, maximum = 10, current value = 5, minor tick spacing = 1, major tick spacing = 2, tick painting enabled, and showing standard labels

JSlider还允许您设置自定义标签。使用JLabel组件显示JSlider上的标签。您需要创建一个带有value-label对的Hashtable,并使用它的setLabelTable()方法来设置标签。一个value-label副由一个Integer-JLabel副组成。下面的代码片段为值0设置标签Poor,为值5设置标签Average,为值10设置标签Excellent。设置标签表不会显示标签。您必须调用setPaintLabels(true)方法来显示它们。图 2-19 显示了由以下代码片段生成的带有自定义标签的JSlider:

// Create the value-label pairs in a Hashtable

Hashtable labelTable = new Hashtable();

labelTable.put(new Integer(0), new JLabel("Poor"));

labelTable.put(new Integer(5), new JLabel("Average"));

labelTable.put(new Integer(10), new JLabel("Excellent"));

// Set the labels for the JSlider and make them visible

points.setLabelTable(labelTable);

points.setPaintLabels(true);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-19。

A JSlider with custom labels

JSeparator

当您想要在两个组件或两组组件之间添加分隔符时,JSeparator是一个方便的组件。通常,菜单中使用一个JSeparator来分隔相关菜单项的组。您可以通过指定方向来创建一个水平或垂直的JSeparator。您可以在任何需要使用 Swing 组件的地方使用它。

// Create a horizontal separator

JSeparator hs = new JSeparator(); // By default, the type is horizontal

// Create a vertical separator

JSeparator vs = new JSeparator(SwingConstants.VERTICAL);

一个JSeparator将自己扩展以填充布局管理器提供的尺寸。您可以使用setOrientation()getOrientation()方法来设置和获取JSeparator的方向。

菜单

菜单组件用于以紧凑的形式向用户提供动作列表。您还可以通过使用一组JButton来提供动作列表,其中每个JButton代表一个动作。使用一个菜单或一组JButton来呈现一个动作列表是一个偏好问题。然而,使用菜单有一个明显的优势;与一组JButton相比,它占用的屏幕空间要少得多。通过将一组选项折叠(或嵌套)在另一个选项下,菜单占用的空间更少。例如,如果您使用的是文件编辑器,那么NewOpenSavePrint等选项会嵌套在顶层File菜单选项下。用户需要点击File菜单来查看其下可用的选项列表。典型地,在一组JButton的情况下,所有的JButton对用户来说一直是可见的,并且用户很容易知道哪些动作是可用的。因此,当您决定使用菜单或JButton s 时,需要在空间和可用性之间进行权衡。

还有一种叫做 a 的菜单,它根本不占用屏幕上的任何空间。通常,它会在用户单击鼠标右键时显示。一旦用户做出选择或在显示的弹出菜单区域之外单击鼠标,它就会消失。这是一个超级紧凑的菜单组件。然而,这使得用户很难知道有任何选项可用。有时,屏幕上会显示一条文本消息,说明用户需要右键单击来查看可用选项的列表。JPopupMenu类的一个对象表示 Swing 中的一个弹出菜单。现在让我们看看菜单的作用。

创建菜单并将其添加到JFrame是一个多步骤的过程。以下步骤详细描述了该过程。

创建该类的一个对象,并使用其setJMenuBar()方法将其添加到一个JFrame中。一个JMenuBar是一个空容器,它将保存一个菜单选项列表,一个JMenuBar中的每个选项代表一个选项列表。

// Create a JMenuBar and set it to a JFrame

JMenuBar menuBar = new JMenuBar();

myFrame.setJMenuBar(menuBar);

此时,您有一个空的JMenuBar与一个JFrame相关联。现在,您需要向JMenuBar添加选项列表,也称为顶级菜单选项。类别的物件代表选项清单。一个JMenu也是一个空容器,可以保存代表选项的菜单项。您将需要添加一个JMenu菜单选项。JMenu并不总是显示添加到其中的选项。相反,它会在用户选择JMenu时显示它们。当你使用菜单时,这是你得到紧凑的地方。当您选择一个JMenu时,它会弹出一个窗口,显示其中包含的选项。一旦您从弹出窗口中选择一个选项或点击JMenu外的某处,弹出窗口就会消失。

// Create two JMenu (or two top-level menu options):

// File and Help, and add them to the JMenuBar

JMenu fileMenu = new JMenu("File");

JMenu helpMenu = new JMenu("Help");

menuBar.add(fileMenu);

menuBar.add(helpMenu);

此时,你的JFrame会在顶部区域显示一个菜单栏,有两个选项叫做FileHelp,如图 2-20 所示。如果您选择或点击FileHelp,此时不会有任何反应。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-20。

A JMenuBar With Two JMenu Options

让我们给你的JMenu添加一些选项。您想在File下显示三个菜单选项,它们是NewOpenExit。您想要在OpenExit选项之间添加一个分隔符(一条水平线作为分隔符)。该类的一个对象代表了一个JMenu中的选项。

// Create menu items

JMenuItem newMenuItem = new JMenuItem("New");

JMenuItem openMenuItem = new JMenuItem("Open");

JMenuItem exitMenuItem = new JMenuItem("Exit");

// Add menu items and a separator to the menu

fileMenu.add(newMenuItem);

fileMenu.add(openMenuItem);

fileMenu.addSeparator();

fileMenu.add(exitMenuItem);

此时,您已经向File菜单添加了三个JMenuItem。当您点击File菜单时,会显示如图 2-21 所示的选项。您可以使用键盘上的向下/向上箭头键滚动浏览File菜单下的选项,或者使用鼠标选择其中一个选项。当您选择File菜单下的任何一个选项时,什么都不会发生,因为您没有给它们添加任何动作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-21。

A File JMenu with three options

你可能想在一个菜单项下有两个子选项,比如在New选项下。也就是说,用户可以创建两个不同的东西,PolicyClaim,并且您希望这两个选项在New选项下可用。您没有尝试在选项中嵌套选项。File菜单是JMenu类的一个实例,它代表一个选项列表,并且您想要添加一个New菜单,它也应该显示一个选项列表。你可以很容易地做到这一点。您唯一需要理解的是,JMenu代表一个选项列表,而JMenuItem只代表一个选项。您可以在JMenu上添加一个JMenuItemJMenu。为此,您需要对前面显示的代码片段做一点修改。现在New菜单将是JMenu类的一个实例,而不是JMenuItem类。您将向New菜单添加两个 JMenuItems。下面的代码片段将完成这项工作:

// New is a JMenu – a list of options

JMenu newMenu = new JMenu("New");

JMenuItem policyMenuItem = new JMenuItem("Policy");

JMenuItem claimMenuItem = new JMenuItem("Claim");

newMenu.add(policyMenuItem);

newMenu.add(claimMenuItem);

JMenuItem openMenuItem = new JMenuItem("Open");

JMenuItem exitMenuItem = new JMenuItem("Exit");

fileMenu.add(newMenu);

fileMenu.add(openMenuItem);

fileMenu.addSeparator();

fileMenu.add(exitMenuItem);

现在菜单显示如图 2-22 所示。当您选择File菜单时,New菜单旁边会显示一个箭头,表示它有子菜单。当您选择New菜单时,会显示两个标有PolicyClaim的子菜单。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-22。

Nesting menus

一个菜单可以嵌套的层数没有限制。然而,两层以上的嵌套被认为不是好的 GUI 实践,因为用户将不得不向下钻取几层才能得到可用的选项。

让菜单工作的最后一步是向菜单项添加动作。您可以向JMenuItem添加动作监听器。当用户选择JMenuItem时,相关的动作监听器会得到通知。下面的代码片段向将退出应用的Exit菜单项添加了一个动作监听器:

// Add an action listener to the Exit menu item

exitMenuItem.addActionListener(e -> System.exit(0));

现在,您已经向 Exit 菜单项添加了一个操作。如果选择它,应用将退出。类似地,您可以向其他菜单项添加操作侦听器,以便在它们被选中时执行操作。

您可以使用setEnabled()方法启用/禁用菜单。尽管可以使菜单可见/不可见,但这样做并不是好的做法。这使得用户很难学习应用。如果您始终保持所有菜单选项可用(处于启用或禁用状态),用户将能够通过了解菜单选项的位置来更快地使用应用。如果你使菜单选项可见/不可见,菜单选项的位置会不断变化,用户每次想使用它们时都必须更加注意菜单选项的位置。

您也可以为菜单选项指定快捷方式。您可以使用setMnemonic()方法通过指定快捷键来添加菜单项的快捷方式。您可以通过按下Alt键和快捷键的组合来调用该菜单项所代表的动作。请注意,菜单项必须可见,其助记键才能起作用。例如,如果您为New菜单选项设置了助记符(N键),您必须选择File菜单,使New菜单选项可见,并按Alt + N调用由New菜单项表示的动作。

如果您想调用菜单项的相关动作,而不管它是否可见,您需要使用setAccelerator()方法来设置它的快捷键。以下代码片段将E键设置为助记键,将Ctrl + E设置为Exit菜单选项的快捷键:

// Set E as mnemonic for Exit menu and Ctrl + E as its accelerator

exitMenuItem.setMnemonic(KeyEvent.VK_E);

KeyStroke cntrlEKey = KeyStroke.getKeyStroke(KeyEvent.VK_E, ActionEvent.CTRL_MASK);

exitMenuItem.setAccelerator(cntrlEKey);

现在你可以通过两种方式调用Exit菜单选项:当Alt + E组合键可见时你可以按下它,或者你可以随时按下Ctrl + E组合键。

您可以使用弹出菜单,该菜单会根据需要显示。弹出菜单的创建类似于JMenu。您需要创建一个JPopupMenu类的实例,它代表一个空的弹出菜单容器,然后向其中添加JMenuItem的实例。您也可以在弹出菜单中包含嵌套菜单,就像在JMenu中一样。

// Create a popup menu

JPopupMenu popupMenu = new JPopupMenu();

// Create three menu items for our popup menu

JMenuItem popup1 = new JMenuItem("Poupup1");

JMenuItem popup2 = new JMenuItem("Poupup2");

JMenuItem popup3 = new JMenuItem("Poupup3");

// Add menu items to the popup menu

popupMenu.add(popup1);

popupMenu.add(popup2);

popupMenu.add(popup3);

由于弹出菜单没有固定的位置,并且是按需显示的,所以您需要知道在哪里以及何时显示它。您需要使用它的show()方法在某个位置显示它。show()方法有三个参数:其空间将用于显示弹出菜单的 invoker 组件,以及它将显示的 invoker 组件上的 x 和 y 坐标。

// Display the popup menu

popupMenu.show(myComponent, xPos, yPos);

通常,当用户单击鼠标右键时,会显示一个弹出菜单。不同的外观和感觉选项使用不同的按键事件来显示弹出菜单。例如,一个外观方案在释放鼠标右键时显示它,而另一个外观方案在按下鼠标右键时显示它。Swing 通过在MouseEvent类中提供一个isPopupTrigger()方法,让您可以轻松地显示弹出菜单。在鼠标按下或释放事件中,您需要调用此方法。如果该方法返回true,显示弹出菜单。以下代码片段将鼠标侦听器与组件相关联,并显示弹出菜单:

// Create a mouse listener

MouseListener ml = new MouseAdapter() {

@Override

public void mousePressed(MouseEvent e) {

if (e.isPopupTrigger()) {

popupMenu.show(e.getComponent(), e.getX(), e.getY());

}

}

@Override

public void mouseReleased(MouseEvent e) {

if (e.isPopupTrigger()) {

popupMenu.show(e.getComponent(), e.getX(), e.getY());

}

}

};

// Add a mouse listener to myComponent

myComponent.addMouseListener(ml);

每当用户右击myComponent,就会出现一个弹出菜单。注意,您需要在mousePressed()mouseReleased()方法中添加相同的代码。它由外观和感觉决定哪个事件应该显示弹出菜单。

清单 2-7 包含了一个显示如何使用菜单的完整程序。节目很长。它执行创建和添加菜单项以及向菜单项添加动作监听器的重复性工作。

清单 2-7。使用菜单和弹出菜单

// JMenuFrame.java

package com.jdojo.swing;

import javax.swing.JFrame;

import java.awt.Container;

import javax.swing.JMenuBar;

import javax.swing.JMenu;

import javax.swing.JMenuItem;

import javax.swing.JLabel;

import java.awt.event.ActionListener;

import javax.swing.JTextArea;

import java.awt.BorderLayout;

import java.awt.event.KeyEvent;

import javax.swing.KeyStroke;

import javax.swing.JPopupMenu;

import java.awt.event.MouseAdapter;

import java.awt.event.MouseEvent;

import java.awt.event.MouseListener;

import javax.swing.JScrollPane;

public class JMenuFrame extends JFrame {

JLabel msgLabel = new JLabel("Right click to see popup menu");

JTextArea msgText = new JTextArea(10, 60);

JPopupMenu popupMenu = new JPopupMenu();

public JMenuFrame(String title) {

super(title);

initFrame();

}

// Initialize the JFrame and add components to it

private void initFrame() {

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Container contentPane = this.getContentPane();

// Add the message label and text area

contentPane.add(new JScrollPane(msgText), BorderLayout.CENTER);

contentPane.add(msgLabel, BorderLayout.SOUTH);

// Set the menu bar for the frame

JMenuBar menuBar = getCustomMenuBar();

this.setJMenuBar(menuBar);

// Create a popup menu and add a mouse listener to show it

createPopupMenu();

}

private JMenuBar getCustomMenuBar() {

JMenuBar menuBar = new JMenuBar();

// Get the File and Help menus

JMenu fileMenu = getFileMenu();

JMenu helpMenu = getHelpMenu();

// Add the File and Help menus to the menu bar

menuBar.add(fileMenu);

menuBar.add(helpMenu);

return menuBar;

}

private JMenu getFileMenu() {

JMenu fileMenu = new JMenu("File");

// Set Alt-F as mnemonic for the File menu

fileMenu.setMnemonic(KeyEvent.VK_F);

// Prepare a New Menu item. It will have sub menus

JMenu newMenu = getNewMenu();

fileMenu.add(newMenu);

JMenuItem openMenuItem = new JMenuItem("Open", KeyEvent.VK_O);

JMenuItem exitMenuItem = new JMenuItem("Exit", KeyEvent.VK_E);

fileMenu.add(openMenuItem);

// You can add a JSeparator or just call the convenience method

// addSeparator() on fileMenu. You can replace the following statement

// with fileMenu.add(new JSeparator());

fileMenu.addSeparator();

fileMenu.add(exitMenuItem);

// Add an ActionListener to the Exit menu item

exitMenuItem.addActionListener(e -> System.exit(0));

return fileMenu;

}

private JMenu getNewMenu() {

// New menu will have two sub menus - Policy and Claim

JMenu newMenu = new JMenu("New");

// Add submenus to New menu

JMenuItem policyMenuItem = new JMenuItem("Policy", KeyEvent.VK_P);

JMenuItem claimMenuItem = new JMenuItem("Claim", KeyEvent.VK_C);

newMenu.add(policyMenuItem);

newMenu.add(claimMenuItem);

return newMenu;

}

private JMenu getHelpMenu() {

JMenu helpMenu = new JMenu("Help");

helpMenu.setMnemonic(KeyEvent.VK_H);

JMenuItem indexMenuItem = new JMenuItem("Index", KeyEvent.VK_I);

JMenuItem aboutMenuItem = new JMenuItem("About", KeyEvent.VK_A);

// Set F1 as the accelerator key for the Index menu item

KeyStroke f1Key = KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0);

indexMenuItem.setAccelerator(f1Key);

helpMenu.add(indexMenuItem);

helpMenu.addSeparator();

helpMenu.add(aboutMenuItem);

// Add an action listener to the index menu item

indexMenuItem.addActionListener(e ->

msgText.append("You have selected Help >>Index menu item.\n"));

return helpMenu;

}

private void createPopupMenu() {

// Create a popup menu and add a mouse listener to the frame,

// so a popup menu is displayed when the user clicks a right mouse button

JMenuItem popup1 = new JMenuItem("Popup1");

JMenuItem popup2 = new JMenuItem("Popup2");

JMenuItem popup3 = new JMenuItem("Popup3");

// Create an action listener

ActionListener al =  e -> {

JMenuItem menuItem = (JMenuItem)e.getSource();

String menuText = menuItem.getText();

String msg = "You clicked " + menuText + " menu item.\n";

msgText.append(msg);

};

// Add the same action listener to all popup menu items

popup1.addActionListener(al);

popup2.addActionListener(al);

popup3.addActionListener(al);

// Add menu items to popup menu

popupMenu.add(popup1);

popupMenu.add(popup2);

popupMenu.add(popup3);

// Create a mouse listener to show a popup menu

MouseListener ml = new MouseAdapter() {

@Override

public void mousePressed(MouseEvent e) {

displayPopupMenu(e);

}

@Override

public void mouseReleased(MouseEvent e) {

displayPopupMenu(e);

}

};

// Add a mouse listener to the msg text and label

msgText.addMouseListener(ml);

msgLabel.addMouseListener(ml);

}

private void displayPopupMenu(MouseEvent e) {

// Make sure this mouse event is supposed to show the popup menu.

// Different platforms show the popup menu in different mouse events

if (e.isPopupTrigger()) {

this.popupMenu.show(e.getComponent(), e.getX(), e.getY());

}

}

// Display the CustomFrame

public static void main(String[] args) {

JMenuFrame frame = new JMenuFrame("JMenu and JPopupMenu Test");

frame.pack();

frame.setVisible(true);

}

}

您也可以使用JRadioButtonMenuItemJCheckBoxMenuItem作为菜单中的菜单项。顾名思义,它们显示为单选按钮和复选框,其工作原理与单选按钮和复选框相同。您可以向JMenu添加任何 swing 组件。要使用单选按钮类型的菜单项,您需要将多个JRadioButtonMenuItem组件分组到一个按钮组中,以便它们代表唯一的选择。要处理单选按钮选择的更改,您可以在JRadioButtonMenuItem中添加一个ActionListenerItemListener。要处理JCheckBoxMenuItem中的状态变化,您需要使用一个ItemListener

Tip

我将最终揭示 Swing 中菜单的秘密。Swing 中的菜单项是一个按钮。啊哈!你用按钮工作,称它们为菜单。是的,这是正确的。一个JMenuBar和一个JPopupMenu只是带有一个BoxLayout的容器。继续操作这些容器,设置它们的属性并向它们添加不同的 Swing 组件。一个JMenuItem是一个简单的按钮。一个JMenu是一个按钮,它有一个关联的容器,当你选择它时就会显示出来。

JToolBar

工具栏是一组按钮,在JFrame中为用户提供常用的操作。通常,您会提供一个工具栏和一个菜单。工具栏包含带有小图标的小按钮。通常,它只包含菜单中可用选项的子集。

JToolBar类的一个对象代表一个工具栏。它充当工具栏按钮的容器。它是一个比其他容器更智能的容器,比如一个JPanel。它可以在运行时移动。它可以是漂浮的。如果它是浮动的,它会显示一个手柄,您可以使用它来移动它。您也可以使用句柄在单独的窗口中弹出它。以下代码片段创建了一些工具栏组件:

// Create a horizontal JToolBar

JToolBar toolBar = new JToolBar();

// Create a horizontal JToolBar with a title. The title is

// displayed as a window title, when it floats in a separate window.

JToolBar toolBarWithTitle = new JToolBar("My ToolBar Title");

// Create a Vertical toolbar

JToolBar vToolBar = new JToolBar(JToolBar.VERTICAL);

让我们给工具栏添加一些按钮。工具栏中的按钮需要比普通按钮小。通过将边距设置为零,可以缩小JButton的尺寸。您还应该为每个工具栏按钮添加一个工具提示,为用户提供有关其用法的快速提示。

// Create a button for the toolbar

JButton newButton = new JButton("New");

// Set the margins to 0 to make the button smaller

newButton.setMargin(new Insets(0, 0, 0, 0));

// Set a tooltip for the button

newButton.setToolTipText("Add a new policy");

// Add the New button to the toolbar

toolBar.add(newButton);

通常,在工具栏按钮中只显示小图标。您可以使用JButton的另一个构造函数,它只接受一个Icon对象作为参数。最后,您需要向按钮添加动作监听器,就像您已经向其他 JButtons 添加的那样。当用户单击工具栏中的按钮时,会通知操作监听器,并执行指定的操作。

您可以使用它的setFloatable(boolean floatable)方法设置工具栏浮动/不浮动。默认情况下,工具栏是可浮动的。它的setRollover(boolean rollOver)方法可以让你指定是否只在鼠标停留在工具栏按钮上时才绘制它们的边框。

应该在BorderLayout的北、南、东或西区域添加一个工具栏,以便在不同的区域移动工具栏。清单 2-8 在一个JFrame中显示了一个JToolBar。图 2-23 显示了一个JFrame在其北部区域有一个工具栏。图 2-24 显示了工具栏浮动在独立窗口中的同一JFrame

清单 2-8。在 JFrame 中使用 JToolBar

// JToolBarFrame.java

package com.jdojo.swing;

import java.awt.Container;

import javax.swing.JFrame;

import javax.swing.JToolBar;

import javax.swing.JButton;

import java.awt.Insets;

import java.awt.BorderLayout;

import javax.swing.JTextArea;

import javax.swing.JScrollPane;

public class JToolBarFrame extends JFrame {

JToolBar toolBar = new JToolBar("My JToolBar");

JTextArea msgText = new JTextArea(3, 45);

public JToolBarFrame(String title) {

super(title);

initFrame();

}

// Initialize the JFrame and add components to it

private void initFrame() {

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Container contentPane = this.getContentPane();

prepareToolBar();

// Add the toolbar in the north and a JTextArea in the center

contentPane.add(toolBar, BorderLayout.NORTH);

contentPane.add(new JScrollPane(msgText), BorderLayout.CENTER);

msgText.append("Move the toolbar around using its" +

" handle at the left end");

}

private void prepareToolBar() {

Insets zeroInset = new Insets(0, 0, 0, 0);

JButton newButton = new JButton("New");

newButton.setMargin(zeroInset);

newButton.setToolTipText("Add a new policy");

JButton openButton = new JButton("Open");

openButton.setMargin(zeroInset);

openButton.setToolTipText("Open a policy");

JButton exitButton = new JButton("Exit");

exitButton.setMargin(zeroInset);

exitButton.setToolTipText("Exit the application");

// Add an action listener to the Exit toolbar button

exitButton.addActionListener(e -> System.exit(0));

toolBar.add(newButton);

toolBar.add(openButton);

toolBar.addSeparator();

toolBar.add(exitButton);

toolBar.setRollover(true);

}

// Display the frame

public static void main(String[] args) {

JToolBarFrame frame = new JToolBarFrame("JToolBar Test");

frame.pack();

frame.setVisible(true);

}

}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-24。

A JToolBar floating in a separate window

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-23。

A JToolBar with three JButtons placed in the north region of a JFrame

JToolBar 符合动作接口

三个组件:JButtonJMenuItemJToolBar中的一个项目有什么共同点?它们都代表一个动作。有时你给用户同样的选项,作为一个菜单项,作为一个工具栏项,作为一个JButton。如何禁用您使用三个组件提供的选项?难道您不认为您需要至少在三个地方分别禁用它们,因为它们是代表同一选项的三个不同的组件吗?你可能是对的。但是,在 Swing 中有一种更简单的方法来处理这种情况。每当你必须以不同的方式为一个动作提供选项时,你应该使用Action接口。您需要将选项的逻辑和属性包装在一个Action对象中,并使用该对象构建工具栏中的JButtonJMenuItem和项目。如果需要禁用选项,只需在Action对象上调用setEnabled(false)一次,所有选项都会被禁用。在这种情况下,使用一个Action对象会让你的编程生活更容易。让我们看看它的实际效果。让我们创建一个继承自AbstractAction类的ExitAction类。它的actionPerformed()方法简单地退出应用。您使用它的putValue()方法在它的构造函数中设置一些属性,如下所示:

public class ExitAction extends AbstractAction {

public ExitAction(String action) {

super(action);

// Set tooltip text for the toolbar

this.putValue(SHORT_DESCRIPTION, "Exit the application");

// Set a mnemonic key

this.putValue(MNEMONIC_KEY, KeyEvent.VK_E);

}

@Override

public void actionPerformed(ActionEvent e) {

System.exit(0);

}

}

如果你想添加一个Exit菜单项、一个JButton和一个工具栏按钮,你可以先创建一个ExitAction类的对象,然后用它来创建你所有的选项项,如下所示:

ExitAction exitAction = new ExitAction("Exit");

JButton exitButton = new JButton(ExitAction);

JMenuItem exitMenuItem = new JMenuItem(exitAction);

JButton exitToolBarButton = new JButton(exitAction);

exitToolBarButton.setMargin(new Insets(0,0,0,0));

现在你可以将exitButton添加到你的JFrame,将exitMenuItem添加到你的菜单,将exitToolBarButton添加到你的工具栏。它们的行为都一样,因为它们共享同一个exitAction对象。如果您想在所有三个地方禁用退出选项,只需调用一次exitAction.setEnabled(false)即可。

组建

Swing 允许您使用JTable组件以表格形式显示和编辑数据。一个JTable使用行和列显示数据。您可以设置列标题的标签。您还可以在运行时对表中的数据进行排序。使用JTable可以简单到写几行代码,也可以复杂到写几百行代码。一个JTable是一个复杂而强大的 Swing 组件,它本身就值得一章。本节解释了使用JTable的基本知识,并为您提供了一些关于其强大功能的提示。一个JTable使用了许多其他的类和接口,这些都在javax.swing.table包中。JTable类本身在javax.swing包中。

先说最简单的JTable例子。您可以通过使用它的无参数构造函数来创建一个JTable

JTable table = new JTable();

嗯,那很简单。但是,它的列、行和数据会怎么样呢?你得到的只是一张没有可视组件的空桌子。您将在一分钟内解决这些问题。

A JTable不存储数据。它只显示数据。它使用存储数据、列数和行数的模型。接口的一个实例代表了一个 ?? 的模型。DefaultTableModel类是TableModel接口的一个实现。当您使用JTable类的默认构造函数时,Java 会将DefaultTableModel类的一个实例设置为它的模型。如果要添加或删除列/行,必须使用其模型。你可以使用它的getModel()方法得到一个JTable的模型的引用。让我们在表格中添加两行和三列。

// Get the reference of the model of the table

DefaultTableModel tableModel = (DefaultTableModel)table.getModel();

// Set the number of rows to 2

tableModel.setRowCount(2);

// Set the number of columns to 3

tableModel.setColumnCount(3);

让我们为表格中的一个单元格设置值。您可以使用表格模型或表格的setValueAt(Object data, int row, int column)方法来设置其单元格中的值。您将设置“John Jacobs”作为第一行和第一列的值。请注意,第一行和第一列从 0 开始。

// Set the value at (0, 0) in the table's model

tableModel.setValueAt("John Jacobs", 0, 0);

// Set the value at (0, 0) in the table

// Works the same as setting the value using the table's model

table.setValueAt("John Jacobs", 0, 0);

如果您将表格添加到容器中,它将如图 2-25 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-25。

A JTable with two rows and three columns with default column header labels

确保将表格添加到一个JScrollPane中。请注意,您会得到两行三列。列标题的标签设置为 A、B 和 c。您可以双击任何单元格开始编辑单元格中的值。要获取单元格中包含的值,可以使用表格模型的getValueAt(int row, int column)方法或JTable。它返回一个Object。您还可以通过使用DefaultTableModel类的addColumn()addRow()方法向JTable添加更多的列或行。您可以使用 its model 类的removeRow(int row)方法从模型中删除一行,从而从。

您可以使用模型的setColumnIdentifiers()方法为列标题设置定制标签,如下所示:

// Store the column headers in an array

Object[] columnHeaderLabels = new Object[]{"Name", "DOB", "Gender"};

// Set the column headers for the table using its model

tableModel.setColumnIdentifiers(columnHeaderLabels);

使用自定义列标题,表格看起来如图 2-26 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-26。

A JTable with two rows, three columns, and custom column header labels

如果您希望列标题始终可见,您必须在JScrollPane中添加一个JTable。如果不将其添加到JScrollPane,当行数超过组件的可用高度时,列标题将不可见。您可以使用JTable的方法获取列标题组件并自己显示(例如,如果JTable在中心区域,则显示在BorderLayout的北部区域)。您可以通过单击某行来选择该行。默认情况下,JTable允许您选择多行。您可以使用JTablegetSelectedRow()方法获取第一个选定的行号,使用getselectedRows()方法获取所有选定行的行号。getSelectedRowCount()方法返回选中的行数。

你从最简单的JTable开始。然而,与所谓的最简单的JTable一起工作并不是一件容易的事情,但是现在你知道了与JTable一起工作的基本知识。

让我们通过使用另一个构造函数创建JTable来重复这个例子。JTable类有另一个构造函数,它接受行数和列数作为参数。您可以创建一个两行三列的JTable,如下所示:

// Create a JTable with 2 rows and 3 columns

JTable table = new JTable(2, 3);

如果要将第一行和第一列的值设置为“John Jacobs”,则不需要使用表的模型。你可以使用JTablesetValueAt()方法来做同样的事情。

table.setValueAt("John Jacobs", 0, 0);

这一个比上一个稍微容易一点。然而,您仍然可以将默认的列标题标签设置为 A、B 和 c。JTable的另外两个构造函数允许您一次性设置行数和列数以及数据。它们的区别仅在于参数类型:一个让您使用一个数组Object,另一个让您使用一个Vector对象。它们声明如下:

  • JTable(Object[][] rowData, Object[] columnNames)
  • JTable(Vector rowData, Vector columnNames)

如果使用二维数组Object来设置行数据,数组的第一维数决定了行数。如果使用一个VectorVector中的元素数量决定了表格中的行数。Vector中的每个元素都应该是一个包含一行数据的Vector对象。下面是如何使用二维数组Object构造一个JTable。图 2-27 显示了显示代码中所有数据集的表格。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-27。

A JTable with two rows, three columns, and data

// Prepare the column headers

Object[] columnNames = {"ID", "Name", "Gender" } ;

// Create a two-dimensioanl array to contain the table's data

Object[][] rowData = new Object[][] {

{new Integer(100), "John Jacobs", "Male" },

{new Integer(101), "Barbara Gentry", "Female"}

};

// Create a JTable with the data and the column headers

JTable table = new JTable(rowData, columnNames);

到目前为止,您的表的数据是硬编码的。JTable将所有数据视为String,表格中的所有单元格都是可编辑的。例如,您将 ID 列的值设置为整数,但它们仍然显示为左对齐的文本。数字在单元格中应该右对齐。如果你想定制一个JTable,你需要用你自己的模型来做桌子。回想一下,TableModel接口定义了一个JTable的模型。下面是该接口的声明:

public interface TableModel

public int getRowCount();

public int getColumnCount();

public String getColumnName(int columnIndex);

public Class<?> getColumnClass(int columnIndex);

public boolean isCellEditable(int rowIndex, int columnIndex);

public Object getValueAt(int rowIndex, int columnIndex);

public void setValueAt(Object aValue, int rowIndex, int columnIndex);

public void addTableModelListener(TableModelListener l);

public void removeTableModelListener(TableModelListener l);

}

该类实现了TableModel接口。它为TableModel接口的方法提供了一个空的实现。它没有提到数据应该如何存储。如果你想实现你自己的表格模型,你需要从AbstractTableModel类继承你的类。如果您在自定义表模型类中至少实现了以下三个方法,您将获得一个只读表模型:

  • public int getRowCount();
  • public int getColumnCount();
  • public Object getValueAt(int row, int column);

该类继承自AbstractTableModel类。它为接口中的所有方法提供了默认实现。它使用Vectors的一个Vector来存储表的数据。

如果您使用自己的表格模型,您可以更好地控制JTable的工作。清单 2-9 实现了一个简单的表格模型,使用数组的数组来存储数据。

清单 2-9。实现简单的表格模型

// SimpleTableModel.java

package com.jdojo.swing;

import javax.swing.table.AbstractTableModel;

public class SimpleTableModel extends AbstractTableModel {

private Object[][] data = {};

private String[] columnNames = {"ID", "Name", "Gender"};

private Class[] columnClass = {Integer.class, String.class, String.class};

private Object[][] rowData = new Object[][]{

{new Integer(100), "John Jacobs", "Male"},

{new Integer(101), "Barbara Gentry", "Female"}

};

public SimpleTableModel() {

}

@Override

public int getRowCount() {

return rowData.length;

}

@Override

public int getColumnCount() {

return columnNames.length;

}

@Override

public String getColumnName(int columnIndex) {

return columnNames[columnIndex];

}

@Override

public Class getColumnClass(int columnIndex) {

return columnClass[columnIndex];

}

@Override

public boolean isCellEditable(int rowIndex, int columnIndex) {

boolean isEditable = true;

if (columnIndex == 0) {

isEditable = false; // Make the ID column non-editable

}

return isEditable;

}

@Override

public Object getValueAt(int rowIndex, int columnIndex) {

return rowData[rowIndex][columnIndex];

}

@Override

public void setValueAt(Object aValue, int rowIndex, int columnIndex) {

rowData[rowIndex][columnIndex] = aValue;

}

}

在方法中,指定列数据的类;JTable将使用这个信息适当地显示列的数据。例如,它会将列中的数字显示为右对齐。如果您为一个列指定了类型Boolean,那么JTable将在该列的每个单元格中使用一个JCheckBox来显示Boolean值。注意,您已经通过从为 0 的columnIndexisEditable()方法返回false使 ID 列不可编辑。在本例中,您再次对表的数据进行了硬编码。但是,您可以从数据库、数据文件、网络或任何其他数据源读取数据。下面的代码片段使用定制模型来创建一个JTable:

// Use the SimpleTableModel as the model for the table

JTable table = new JTable(new SimpleTableModel());

请注意,您的表模型不允许添加和删除行/列。如果您想要这些扩展功能,您最好从DefaultTableModel类继承 model 类并定制您想要改变的行为。

您可以通过调用其方法setAutoCreateRowSorter(true)将数据排序功能添加到您的JTable中。通过单击列标题,可以对列中的数据进行排序。在您调用这个方法之后,JTable将显示一个向上/向下箭头作为列标题的一部分,以指示一个列是按升序还是降序排序的。您还可以使用一个行过滤器,根据某些标准隐藏JTable中的行,如下所示:

// Set a row sorter for the table

TableRowSorter sorter = new TableRowSorter(table.getModel());

table.setRowSorter(sorter);

// Set an ID filter for the table

RowFilter<SimpleTableModel, Integer> IDFilter = new RowFilter<SimpleTableModel, Integer> () {

@Override

public boolean include(Entry<? extends SimpleTableModel,                                    ? extends Integer> entry) {

SimpleTableModel model = entry.getModel();

int rowIndex = entry.getIdentifier().intValue();

Integer ID = (Integer) model.getValueAt(rowIndex, 0);

if (ID.intValue() <= 100) {

return false; // Do not show rows with an ID <= 100

}

return true;

}

};

sorter.setRowFilter(IDFilter);

上面的代码片段为一个名为tableJTable设置了一个过滤器,这样就不会显示 id 小于或等于 100 的行。RowFilter号是一艘abstract级;您必须覆盖它的include()方法来指定您的过滤标准。它还有几个返回不同种类的RowFilter对象的静态方法,您可以将这些方法直接用于RowSorter对象。以下是创建行过滤器的一些示例:

// Create a filter that will show only rows that starts

// with "John" in the second column (column index = 1)

RowFilter nameFilter = RowFilter.regexFilter("^John*", 1);

// Create a filter that will show only rows that has a

// "Female" value in its third column (column index = 2)

RowFilter genderFilter = RowFilter.regexFilter("^Female$", 2);

// Create a filter that will show only rows that has 3rd,

// 5th and 7th columns values starting with "A"

RowFilter anyFilter1 = RowFilter.regexFilter("^A*", 3, 5, 7);

// Create a filter that will show only rows that has any

// column whose value starts with "A"

RowFilter anyFilter2 = RowFilter.regexFilter("^A*");

您可以将一个TableModelListener添加到一个TableModel中,以监听对表格模型所做的任何更改。

Tip

由于篇幅限制,A JTable有许多特性无法在本节中描述。它还允许您设置一个自定义单元格,呈现为在单元格中显示一个值。例如,您可以在单元格中显示单选按钮,供用户选择,而不是让他们编辑纯文本值。

树形结构

A JTree用于以树状结构显示分层数据,如图 2-28 所示。你可以把一个JTree想象成颠倒显示一棵真实的树。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-28。

A JTree showing departments and a list of employees in the departments

一个JTree中的每一项被称为一个节点。在图中,部门、销售、约翰等。是节点。节点被进一步分类为分支节点或叶节点。如果一个节点下可以有其他节点,称为其子节点,则称为分支节点。如果一个节点没有子节点,它被称为叶节点。部门、销售和信息技术是分支节点的示例,而 John、Elaine 和 Aarav 是叶节点的示例。现实世界的树中总有一个特殊的分支叫做根。类似地,JTree总是有一个特殊的分支节点,称为根节点。您的JTree有一个名为部门的根节点。在JTree中,您可以通过使用它的setRootVisible(boolean visibility)方法使根节点可见或不可见。

分支节点被称为其子节点的父节点。注意,子节点也可以是分支节点。“销售”、“信息技术”和“广告”是“部门”节点的子节点。销售节点有两个子节点:John 和 Elaine。John 和 Elaine 都有相同的父节点,即销售节点。

同一级别的节点称为兄弟节点。换句话说,具有相同父节点的节点称为兄弟节点。销售、信息技术和广告是兄弟姐妹;约翰和伊莱恩是兄弟姐妹;Tejas 和 Aarav 是兄弟姐妹。两个术语,祖先和后代,在节点的上下文中经常使用。作为父节点的父节点的父节点等等的节点都称为祖先节点。也就是说,从祖父开始的节点都是祖先节点。从孙开始向下的节点都称为后代。例如,Departments 节点是 Elaine 节点的祖先,而 Elaine 节点是 Departments 节点的后代。

你已经学了足够多与 a JTree相关的术语。是时候看看一个JTree在行动了。与JTree相关的类是在javax.swingjavax.swing.tree包中。一个JTree由节点组成。TreeNode接口的一个实例代表一个节点。TreeNode接口声明了提供节点基本信息的方法,比如节点类型(分支或叶子)、父节点、子节点等。

是扩展接口的接口。它声明了额外的方法,允许您通过插入/移除子节点或更改节点对象来更改节点。DefaultMutableTreeNode类是MutableTreeNode接口的一个实现。

在开始创建节点之前,您需要理解节点是 Java 对象的可视化表示(通常是一行文本)。换句话说,节点包装一个对象,通常显示该对象的单行文本表示。节点表示的对象称为该节点的用户对象。因此,在构建节点之前,必须有一个节点将表示的对象。不用担心创建新类来构建节点。您可以只使用一个String来构建您的节点。下面的代码片段创建了一些可以在JTree中使用的节点:

// Create a Departments node

DefaultMutableTreeNode root = new DefaultMutableTreeNode("Departments");

// Create a Sales node

DefaultMutableTreeNode sales = new DefaultMutableTreeNode("Sales");

// Create a John node

DefaultMutableTreeNode john = new DefaultMutableTreeNode("John");

// Create a customer node, assuming you have a Customer class.

// In this case, the node will wrap a Customer object

Customer cust101 = new Customer(101, "Joe");

DefaultMutableTreeNode c101Node = new DefaultMutableTreeNode(cust101);

// If you want to get the user object that a node wraps, you would

// use the getUserObject() method of the DefaultMutableTreeNode class

Customer c101Back = (Customer)c101Node.getUserObject();

一旦有了一个节点,使用add()insert()方法添加子节点就很容易了。add()方法将节点追加到末尾;insert()方法允许您指定新节点的位置。例如,添加一个Sales节点作为您编写的Departments根节点的子节点

root.add(sales);

要将John作为子节点添加到sales,您需要编写

sales.add(john);

一旦准备好了节点,就很容易将它们放入JTree中。您需要通过指定根节点来创建一个JTree

JTree tree = new JTree(root);

JTree类的其他构造函数允许你以不同的方式创建一个JTree。除非你正在学习JTree,否则无参数构造函数不是很有用。它创建了一个添加了一些节点的JTree,如果您想使用JTree进行实验,这可以省去添加节点的麻烦。您还可以通过将一个数组Object或一个ObjectVector传递给它的构造函数来创建一个JTree,作为JTree根的子节点。在添加传入的对象作为其子节点之前,一个根将被添加到新的JTree中。例如,

// Create a JTree. It will create a default root node called Root

// and it will add two, "One" and "Two", child nodes for Root.

// The Root node is not displayed by default.

JTree tree = new JTree(new Object[]{"One", "Two"});

一旦创建了JTree组件,就该在 Swing 容器中显示它了。通常,你给一个JScrollPane添加一个JTree,这样它就有了滚动能力。

myContainer.add(new JScrollPane(tree));

如何访问或浏览JTree节点?有两种方法可以访问JTree中的节点:使用行号和使用树路径。

一个JTree由节点组成。一个JTree如何显示节点?回想一下,节点是TreeNode类的一个实例,它包装任何类型的对象。因此,你可以说节点是对象的包装器。默认情况下,JTree调用节点对象的toString()方法来获取要显示的节点文本。如果您的节点包装了一个对象,该对象的方法没有返回要在JTree节点中显示的有意义的字符串,您可以通过创建一个自定义的JTree并覆盖其convertValueToText()方法来为该节点提供一个自定义字符串。在示例中,您已经将一个String对象包装在一个节点中,并且一个String对象的toString()方法返回字符串本身。假设您想要为Customer对象创建一个节点。确保覆盖Customer类的toString()方法,并返回一个有意义的字符串显示在Customer节点中,如客户名称和 id。

如果从上到下查看JTree节点,每个节点都显示在单独的水平行中。第一个节点(根节点,如果根节点可见)是第零行。第二个在第一行,依此类推。在图 2-28 中,部门、销售、John、Elaine 和信息技术的行号分别为 0、1、2、3 和 4。请注意,只有在显示节点时,才会将行号分配给该节点。当父节点折叠时,节点可能不会显示。例如,广告节点有一些未显示的子节点,并且没有为它们分配行号,因为广告节点(它们的父节点)已折叠。一个JTree的方法返回可视节点的数量。请注意,当您在JTree中展开和折叠节点时,可见节点的数量会发生变化。

一个TreePath类的对象在一个JTree中唯一地代表一个节点。它的结构类似于文件系统中用来表示文件的路径。文件路径通过指定从根文件夹开始的路径来唯一地表示文件,例如/Departments/Sales/John 表示名为 John 的文件,该文件位于根文件夹下的 Departments 文件夹下的 Sales 文件夹下。一个TreePath对象封装了相同类型的信息来表示一个JTree中的一个节点。它由从根开始的有序节点数组组成。例如,如果您需要为示例中的节点 John 构造一个TreePath对象,您可以按如下方式完成:

Object[] path = new Object[] {root, sales, john};

TreePath johnNodePath = new TreePath(path);

TreePath类的方法返回Object数组,getLastPathComponent()方法返回数组的最后一个元素,这是对节点的引用,TreePath对象表示节点的路径。通常,当你使用一个JTree时,你不会构造一个TreePath对象。相反,您可以在JTree事件中使用一个TreePath对象。如果您使用一个JTree,代表一个TreePath对象的数组对象的每个元素都是一个TreeNode的实例。如果您使用默认的树模型,TreePath将由一组对象组成。拥有一个到节点的TreePath,您可以得到节点包装的对象,如下所示:

// Suppose path is an instance of the TreePath class and it represents a node

DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();

Object myObject = node.getUserObject();

一个JTree提供了两个叫做getRowForPath()getPathForRow()的方法来将一个行号转换成一个TreePath,反之亦然。当你很快了解到JTree事件时,你将和TreePath一起工作。

如果您没有为一个JTree的事件编写代码,您将没有一个节点的(除非您存储了节点引用本身,这不是必需的)。在这种情况下,您可以始终从根节点开始,继续沿着树向下导航。一个JTree的模型是一个TreeModel类的实例,它有一个getRoot()方法。一旦获得了根节点的句柄,就可以使用TreeNode类的children()方法,该方法返回一个TreeNode的所有子节点的枚举。下面的代码片段定义了一个方法navigateTree(),如果您将根节点的引用传递给它,它将遍历所有树节点:

public void navigateTree(TreeNode node) {

if (node.isLeaf()) {

System.out.println("Got a leaf node: " + node);

return;

}

else {

System.out.println("Got a branch node: " + node);

Enumeration e = node.children();

while(e.hasMoreElements()) {

TreeNode n = (TreeNode)e.nextElement();

navigateTree(n); // Recursive method call

}

}

}

您可以通过单击来选择树节点。一个JTree使用一个选择模型来跟踪被选择的节点。您需要与其选择模型进行交互,以选择节点或获取关于所选节点的信息。选择模型是TreeSelectionModel接口的一个实例。一个JTree允许用户在三种不同的模式下选择节点。它们由接口中定义的三个常数表示:

  • SINGLE_TREE_SELECTION:允许用户一次只选择一个节点。
  • CONTIGUOUS_TREE_SELECTION:允许用户选择任意数量的相邻节点。
  • DISCONTIGUOUS_TREE_SELECTION:允许用户选择任意数量的节点,没有任何限制。

下面的代码片段演示了如何使用JTree的选择模型的一些方法:

// Get selection model for JTree

TreeSelectionModel selectionModel = tree.getSelectionModel();

// Set the selection mode to discontinuous

selectionModel.setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);

// Get the selected number of nodes

int selectedCount = selectionModel.getSelectionCount();

// Get the TreePath of all selected nodes

TreePath[] selectedPaths = selectionModel.getSelectionPaths();

您可以将一个TreeSelectionListener添加到一个JTree中,当一个节点被选中或取消选中时,它会得到通知。以下代码片段演示了如何将添加到JTree:

// Create a JTree. Java will add some nodes

JTree tree = new JTree();

// Add selection listener to the JTree

tree.addTreeSelectionListener((TreeSelectionEvent event) -> {

TreeSelectionModel selectionModel = tree.getSelectionModel();

TreePath[] paths = event.getPaths();

for (TreePath path : paths) {

Object node = path.getLastPathComponent();

if (selectionModel.isPathSelected(path)) {

System.out.println("Selected: " + node);

}

else {

// Node is deselected

System.out.println("DeSelected: " + node);

}

}

});

您可以通过单击加号或单击节点本身来展开节点。您可以通过单击减号或单击节点本身来折叠节点。当一个节点展开或折叠时,JTree触发两个事件。它按顺序触发树扩展事件和树扩展事件。tree-will-expand 事件在展开或折叠节点之前触发。如果你从这个事件抛出一个ExpandVetoException,展开(或者折叠)就会停止。否则,将触发树扩展事件。以下代码片段演示了如何为这些事件编写代码:

// Add a TreeWillExpandListener

tree.addTreeWillExpandListener(new TreeWillExpandListener() {

@Override

public void treeWillExpand(TreeExpansionEvent event)

throws ExpandVetoException {

System.out.println("Will Expand:" + event.getPath());

}

@Override

public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {

System.out.println("Will Collapse: " + event.getPath());

}

});

// Add TreeExpansionListener

tree.addTreeExpansionListener(new TreeExpansionListener() {

@Override

public void treeExpanded(TreeExpansionEvent event) {

System.out.println("Exapanded: " + event.getPath());

}

@Override

public void treeCollapsed(TreeExpansionEvent event) {

System.out.println("Collapsed: " + event.getPath());

}

});

Tip

A JTree是一个强大而复杂的 Swing 组件。它可以让你定制几乎所有的东西。每个节点显示在一个JLabel中。分支和叶节点显示的图标不同。默认图标取决于外观。您可以通过创建自己的树单元渲染器来自定义默认图标。您还可以向JTree添加一个TreeModelListener,它会通知您其模型的任何变化。您可以通过使用setEditable(true)方法使JTree可编辑。您可以通过双击来编辑节点的标签。

JTabbedPane 和 JSplitPane

有时,由于空间限制,不可能在一个窗口中显示所有信息。您可以使用JTabbedPane对窗口中的信息进行分组和分离。图 2-29 显示了一个JFrame,它有一个带有两个标签的窗格,标题分别为General InformationContacts,显示一个人的一般信息和联系信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-29。

A JTabbedPane with two tabs

一个JTabbedPane组件充当其他 Swing 组件的容器,以选项卡的方式排列它们。它可以使用标题、图标或两者来显示选项卡。用户需要点击标签来查看标签的内容。使用JTabbedPane最大的好处就是空间共享。一次只能看到JTabbedPane中一个标签的内容。用户可以在选项卡之间切换,以查看另一个选项卡的内容。

一个JTabbedPane也可以让你指定在哪里显示标签。您可以指定将选项卡放置在顶部、底部、左侧或右侧。图 2-29 显示了顶部的选项卡。如果您有一个名为frameJFrame,下面的代码片段会产生如图 2-29 所示的帧。代码向由两个JPanels表示的两个选项卡添加了一个JLabel

JPanel generalInfoPanel = new JPanel();

JPanel contactInfoPanel = new JPanel();

JTabbedPane tabbedPane = new JTabbedPane();

generalInfoPanel.add(new JLabel("General info components go here..."));

contactInfoPanel.add(new JLabel("Contact info components go here..."));

tabbedPane.addTab("General Information", generalInfoPanel);

tabbedPane.addTab("Contacts", contactInfoPanel);

frame.getContentPane().add(tabbedPane, BorderLayout.CENTER);

getTabCount()方法返回一个JTabbedPane中选项卡的数量。一个JTabbedPane中的每个标签都有一个索引。第一个选项卡的索引为 0,第二个选项卡的索引为 1,依此类推。您可以使用其索引来获取表示选项卡的组件。

// Get the reference of the component for the Contact tabs

JPanel contactsPanel = tabbedPane.getTabComponentAt(1);

JSplitPane是一个分割器,可以用来分割两个组件之间的空间。拆分条可以水平或垂直显示。当可用空间小于显示两个组件所需的空间时,用户可以上下或左右移动拆分条,这样一个组件比另一个组件获得更多的空间。如果有足够的空间,两个组件都可以完全显示。

JSplitPane类提供了许多构造函数。您可以使用它的默认构造函数创建它,并使用它的setXxxComponent(Component c)添加两个组件,其中Xxx可以是TopBottomLeftRight。它还允许您指定在更改拆分条的位置时组件的重绘方式。它可以是连续的或非连续的。如果它是连续的,当您移动拆分条时,组件将被重新绘制。如果它是不连续的,当您停止移动拆分条时,组件将被重新绘制。

下面的代码片段显示了添加到一个JSplitPane中的JPanel类的两个实例,该实例又被添加到一个名为frameJFrame的内容窗格中。图 2-30 显示了最终的JFrame

// Create two JPanels and a JSplitPane

JPanel generalInfoPanel = new JPanel();

JPanel contactInfoPanel = new JPanel();

JSplitPane splitPane = new JSplitPane();

generalInfoPanel.add(new JLabel("General info components go here..."));

contactInfoPanel.add(new JLabel("Contact info components go here..."));

// Add two JPanels to the JSplitPane and the JSplitPane

// to the content pane of the JFrame

splitPane.setLeftComponent(generalInfoPanel);

splitPane.setRightComponent(contactInfoPanel);

frame.getContentPane().add(splitPane, BorderLayout.CENTER);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-30。

Using a JSplitPane to split space between two components

自定义对话框

一个JDialog是顶级的 Swing 容器。它被用作一个临时的顶层容器(或者一个弹出窗口)来帮助主窗口吸引用户的注意力。我不严格地使用窗口这个术语来表示一个 Swing 顶级容器。假设您有一个JFrame,您必须在其中显示一个人的信息。您可能没有足够的空间在JFrame中显示一个人的所有细节。在这种情况下,您只能在一个JFrame上显示基本的个人最低信息,并提供一个标记为“个人详细信息”的按钮。当用户点击这个按钮时,你可以打开一个JDialog,显示这个人的详细信息。这是一个使用JDialog向用户显示信息的例子。使用对话窗口的另一个例子是让用户从文件系统中选择一个文件。您可以向用户显示一个对话框,让他浏览文件系统并选择一个文件。您也可以在下列其他场合使用JDialog:

  • 当您想要确认用户的操作时:这称为。例如,当用户在窗口中选择一个人记录并试图删除该人记录时,您会显示一条确认消息“您确定要删除此人吗?”该对话框显示两个标记为“是”和“否”的按钮,以指示用户的选择。
  • 当您需要用户的一些输入时:这被称为。例如,当焦点移到日期字段时,您可能会在JDialog中显示一个日历,并希望用户选择一个日期。输入对话框可以简单到输入/选择一个值或输入多个值,例如一个人的详细信息。
  • 当您想要向用户显示一些消息时:这称为。例如,当用户将一些信息保存到数据库时,您想要用一条消息通知用户,该消息指示数据库事务的状态。

创建一个对话框非常简单:只需创建一个继承自JDialog类的新类。您可以将任意数量的 Swing 组件添加到您的定制JDialog中,就像您添加到JFrame中一样。一个JDialog让添加组件变得更容易。您不需要获取对其内容窗格的引用来设置其布局管理器和添加组件。相反,您可以调用JDialog本身的setLayout()add()方法。这些方法将调用路由到其内容窗格。默认情况下,JDialog使用BorderLayout作为布局管理器。

清单 2-10 列出了一个自定义的JDialog,它在一个JLabel和一个 OK JButton中显示当前的日期和时间。当用户点击JButton时,JDialog关闭。

清单 2-10。显示当前日期和时间的自定义 JDialog

// DateTimeDialog.java

package com.jdojo.swing;

import java.awt.BorderLayout;

import java.time.LocalDateTime;

import java.time.format.DateTimeFormatter;

import javax.swing.JButton;

import javax.swing.JDialog;

import javax.swing.JLabel;

public class DateTimeDialog extends JDialog {

JLabel dateTimeLabel = new JLabel("Datetime placeholder");

JButton okButton = new JButton("OK");

public DateTimeDialog() {

initFrame();

}

private void initFrame() {

// Release all resources when JDialog is closed

this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);

this.setTitle("Current Date and Time");

this.setModal(true);

String currentDateTimeString = getCurrentDateTimeString();

dateTimeLabel.setText(currentDateTimeString);

// There is no need to add components to the content pane.

// You can directly add them to the JDialog.

this.add(dateTimeLabel, BorderLayout.NORTH);

this.add(okButton, BorderLayout.SOUTH);

// Add an action listeenr to the OK button

okButton.addActionListener(e -> DateTimeDialog.this.dispose());

}

private String getCurrentDateTimeString() {

LocalDateTime ldt = LocalDateTime.now();

DateTimeFormatter formatter =

DateTimeFormatter.ofPattern("EEEE MMMM dd, yyyy hh:mm:ss a");

String dateString = ldt.format(formatter);

return dateString;

}

}

DateTimeDialog类是自定义JDialog的一个简单例子。要在您的应用中使用它,您需要创建这个JDialog的一个实例,打包它,并使它可见,如下所示:

DateTimeDialog dateTimeDialog = new DateTimeDialog();

dateTimeDialog.pack();

dateTimeDialog.setVisible(true);

如果您正在显示来自另一个顶级容器的JDialog,比如说一个JFrame或另一个JDialog,您可能希望将它显示在顶级容器的中央。有时你可能想把它显示在屏幕中央。你可以通过使用它的setLocationRelativeTo(Component c)方法将一个JDialog放在顶层容器或屏幕的中央。如果将null作为参数传递,那么JDialog将在屏幕上居中。否则,它将在作为参数传递的组件内居中。

// Center the JDialog within a frame, assuming that myFrame exists

dateTimeDialog.setLocationRelativeTo(myFrame);

// Place the JDialog in the center of screen

dateTimeDialog.setLocationRelativeTo(null);

您可以创建一个拥有者的JDialog,它可以是另一个JDialogJFrameJWindow。通过指定一个JDialog的所有者,您创建了一个父子关系。当JDialog的所有者(或父母)关闭时,JDialog也关闭。当拥有者被最小化或最大化时,JDialog也被最小化或最大化。带有所有者的JDialog总是显示在其所有者的上方。您可以在构造函数中指定一个JDialog的所有者。当您使用它的无参数构造函数创建一个JDialog时,一个隐藏的Frame被创建为它的所有者。注意是个java.awt.Frame,不是javax.swing.JFrameJFrame类继承自Frame类。您还可以创建一个以null为所有者的JDialog,在这种情况下,它没有所有者。

默认情况下,JDialog是可调整大小的。如果你不希望用户调整你的JDialog的大小,你可以通过调用它的setResizable(false)方法来实现。

根据JDialog的焦点行为,可以将其分类为

  • 情态的
  • 非模态的

当显示一个模态JDialog时,它会阻塞应用中其他显示的窗口。换句话说,如果显示了一个模态JDialog,您必须先关闭它,然后才能使用该应用中的任何其他窗口。要制作一个JDialog模态,可以使用它的setModal(true)方法。一些JDialog类的构造函数也让你指定JDialog应该是模态的还是非模态的。

非模态JDialog不会阻止应用中任何其他显示的窗口。您可以在其他窗口和非模态实例JDialog之间切换焦点。默认情况下,JDialog是无模式的。

您也可以为模态JDialog设置模态的范围。一个JDialog可以有四种模态中的一种。它们由java.awt.Dialog.ModalityType枚举中的四个常数定义:

  • MODELESS
  • DOCUMENT_MODAL
  • APPLICATION_MODAL
  • TOOLKIT_MODAL

您可以在其构造函数中或通过使用其方法来指定JDialog的模态类型。

MODELESS的设备类型意味着JDialog不会阻挡任何窗口。

DOCUMENT_MODAL的模态类型意味着JDialog将阻塞其父层次结构中的任何窗口(其所有者、所有者的所有者等等)。它不会阻塞其子层次结构中的任何窗口(其子、子的子等等)。假设你显示了三个窗口:frame是一个JFramedialog1是一只JDialog,主人是framedialog2是另一个JDialog,它的主人是dialog1。如果您为dialog1指定了DOCUMENT_MODAL的设备类型,您可以使用dialog2,但不能使用frame。如果dialog2的设备类型为MODELESS,您可以同时使用dialog1dialog2,因为dialog2不会阻挡任何窗口。

APPLICATION_MODAL的模态类型意味着JDialog将阻止该 Java 应用中的任何窗口,除了其子层次结构中的窗口。

TOOLKIT_MODAL的模态类型意味着JDialog将阻止从同一工具包运行的任何窗口,除了它的子层次结构中的窗口。在 Java 应用中,它与APPLICATION_MODAL相同。当您在使用 Java Web Start 启动的 Applet 或应用中使用它时,它非常有用。你可以把一个浏览器想象成一个应用,多个 Applet 想象成顶层窗口。所有 Applet 都由同一个工具包加载。如果在一个 Applet 中显示一个设备类型为TOOLKIT_MODALJDialog,它将阻止输入到同一浏览器中的任何其他 Applet。您必须授予“toolkitModalityAWTPermission以使 Applet 使用TOOLKIT_MODAL设备。用 Java Web Start 启动的多个应用也会出现同样的行为。

清单 2-11 包含了一个试验JDialog模态类型的程序。对dialog1Modalitydialog2Modality变量使用不同的值,看看它如何影响其他窗口中的阻塞输入。

清单 2-11。试验 JDialog 的通道类型

// JDialogModalityTest.java

package com.jdojo.swing;

import javax.swing.JButton;

import javax.swing.JDialog;

import javax.swing.JFrame;

import java.awt.Dialog.ModalityType;

public class JDialogModalityTest {

public static void main(String[] args) {

JFrame frame = new JFrame("My JFrame");

frame.setBounds(0, 0, 400, 400);

frame.setVisible(true);

final ModalityType dialog1Modality = ModalityType.DOCUMENT_MODAL;

final ModalityType dialog2Modality = ModalityType.DOCUMENT_MODAL;

final JDialog dailog1 = new JDialog(frame, "JDialog 1");

JButton openBtn = new JButton("Open JDialog 2");

openBtn.addActionListener(e -> {

JDialog d2 = new JDialog(dailog1, "JDialog 2");

d2.setBounds(200, 200, 200, 200);

d2.setModalityType(dialog2Modality);

d2.setVisible(true);

});

dailog1.add(openBtn);

dailog1.setBounds(20, 20, 200, 200);

dailog1.setModalityType(dialog1Modality);

dailog1.setVisible(true);

}

}

例如,在 Swing 应用中经常使用JDialog来向用户显示错误消息。每当你需要一个对话窗口时,创建一个自定义的JDialog是很费时间的。秋千的设计者意识到了这一点。他们给了我们JOptionPane类,让我们在使用常用的JDialog类型时更容易。我将在下一节讨论JOptionPane

标准对话框

JOptionPane类让你很容易创建和显示标准的模态对话框。它包含许多静态方法来创建不同种类的JDialog,用细节填充它们,并将它们显示为模态JDialog。当JDialog关闭时,该方法返回一个值来指示用户在JDialog上的动作。注意,JOptionPane类是从JComponent类继承而来的。除了被用作创建标准对话框的工厂之外,JOptionPane类与JDialog类没有任何关系。它还包含返回一个JDialog对象的方法,您可以在您的应用中定制和使用这个对象。您可以显示以下四种标准对话框:

  • 消息对话框
  • 确认对话框
  • 输入对话框
  • 选项对话框

显示标准JDialogJOptionPane类的静态方法的名字类似于showXxxDialog()Xxx可以替换为MessageConfirmInputOption。同样的方法还有另一个版本,叫做showInternalXxxDialog(),它使用一个JInternalFrame来显示对话框细节,而不是一个JDialog。所有四种类型的标准对话框都接受不同类型的参数,并返回不同类型的值。表 2-17 显示了这些方法的参数列表及其描述。

表 2-17。

List of Standard Argument Types and Their Values Used With JOptionPane

| 参数名称 | 参数类型 | 描述 | | --- | --- | --- | | `parentComponent` | `Component` | `JDialog`以指定的父组件为中心。包含该组件的顶层容器成为所显示的`JDialog`的所有者。如果是`null`,则`JDialog`在屏幕上居中。 | | `message` | `Object` | 通常,它是一个需要在对话框中显示为消息的字符串。但是,您可以传递任何对象。如果您传递一个 Swing 组件,它只是简单地显示在对话框中。如果你通过了一个`Icon`,它会显示在一个`JLabel`中。如果您传递任何其他对象,则在该对象上调用`toString()`方法,并显示返回的字符串。你也可以传递一个对象数组(通常是一个字符串数组),数组中的每个元素将一个接一个地垂直显示。 | | `messageType` | `Int` | 它表示您想要显示的消息类型。根据消息的类型,对话框中会显示合适的图标。可用的消息类型由`JOptionPane`类中的下列常量定义:`ERROR_MESSAGE,``INFORMATION_MESSAGE,WARNING_MESSAGE, QUESTION_MESSAGE,PLAIN_MESSAGE.``PLAIN_MESSAGE`类型不显示任何图标。另一个参数是`Icon`类型的,允许您指定自己的图标显示在对话框中。 | | `optionType` | `Int` | 它表示需要在对话框中显示的按钮。下面是在`JOptionPane`类中定义的常数列表,你可以用它来获得对话框中的标准按钮:`DEFAULT_OPTION, YES_NO_OPTION,``YES_NO_CANCEL_OPTION, OK_CANCEL_OPTION``DEFAULT_OPTION`显示一个`OK`按钮。其他选项显示一组按钮,顾名思义。您可以通过向`showOptionDialog()`方法提供`options`参数来定制按钮的数量及其文本。 | | `options` | `Object[]` | 此参数允许您自定义对话框中显示的一组按钮。如果您在数组中传递一个`Component`对象,该组件将显示在按钮行中。如果指定一个`Icon`对象,图标会显示在一个`JButton`中。对于您传递的任何其他类型的对象,将显示一个`JButton`,并且`JButton`的文本是从该对象的`toString()`方法返回的字符串。通常,您传递一个字符串数组作为此参数,以在对话框中显示一组自定义按钮。 | | `title` | `String` | 它是显示为对话框标题的文本。如果不传递此参数,将提供一个默认标题。 | | `initialValue` | `Object` | 该参数用于输入对话框。它表示输入对话框中显示的初始值。 |

通常,当用户关闭对话框时,您希望检查用户使用了什么按钮来关闭对话框。但是也有例外,当对话框只有一个按钮时,比如一个 OK 按钮。在这种情况下,要么您用来显示对话框的方法不返回值,要么您干脆忽略返回值。以下是可用于检查返回值是否相等的常数列表:

  • 确定选项
  • 是 _ 选项
  • 无选项
  • 取消选项
  • 关闭选项

CLOSED_OPTION表示用户使用标题栏上的关闭(X)菜单按钮或使用其他方式(如在 Windows 平台上按下键盘上的 Ctrl + F4 键)关闭了对话框。其他常数表示对话框上正常的按钮用法;例如,OK_OPTION表示用户点击对话框上的 OK 按钮关闭对话框。

JOptionPane还可让您自定义它所显示的按钮的标签。您也不局限于标准的按钮集。也就是说,您可以在对话框中显示任意数量的按钮。在这种情况下,用于显示对话框的JOptionPane方法将为第一次按钮点击返回 0,为第二次按钮点击返回 1,为第三次按钮点击返回 2,依此类推。当稍后讨论JOptionPane类的showOptionDialog()方法时,您将看到这种类型的一个例子。

您可以通过使用JOptionPane类的showMessageDialog()静态方法之一来显示一个消息对话框。消息对话框总是用一个按钮向用户显示某种信息,通常是 OK 按钮。该方法不返回任何值,因为用户所能做的只是单击“确定”按钮关闭对话框。showMessageDialog()方法的签名如下所示:

  • showMessageDialog(Component parentComponent, Object message)
  • showMessageDialog(Component parentComponent, Object message, String title, int messageType)
  • showMessageDialog(Component parentComponent, Object message, String title, int messageType, Icon icon)

下面的代码片段显示了一个消息对话框,如图 2-31 所示。

// Show an information message dialog

JOptionPane.showMessageDialog(null, "JOptionPane is cool!", "FYI",        JOptionPane.INFORMATION_MESSAGE);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-31。

An information message dialog using the JOptionPane .showMessageDialog() method

您可以使用方法显示确认对话框。当您使用此方法时,您对知道用户的响应感兴趣,这由方法的返回值指示。以下代码片段显示一个确认对话框,如图 2-32 所示,并处理用户的响应:

// Show a confirmation dialog box

int response = JOptionPane.showConfirmDialog(null,

"Are you sure you want to save the changes?",

"Confirm Save Changes",

JOptionPane.YES_NO_CANCEL_OPTION,

JOptionPane.QUESTION_MESSAGE);

switch (response) {

case JOptionPane.YES_OPTION:

System.out.println("You chose yes");

break;

case JOptionPane.NO_OPTION:

System.out.println("You chose no");

break;

case JOptionPane.CANCEL_OPTION:

System.out.println("You chose cancel");

break;

case JOptionPane.CLOSED_OPTION:

System.out.println("You closed the dialog box.");

break;

default:

System.out.println("I do not know what you did ");

}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-32。

A confirmation dialog box using the JOptionPane.showConfirmDialog() method

您可以使用方法要求用户输入。您可以为用户输入指定初始值。如果希望用户从列表中选择一个值,可以传递包含该列表的对象数组。UI 将在合适的组件中显示列表,如JComboBoxJList。下面的代码片段显示了一个输入对话框,如图 2-33 所示。

// Ask the user to enter some text about JOptionPane

String response = JOptionPane.showInputDialog("Please enter your opinion about input dialog.");

if (response == null) {

System.out.println("You have cancelled the input dialog.");

}

else {

System.out.println("You entered: " + response);

}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-33。

A simple input dialog

您使用的showInputDialog()方法版本返回一个String,它是用户在输入字段中输入的文本。如果用户取消输入对话框,它返回null

下面的代码片段显示了一个带有选项列表的输入对话框。用户可以从列表中选择一个选项。对话框如图 2-34 所示该版本的方法返回一个Object,而不是一个String

// Show an input dialog that shows the user three options: "Cool!", "Sucks", "Don't know".

// The default selected value is "Don't know".

JComponent parentComponent = null;

Object message = "Please select your opinion about JOptionPane";

String title = "JOptionPane Input Dialog";

int messageType = JOptionPane.INFORMATION_MESSAGE;

Icon icon = null;

Object[] selectionValues = new String[] {"Cool!", "Sucks", "Don't know"};

Object initialSelectionValue = selectionValues[2];

Object response = JOptionPane.showInputDialog(parentComponent, message,                                                                                      title, messageType, icon, selectionValues, initialSelectionValue);

if (response == null) {

System.out.println("You have cancelled the input dialog.");

}

else {

System.out.println("You entered: " + response);

}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-34。

An input dialog with a list of choices

最后,您可以使用如下声明的方法自定义选项按钮:

int showOptionDialog(Component parentComponent, Object message, String title, int optionType, int messageType, Icon icon, Object[] options, Object initialValue)

options参数指定用户的选项。如果在options参数中传递组件,组件显示为选项。如果您传递任何其他对象,比如字符串,那么会为options数组中的每个元素显示一个按钮。

下面的代码片段显示了如何在对话框中显示自定义按钮。它询问用户对某个JOptionPane的看法。出现的对话框如图 2-35 所示。

JComponent parentComponent = null;

Object message = "How is JOptionPane?";

String title = "JOptionPane Option Dialog";

int messageType = JOptionPane.INFORMATION_MESSAGE;

Icon icon = null;

Object[] options = new String[] {"Cool!", "Sucks", "Don't know" };

Object initialOption = options[2];

int response = JOptionPane.showOptionDialog(null, message, title,

JOptionPane.DEFAULT_OPTION,

JOptionPane.QUESTION_MESSAGE,

icon, options, initialOption);

switch(response) {

case 0:

case 1:

case 2:

System.out.println("You selected:" + options[response]);

break;

case JOptionPane.CLOSED_OPTION:

System.out.println("You closed the dialog box.");

break;

default:

System.out.println("I don't know what you did.");

}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-35。

Customizing the Option buttons using the JOptionPane.showOptionDialog() method

默认情况下,您在本节中显示的所有对话框都是不可调整大小的。您想要自定义它们,以便它们可以调整大小。通过使用JOptionPanecreateDialog()方法并执行一系列步骤,你可以定制由JOptionPane的静态方法显示的对话框。

Create an object of JOptionPane.   Optionally, customize the properties of JOptionPane using its methods.   Use createDialog() method to get the reference of the dialog box.   Customize the dialog box.   Display the dialog box using its setVisible(true) method.

以下代码片段显示了如图 2-36 所示的自定义可调整大小对话框。

// Show a custom resizable dialog box using

JOptionPane pane = new JOptionPane("JOptionPane is cool!", JOptionPane.INFORMATION_MESSAGE);

String dialogTitle = "Resizable Custom Dialog Using JOptionPane";

JDialog dialog = pane.createDialog(dialogTitle);

dialog.setResizable(true);

dialog.setVisible(true);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-36。

A custom dialog box using the JOptionPane.createDialog() method

文件和颜色选择器

Swing 有两个内置的 JDialogs,使得从文件系统中选择文件/目录或者以图形方式选择颜色变得更加容易。允许用户从文件系统中选择一个文件。它提供了非静态方法,不像你在JOptionPane中看到的那样,在JDialog中创建和显示文件选择器组件。

是一个 Swing 组件,允许您在JDialog中以图形方式选择颜色。它提供了一个静态方法,正如你在JOptionPane中看到的,它在JDialog中创建和显示了一个颜色选择器组件。

Tip

JFileChooser类提供了创建和显示 JDialogs 的非静态方法,而JColorChooser类提供了用于相同目的的静态方法。拥有静态或非静态方法意味着非静态方法允许您定制JDialog,而静态方法只允许您通过参数定制JDialog。这意味着您可以定制由JFileChooser显示的JDialog,但不能定制JColorChooser。另一个区别是,您必须创建一个JFileChooser类的对象来使用它。最好重用同一个JFileChooser对象,因为它记得最后访问的目录,所以当你重用它时,默认情况下它会将你导航到最后访问的目录。

对话框

以下是在JDialog中显示文件选择器需要执行的步骤。

Create an object of the JFileChooser class.   Optionally, customize its properties using its methods. You can customize properties such as should it let the user choose only files, only directories, or both; should it let the user select multiple files; apply a file filter criteria to show files based on your criteria, etc.   Use one of the three non-static methods, showOpenDialog(), showSaveDialog(), or showDialog(), to display it in a JDialog.   Check for the return value, which is an int, from the method call in the previous step. If it returns JFileChooser.APPROVE_OPTION, the user made a selection. The other two possible return values are JFileChooser.CANCEL_OPTION and JFileChooser.ERROR_OPTION, which indicate that either user cancelled the dialog box or some kind of error occurred. To get the selected file, call the getSelectedFile() or getSelectedFiles() method, which returns a File object and a File array, respectively. Note that a JFileChooser component only lets you select a file from a file system. It does not save or read a file. You can do whatever you like with the file reference returned from it.   You can reuse the file chooser object. It remembers the last visited folder.

默认情况下,JFileChooser开始显示用户默认目录中的文件。您可以在其构造函数中或使用其方法来指定初始目录。

// Create a file chooser with the default initial directory

JFileChooser fileChooser = new JFileChooser();

// Create a file chooser, with an initial directory of C:\myjava.

// You can specify a directory path according to your operating system syntax.

// C:\myjava is using Windows file path syntax.

JFileChooser fileChooser = new JFileChooser("C:\\myjava");

默认情况下,文件选择器只允许选择文件。让我们自定义它,以便您可以选择一个文件或目录。它还应该允许多重选择。以下代码片段完成了这一定制:

// Let the user select files and directories

fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

// Aloow multiple selection

fileChooser.setMultiSelectionEnabled(true);

让我们显示一个打开的文件选择器对话框,并检查用户是否选择了一个文件。如果用户做出选择,在标准输出中打印文件路径。以下代码片段显示如图 2-37 所示的对话框。

// Display an open file chooser

int returnValue = fileChooser.showOpenDialog(null);

if(returnValue == JFileChooser.APPROVE_OPTION) {

File selectedFile = fileChooser.getSelectedFile();

System.out.println("You selected: " + selectedFile);

}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-37。

An open file chooser dialog box using a JFileChooser

该类的所有三个方法都接受一个Component参数。它被用作它所显示的JDialog的所有者,并使对话框居中。将null作为其父组件,使其在屏幕上居中。

注意,在图 2-37 中,有两个按钮。一个被标记为Open,另一个Cancel.``Open按钮被称为批准按钮。对话框的标题是Open。当您使用JFileChooser的方法时,您会得到相同的对话框,除了按钮和标题的文本Open被替换为文本Save。您可以在显示对话框标题和批准按钮文本之前对其进行自定义,如下所示:

// Change the dialog's title

fileChooser.setDialogTitle("Open a picture file");

// Change the button's text

fileChooser.setApproveButtonText("Open File");

第三种方法showDialog(),让您指定批准按钮文本和对话框标题,如下所示:

// Open a file chooser with Attach as its title and approve button's text

int returnValue = fileChooser.showDialog(null, "Attach");

if (returnValue == JFileChooser.APPROVE_OPTION) {

File selectedFile = fileChooser.getSelectedFile();

System.out.println("Attaching file: " + selectedFile);

}

请注意,设置 approve 按钮的文本不会更改该方法的返回值。您仍然需要检查它是否返回了一个JFileChooser.APPROVE_OPTION,这样您就可以继续获取所选择的文件。

Tip

当您使用showOpenDialog()showSaveDialog()方法时,approve 按钮的默认文本取决于外观。在 Windows 上,它们分别是OpenSave

一个JFileChooser让你设置一个文件过滤器。文件过滤器是在对话框中显示文件之前应用的一组条件。文件过滤器是FileFilter类的一个对象,它在javax.swing.filechooser包中。FileFilter级是一个abstract级。要创建文件过滤器,您需要创建一个从FileFilter类继承而来的类,并覆盖accept()getDescription()方法。当文件选择器想要显示文件时,使用文件引用调用该方法。如果accept()方法返回true,则显示该文件。否则,不会显示该文件。下面的代码片段创建并设置了一个文件过滤器,只显示一个目录或一个扩展名为doc的文件。请记住,用户需要导航到文件系统,您必须显示目录。

// Create a file filter to show only a directory or .doc files

FileFilter filter = new FileFilter() {

@Override

public boolean accept(File f) {

if (f.isDirectory()) {

return true;

}

String fileName = f.getName().toLowerCase();

if (fileName.endsWith(".doc")) {

return true;

}

return false; // Reject any other files

}

@Override       public String getDescription() {

return "Word Document";

}

};

// Set the file filter

fileChooser.setFileFilter(filter);

int returnValue = fileChooser.showDialog(null, "Attach");

if (returnValue == JFileChooser.APPROVE_OPTION) {

// Process the file

}

基于文件扩展名设置文件过滤器是如此普遍,以至于从FileFilter类继承而来的FileNameExtensionFilter类直接支持它。它的构造函数接受文件扩展名及其描述。第二个参数是可变长度参数。请注意,文件扩展名是文件名中最后一个点之后的部分。如果文件名中没有点,则它没有扩展名。在你创建了一个FileNameExtensionFilter类的对象后,你需要调用文件选择器的方法来设置一个过滤器。下面的代码片段添加了“java”和“jav”作为文件扩展名过滤器。

FileNameExtensionFilter extFilter =

new FileNameExtensionFilter("Java Source File", "java", "jav");

fileChooser.addChoosableFileFilter(extFilter);

您可以在文件选择器中添加多个文件扩展名过滤器。它们作为文件类型显示在文件选择器下拉列表中。如果要限制用户只能选择您设置为文件过滤器的文件,您需要删除允许用户选择任何文件的文件过滤器,该过滤器称为“接受所有文件过滤器”。在 Windows 上,文件类型显示为“All Files(*.*)”。

// Disable "accept all files filter"

fileChooser.setAcceptAllFileFilterUsed(false);

您可以使用方法检查“接受所有文件过滤器”是否已启用,如果文件选择器正在使用此过滤器,该方法将返回true。您可以使用getAcceptAllFileFilter()方法获得“接受所有文件过滤器”的引用。下面的代码片段设置了“接受所有文件过滤器”(如果尚未设置)。

if (!fileChooser.isAcceptAllFileFilterUsed()) {        fileChooser.setAcceptAllFileFilterUsed(true); }

Tip

一个JFileChooser有许多你可以在应用中使用的特性。有时你可能想得到一个文件类型的相关图标。您可以通过使用文件选择器的getIcon(java.io.File file)方法获得文件类型的关联图标,该方法返回一个Icon对象。注意,您可以使用一个JLabel组件来显示一个Icon对象。当它显示在对话框中时,它还提供了一种机制来侦听用户执行的选择更改和其他操作。

颜色选择对话框

JColorChooser允许您使用对话框选择颜色。它是可定制的。可以向默认颜色选择器添加更多面板。也可以将颜色选择器组件嵌入到容器中。它提供了监听颜色选择器组件上的用户操作的方法。它的常见用法非常简单。您需要调用它的showDialog()静态方法,该方法将返回一个代表用户选择的颜色的java.awt.Color对象。否则返回null。我将在本章的后面介绍Color类。

showDialog()方法的签名如下。它允许您指定对话框的父组件和标题。您还可以设置初始颜色,它将显示在对话框中。

  • static Color showDialog(Component parentComponent, String title, Color initialColor)

下面的代码片段让用户使用JColorChooser选择一种颜色,并在标准输出上打印一条消息:

// Display a color chooser dialog

Color color = JColorChooser.showDialog(null, "Select a color", null);

// Check if user selected a color

if (color == null) {

System.out.println("You cancelled or closed the color chooser");

}

else {

System.out.println("You selected color: " + color);

}

窗户

JFrame一样,JWindow是另一个顶级容器。这是一座未经装饰的JFrame。它没有标题栏、窗口菜单等功能。它不是一个非常常用的顶级容器。您可以将它用作启动窗口,当应用启动时显示一次,几秒钟后自动消失。关于如何在 Java 应用中显示闪屏的更多细节,请参考java.awt.SplashScreen类的 API 文档。像JFrame一样,你可以给JWindow添加 Swing 组件。

使用颜色

java.awt.Color类的对象代表一种颜色。您可以使用 RGB(红色、绿色和蓝色)组件创建一个Color对象。RGB 值可以指定为floatint值。作为一个float值,RGB 中每个分量的范围从 0.0 到 1.0。作为一个int值,RGB 中每个分量的范围是从 0 到 255。还有一个叫做 alpha 的成分与颜色相关联。颜色的 alpha 值定义了颜色的透明度。作为一个float,其取值范围为 0.0 到 1.0,作为一个int,其取值范围为 0 到 255。alpha 值为 0.0 或 0 表示颜色完全透明,而值为 1.0 或 255 表示颜色完全不透明。

您可以创建一个Color对象,如下所示。注意构造函数Color(int red, int green, int blue)中 RGB 分量的值。

// Create red color

Color red = new Color(255, 0, 0);

// Create green color

Color green = new Color(0, 255, 0);

// Create blue color

Color blue = new Color(0, 0, 255);

// Create white color

Color white = new Color(255, 255, 255);

// Create black color

Color black = new Color(0, 0, 0);

alpha 分量被隐式设置为 1.0 或 255,这意味着如果不指定颜色的 alpha 分量,则该颜色是不透明的。以下代码片段通过将 alpha 组件指定为 0 来创建红色透明色:

// Create a transparent red color. The last argument of 0 is the alpha value.

Color transparentRed = new Color(255, 0, 0, 0);

Color类为常用的颜色定义了许多颜色常数。例如,您不需要创建红色。相反,你可以使用Color.redColor.RED常数。Color.red常量从 Java 1.0 开始就存在了。Java 1.4 中增加了相同常量Color.RED的大写版本,以遵循常量的命名约定(常量的名称应该是大写的)。同样,你还有Color.blackColor.BLACKColor.greenColor.GREENColor.darkGrayColor.DARK _ GRAY等。如果您有一个Color对象,您可以分别使用它的getRed()getGreen()getBlue()getAlpha()方法获得它的红色、绿色、蓝色和 alpha 组件。

还有另一种方法来指定颜色,那就是使用 HSB(色调、饱和度和亮度)组件。Color类有两个叫做RGBtoHSB()HSBtoRGB()的方法,可以让你从 RBG 模型转换到 HSB 模型,反之亦然。

一个Color对象与 Swing 组件的setBackground(Color c)setForeground(Color c)方法一起使用。所有 Swing 组件都从JComponent继承了这些方法。这些方法调用可能会被外观忽略。背景色是用来绘制组件的颜色,而前景色通常是组件中显示的文本的颜色。当你设置一个组件的背景颜色时,有一件重要的事情需要考虑,那就是透明度。如果组件是透明的,它不会在其边界内绘制像素。相反,它让容器的像素显示出来。为了让背景色生效,你必须通过调用组件的setOpaque(true)方法使其不透明。下面的代码创建了一个JLabel,并将其背景色设置为红色,前景色(或文本)设置为黑色:

JLabel testLabel = new JLabel("Color Test");

// First make the JLabel opaque. By default, a JLabel is transparent.

testLabel.setOpaque(true);

testLabel.setBackground(Color.RED);

testLabel.setForeground(Color.BLACK);

Tip

Color类的对象是不可变的。它没有任何方法可以让你在创建一个Color对象后设置颜色分量值。这使得共享Color对象成为可能。

使用边框

Swing 为您提供了在组件边缘绘制边框的能力。有不同种类的边界:

  • 斜角边框
  • 柔和的斜面边框
  • 蚀刻边框
  • 线条边框
  • 标题边框
  • 哑光边框
  • 空白边框
  • 复合边框

图 2-38 显示了不同种类的边框是如何使用窗口外观来显示的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-38。

Different types of borders

尽管您可以为任何 Swing 组件设置边框,但是 Swing 组件的实现可能会忽略它。使用带JPanel的标题边框来产生分组效果是很常见的。许多 GUI 工具都有一个分组框 GUI 组件来对相关组件进行分组。Java 没有分组框组件。如果你需要一个分组效果,你需要把你的相关组件放在一个JPanel里面,并给它设置一个标题边框。图 2-39 显示了一个JPanel,它有五个与地址相关的字段,带有一个标题设置为Address的标题边框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-39。

Creating a group box effect using a JPanel with a titled border

为 Swing 组件设置边框很容易:您需要创建一个 border 对象并使用组件的setBorder(Border b)方法。Border是一个由所有类实现的接口,这些类的实例代表一种特定的边界。每种边框都有一个类。你也可以通过从AbstractBorder类继承一个类来创建一个自定义边框。所有与边界相关的类和Border接口都在javax.swing.border包中。

对象是为共享而设计的。虽然您可以直接使用 border 类来创建一个 border 对象,但是建议您使用javax.swing.BorderFactory类来创建一个边框,以便可以共享这些边框对象。BorderFactory类负责边界对象的缓存和共享。你只需要使用它的createXxxBorder()方法来创建一个特定类型的边框,其中Xxx是一个边框类型。表 2-18 列出了所有边界类型的边界等级。

表 2-18。

Available Border Classes

| 边框类型 | 边界等级 | | --- | --- | | 斜角边框 | `BevelBorder` | | 柔和的斜面边框 | `SoftBevelBorder` | | 蚀刻边框 | `EtchedBorder` | | 线条边框 | `LineBorder` | | 标题边框 | `TitledBorder` | | 哑光边框 | `MatteBorder` | | 空白边框 | `EmptyBorder` | | 复合边框 | `CompoundBorder` |

以下代码片段创建了不同种类的边框:

// Create bevel borders

Border bevelRaisedBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED);

Border bevelLoweredBorder = BorderFactory.createBevelBorder(BevelBorder.LOWERED);

// Create soft bevel borders

Border softBevelRaisedBorder = BorderFactory.createSoftBevelBorder(BevelBorder.RAISED);

Border softBevelLoweredBorder = BorderFactory.createSoftBevelBorder(BevelBorder.LOWERED);

// Create etched borders

Border etchedRaisedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);

Border etchedLoweredBorder = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);

// Create line borders

Border lineBorder = BorderFactory.createLineBorder(Color.BLACK);

Border lineThickerBorder = BorderFactory.createLineBorder(Color.BLACK, 3);

// Create titled borders

Border titledBorderAtTop =

BorderFactory.createTitledBorder(etchedLoweredBorder,

"Title text goes here",

TitledBorder.CENTER,

TitledBorder.TOP);

Border titledBorderAtBottom =

BorderFactory.createTitledBorder(etchedLoweredBorder,

"Title text goes here",

TitledBorder.CENTER,

TitledBorder.BOTTOM);

// Create a matte border

Border matteBorder = BorderFactory.createMatteBorder(1,3,5,7, Color.BLUE);

// Create an empty border

Border emptyBorder = BorderFactory.createEmptyBorder();

// Create compound borders

Border twoCompoundBorder = BorderFactory.createCompoundBorder(etchedRaisedBorder, lineBorder);

Border threeCompoundBorder =

BorderFactory.createCompoundBorder(titledBorderAtTop, twoCompoundBorder);

您可以为组件设置边框,如下所示:

myComponent.setBorder(matteBorder);

斜角边框通过在边框的内侧和外侧边缘使用阴影和高光,为您提供三维效果。你可以提高或降低效果。柔和的斜面边框是具有柔和边角的斜面边框。

蚀刻的边框给你一种雕刻的效果。它有两种味道:抬高的和放低的。

线条边框只是画一条线。您可以指定线条的颜色和粗细。

您可以为任何边框类型提供标题。边框的标题是可以显示在边框中指定位置的文本,例如在顶部/底部边框的中间或顶部上方/底部下方。您还可以指定标题文本的对齐方式、颜色和字体。请注意,要使用标题边框,您必须有另一个边框对象。标题边框只是让你为另一种边框提供标题文本。

无光边框允许您用图标装饰边框。如果没有图标,可以指定边框的粗细。

空边框,顾名思义,不显示任何东西。你能猜到为什么你需要一个空的边框吗?边框增加了组件周围的空间。如果您只想在组件周围添加空格,可以使用空边框。空白边框允许您分别指定四条边的间距。

复合边框是一种复合边框,允许您将任意两种边框组合成一个 border 对象。嵌套的层数没有限制。您可以通过用前两个边框创建复合边框来组合三个边框,然后将复合边框与第三个边框组合来创建最终的复合边框。

使用字体

字体用于在视觉上表示文本,例如在计算机屏幕、打印纸或任何其他设备上。类的一个对象代表了 Java 程序中的一种字体。你已经在几乎每个程序中使用了Font对象,而没有直接引用Font类。Java 负责用特定的字体显示文本。例如,您一直在使用显示标签的按钮。为了显示按钮的标签,Java 一直使用默认字体。您可以使用Font对象为 Java 程序中显示的任何文本指定字体。在代码中使用Font对象很简单:创建一个Font类的对象,并使用组件的setFont(Font f)方法。在使用Font类之前,让我们定义术语“字体”和相关术语。

在计算机的内存中,一切都是用 0 和 1 表示的数字。所以一个字符在内存中也是用 0 和 1 来表示的。你如何在电脑屏幕或一张纸上表现一个字符?一个字符用一个符号显示在屏幕或纸上。代表字符的符号形状称为。您可以将字形视为字符的图形表示(或图像)。字符和字形之间的关系并不总是一对一的。

一组字符的字形的特定设计称为。注意,字样是字符(字形)的视觉表示的设计方面,它不指字形的特定实现。表 2-19 列出了一些字体类别及其描述和示例文本。如果在不支持所有字体的设备(如 Kindle)上查看,表格中的示例文本可能不会以相同的字体显示。有些字体的名称是泰晤士报、信使报、Helvetica、加拉蒙德等。

表 2-19。

Examples of Typefaces

| 字体 | 描述 | 示例文本 | | --- | --- | --- | | 衬线 | 字形在行尾有结束笔画。请注意衬线字体和无衬线字体中每个字符的结束笔画的区别。在 Windows 上,它被称为 Roman。例如:Times New Roman。 | 敏捷的棕色狐狸... | | 无衬线字体 | 与衬线不同,字形没有结束笔画。比较此类别和衬线的文本示例。你会发现无衬线字体是由普通线条组成的。在 Windows 上,它被称为瑞士。例如:Arial。 | 敏捷的棕色狐狸... | | 草书 | 它看起来像手写文本,其中一个单词中的后续字形通常是连接在一起的。它通常用于书法。在 Windows 上,它被称为脚本。例如:Mistral AV。 | 敏捷的棕色狐狸... | | 幻想 | 这是一种装饰字体。在窗户上,它被称为装饰。例如:影响。 | 敏捷的棕色狐狸... | | 单一间隔 | 代表所有字符的所有字形都具有相同的宽度。在 Windows 上,它被称为 Modern。通常,它用于计算机程序中。 | `The quick brown fox...` |

除了它的形状设计,一个角色的视觉表现还有另外两个组成部分:风格和大小。风格是指其特征,如粗体(黑色或浅色)、斜体和常规(或罗马体)。尺码是 10、12、14 等。字符的高度以磅为单位,其中一磅为 1/72 英寸。字符的宽度在中指定。间距决定了一英寸中可以显示多少个字符。螺距的典型值范围从 8 到 14。

现在让我们来定义术语“字体”字体是以特定字样、风格和大小表示一组字符的一组字形。您可以拥有使用相同字样的字体,但它们具有不同的样式和大小。这种字体(相同的字样,但不同的风格和大小)的集合被称为字体族。例如,Times 是一个字体系列名称,包含 Times Roman、Times Bold、Times Bold Italic 等字体。

根据存储和呈现的方式,字体可以分为位图字体或矢量字体(也称为面向对象字体或轮廓字体)。在位图字体中,每个字符都以特定样式和大小的位图形式(代表每一位)存储。当您需要在屏幕上呈现一个字符或在纸上打印它时,您需要找到该样式和大小的字符的位图并呈现它。在矢量字体中,几何算法定义每个字符的形状,而不涉及特定的大小。当需要以特定大小的矢量字体呈现字符时,该算法适用于该大小。这就是矢量字体也被称为可缩放字体的原因。TrueType 和 PostScript 是使用矢量字体的字体技术。所有 Java 实现都需要支持 TrueType 字体。

计算机上可用的字体数量可能会有很大差异。您的操作系统可能会安装一些字体,您可能会添加一些字体,或者您可能会删除一些字体。由于 Java 是为在各种操作系统上工作而设计的,它允许你使用一种字体的逻辑字体族名称,并且它会为你找出最佳的物理(真正的)字体。这样,您就不必担心实际的字体名称,也不必担心它们是否在所有执行您的程序的计算机上都可用。Java 定义了五种逻辑字体系列名称,并根据运行它的计算机将它们映射到物理字体系列名称。五种逻辑字体系列名称如下:

  • 衬线
  • 无锡里夫
  • 对话
  • 对话输入
  • 单一间隔

创建字体对象时,需要指定三个元素:逻辑系列名称、样式和大小。以下代码片段创建了一些Font对象:

// Create serif, plain font of size 10

Font f1 = new Font(Font.SERIF, Font.PLAIN, 10);

// Create SansSerif, bold font of size 10

Font f2 = new Font(Font.SANS_SERIF, Font.BOLD, 10);

// Create dialog, bold font of size 15

Font f3 = new Font(Font.DIALOG, Font.BOLD, 15);

// Create dialog input, bold and italic font of size 15

Font f4 = new Font(Font.DIALOG_INPUT, Font.BOLD|Font.ITALIC, 15);

Font类包含逻辑字体系列名称的常量。如果你想对一个字体对象应用多种样式,比如粗体和斜体,你需要像在Font.BOLD|Font.ITALIC中一样使用Font.BOLDFont.ITALIC的位掩码联合。

要为 Swing 组件设置字体,您需要使用该组件的方法,就像这样:

JButton closeButton = new JButton("Close");

closeButton.setFont(f4);

Font类有几个方法可以让你使用字体对象。例如,您可以使用getFamily()getStyle()getSize()方法分别获取字体对象的系列名称、样式和大小。

验证组件

组件可以是有效的,也可以是无效的。除非另有说明,本节中的短语“组件”也包括容器。您可以使用isValid()方法来检查组件是否有效。如果组件有效,该方法返回true。否则,它返回false。如果一个组件的大小和位置已经计算出来,并且它的子组件也是有效的,那么这个组件就是有效的。如果一个组件无效,这意味着它的大小和位置需要重新计算,并且需要在它的容器中重新布局。

向容器添加组件或从容器中移除组件时,容器会被标记为无效。在容器第一次可见之前,容器被验证。容器的验证过程计算其容器层次结构中所有子容器的大小和位置。考虑下面的代码片段来显示一个框架:

MyFrame frame = new MyFrame("Test Frame");

frame.pack();

frame.setVisible(true);

pack()方法做两件事:

  • 首先,它计算框架所有子框架的大小和位置(即验证框架)。
  • 第二,它调整框架的大小,使其子框架正好适合它。

代码中的setVisible()方法足够聪明,不会再次验证该帧,因为pack()方法已经验证了该帧。如果你不调用pack()方法,在调用setVisible()方法之前,setVisible()方法将验证框架。

因此,组件在第一次显示之前是有效的。组件是如何失效的?在容器中添加/删除组件会使容器无效。设置某些属性(如组件的大小)也会使该组件无效。当一个组件变得无效时,它的无效性会向上传播到容器层次结构。您还可以通过调用组件或容器的invalidate()方法来使其无效。注意,调用invalidate()方法将使组件无效,并且它将无效性传播到包含层次结构中。它需要将包含层次结构中的所有容器标记为无效的原因是,如果一个组件被再次布局(通过重新计算其大小/位置),它也会影响其他组件的大小/位置。因此,如果一个组件失效了,容器层次结构中的所有组件和容器也会被标记为无效。

如何再次验证组件?你需要使用组件或者容器的validate()方法。与invalidate()方法不同,validate()方法沿着容器层次结构向下传播,它验证调用它的组件的所有子组件/容器。您可能需要在调用validate()方法之后调用repaint()方法,以便重新绘制屏幕。

您也可以重新验证组件。请注意,重新验证选项仅适用于JComponent并且不适用于容器。您可以通过调用组件的方法来重新验证组件。它在父容器上安排一个validate()方法调用。验证组件的哪个父容器?是直系父母、祖父母还是曾祖父母等。?容器可以是验证根。您可以通过使用isValidateRoot()方法来测试一个容器是否是一个验证根。如果这个方法返回true,那么这个容器就是一个验证根。当你在一个组件上调用revalidate()方法时,它在容器层次结构中一直向上,直到它找到一个作为验证根的容器。JRootPaneJScrollPane是验证根。对验证根的validate()方法的调用被安排在事件调度线程上。如果有对revalidate()的多次调用,它们都被组合起来,一个组件只被重新验证一次。

绘制组件和形状

绘画机制是任何 GUI 的核心。你知道在屏幕上显示一个JFrame需要什么吗?这是一个非常复杂的过程。这是通过绘制一个图像来完成的,你在屏幕上看到的是一个JFrame。当你按下JFrame内的一个JButton时,被那个JButton占据的区域会用不同的阴影和颜色重新绘制,给你一种按钮被按下的印象。大多数情况下,Swing 会在适当的时间绘制屏幕的适当区域。您可能会遇到需要重新绘制 Swing 组件区域的情况。例如,当您在一个 Swing 容器中添加或删除一个可见的组件时,您需要验证并重新绘制该容器,以便正确地重新绘制屏幕上修改过的区域。

Swing 中的一切都有一个经理!您还有一个重画管理器,它是该类的一个实例。它提供油漆服务。您可以通过调用组件上的repaint()方法来请求重画组件。repaint()方法被重载。也可以只重画组件的一部分,而不是整个组件。对方法的调用在事件调度线程中排队。当重画管理器开始重画组件时,如果许多重画请求未决,它将只重画组件一次。

如何在 Swing 组件上执行自定义绘制?Swing 允许您使用回调机制在组件上执行自定义绘制。JComponent类有一个名为paintComponent(Graphics g)的回调方法。Graphics级在java.awt包里。它用于在组件上绘图。注意,绘图可以在各种设备上实现,例如在计算机屏幕、屏幕外图像或打印机上。要实现一个组件的自定义绘制,覆盖它的paintComponent()方法。JComponent类中的paintComponent()方法负责绘制组件的背景。为了确保组件的背景被正确绘制,您需要从组件的paintComponent()方法中调用JComponentpaintComponent()方法。该方法的典型代码如下:

import java.awt.Graphics;

public class YourCustomSwingComponent extends ASwingComponent {

@Override

public void paintComponent(Graphics g) {

// Paint the background

super.paintComponent(g);

// Your custom painting code goes here

}

}

每当需要重画或者当程序调用repaint()方法时,组件的paintComponent()方法被调用。

当您在 Swing 组件上调用repaint()方法时,重画管理器可能会不止绘制您请求绘制的组件。在油漆一个部件之前,有许多事情要考虑。在绘制组件时,组件的背景及其与其他组件的重叠区域是需要考虑的两个最重要的事情。如果组件不是不透明的,则必须在绘制该组件之前绘制该组件的容器。这是必要的,这样你就不会看穿组件的垃圾背景。如果一个组件与另一个组件重叠,至少重叠区域必须考虑显示重叠区域的正确颜色和形状。重叠区域的涂漆将包括所有重叠部件的涂漆。

一个对象有许多方法可以用来绘制几何形状和字符串。你可以画不同的形状,如矩形、椭圆形、弧形等。一个Graphics对象有许多绘图属性,如字体、颜色、坐标系(称为平移)、剪辑(定义绘图区域)、要在其上绘图的组件等。在paintComponent()方法参数中的一个Graphics对象已经设置了许多属性。例如,

  • 字体设置为组件的字体。
  • 颜色设置为组件的前景色。
  • 平移设置为组件的左上角。组件的左上角代表原点,即坐标(0,0)。
  • 剪辑被设置为组件中需要绘制的区域。

您可以在paintComponent()方法中更改Graphics对象的这些属性。然而,如果你想改变翻译或剪辑,你需要小心。你应该创建一个Graphics对象的副本,并使用该副本进行绘图,而不是改变原始Graphics对象的属性。您可以使用Graphics类的create()方法来创建一个Graphics对象的副本。确保在Graphics对象的副本上调用dispose()方法,以释放它用尽的系统资源。复制和使用Graphics对象的典型逻辑如下所示:

public void paintComponent(Graphics g) {

// Create a copy of the passed in Graphics object

Graphics gCopy = g.create();

// Change the properties of gCopy and use it for drawing here

// Dispose the copy of the Graphics object

gCopy.dispose();

}

当您为传递给方法的组件使用Graphics对象时,有一些事情需要注意。

  • 它使用笛卡尔坐标系,原点位于组件的左上角。

  • The x-axis extends to the right and y-axis extends down, as shown in Figure 2-40.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 2-40。

    The coordinate system used by a graphics object inside the paintComponent() method of a component. It shows the coordinates of four corners of a 600 X 200 JPanel

  • 当您使用Graphics对象绘图时,您的绘图可能会超出组件的边界。然而,重画管理器在Graphics对象中设置的剪辑区域之外的任何图形都将被忽略。事实上,在paintComponent()方法返回后,重画管理器将只使用已绘制组件的剪辑区域在屏幕上显示它。这就是为什么你不应该在一个paintComponent()方法中改变Graphics对象的 clip 属性的原因。clip 属性设置为需要绘制的组件区域。

  • Graphics对象的 translation 属性用于设置绘图的坐标系。传递给paintComponent()方法的Graphics对象已经设置了 translation 属性,因此组件的左上角代表坐标系的原点(0,0)。如果您在paintComponent()方法中更改了Graphics对象的 translation 属性,您最好知道自己在做什么。

  • 使用Graphics对象的当前颜色和字体进行绘制。

Graphics类中有许多方法可以让你画出不同种类的形状,比如圆角矩形、弧形、多边形等等。表 2-20 列出了其中的一些方法。关于方法的完整列表,请参考Graphics类的 API 文档。

表 2-20。

Methods of the Graphics Class

| 方法 | 描述 | | --- | --- | | `void drawLine(int x1, int y1, int x2, int y2)` | 从点`(x1, y1)`到点`(x2, y2).`画一条直线 | | `void drawRect(int x, int y,` `int width, int height)` | 绘制左上角坐标为`(x, y)`的矩形。指定的`width`和`height`分别是矩形的宽度和高度。 | | `void fillRect(int x, int y,` `int width, int height)` | 与`drawRect()`方法相同,但有两点不同。它用`Graphics`对象的当前颜色填充该区域。它的宽度和高度比指定的`width`和`height`小一个像素。 | | `void drawOval(int x, int y, int width, int height)` | 绘制一个适合矩形的椭圆形,该矩形以点`(x, y)`作为其左上角并具有指定的宽度和高度。如果你指定相同的宽度和高度,它会画一个圆。 | | `void fillOval(int x, int y, int width, int height)` | 它绘制一个椭圆形并用当前颜色填充该区域。 | | `void drawstring(String str, int x, int y)` | 它绘制指定的字符串`str`。最左边字符的基线在点`(x, y)`。 |

通常,您使用一个JPanel作为定制绘图的画布。清单 2-12 中的代码显示了一个名为的类,它继承自JPanel类。在其构造函数中,它设置自己的首选大小。它覆盖了paintComponent()方法来绘制一些自定义的形状和字符串。图 2-41 显示了运行DrawingCanvas类时的屏幕。

清单 2-12。用作绘图画布的自定义 JPanel

// DrawingCanvas.java

package com.jdojo.swing;

import javax.swing.JPanel;

import java.awt.Graphics;

import java.awt.Dimension;

import java.awt.Graphics2D;

import java.awt.BasicStroke;

import javax.swing.JFrame;

public class DrawingCanvas extends JPanel {

public DrawingCanvas() {

this.setPreferredSize(new Dimension(600, 75));

}

@Override

public void paintComponent(Graphics g) {

// Paint its background

super.paintComponent(g);

// Draw a line

g.drawLine(10, 10, 50, 50);

// Draw a rectangle

g.drawRect(80, 10, 40, 20);

// Draw an oval

g.drawOval(140, 10, 40, 20);

// Fill an oval

g.fillOval(200, 10, 40, 20);

// Draw a circle

g.drawOval(250, 10, 40, 40);

// Draw an arc

g.drawArc(300, 10, 50, 50, 60, 120);

// Draw a string

g.drawString("Hello Swing!", 350, 30);

// Draw a thicker rectangle using Graphics2D

Graphics2D g2d = (Graphics2D)g;

g2d.setStroke(new BasicStroke(4));

g2d.drawRect(450, 10, 50, 50);

}

public static void main(String[] args) {

JFrame frame =

new JFrame("Sample Drawings Using a Graphics Object");

frame.getContentPane().add(new DrawingCanvas());

frame.pack();

frame.setVisible(true);

}

}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-41。

Drawing shapes on a custom JPanel using a graphics object

在运行时,您会得到一个传递给该方法的Graphics2D类的实例。Graphics2D类继承了Graphics类,它有一个非常强大的 API 来绘制几何图形。例如,当您使用Graphics对象时,它使用 1.0 的描边(线宽)绘制形状。如果使用Graphics2D,可以使用自定义笔画。下面的代码片段在你的DrawingCanvas类的paintComponent()方法中使用 4.0 的笔画绘制一个矩形。要使用paintComponent()方法中的Graphics2D API,将传入的Graphics对象转换为Graphics2D,如下所示:

Graphics2D g2d = (Graphics2D)g;

g2d.setStroke(new BasicStroke(4));

g2d.drawRect(450, 10, 50, 50);

JComponent类有一个返回组件的Graphics对象的方法。如果您需要在组件的paintComponent()方法之外绘制组件,您可以使用这个方法来获取组件的Graphics对象,以便使用它进行绘制。

即时绘画

Swing 负责在适当的时候重新绘制可见的组件区域。你也可以通过调用组件的repaint()方法来请求组件的重画。对repaint()方法的调用是异步的。也就是说,它不是立即执行的。它在事件调度线程上排队,并将在将来的某个时间执行。有时情况可能需要立即上漆。使用组件的paintImmediately()方法立即进行绘制。该方法被重载。这两个版本声明如下:

  • void paintImmediately(int x, int y, int w, int h)
  • void paintImmediately(Rectangle r)

Tip

如果需要更频繁地绘制或循环绘制,调用repaint()方法会更有效。对repaint()方法的多次调用被合并成一次调用,而对paintImmediately()方法的调用是单独进行的。

双重缓冲

可以使用不同的技术在屏幕上绘制组件。如果组件直接绘制在屏幕上,则称为屏幕绘制。如果一个组件是使用离屏缓冲区绘制的,并且该缓冲区是一步复制到屏幕上的,这就叫做双缓冲。还有一种绘制组件的技术叫做翻页。翻页使用计算机显卡的视频指针功能来显示视频,视频指针是视频内容的地址。与双缓冲类似,您绘制要在离屏缓冲区上显示的内容。当您在离屏缓冲区上完成绘制时,您将图形卡的视频指针更改到这个离屏缓冲区,图形卡将负责在屏幕上显示图像。与双缓冲不同,翻页不会将屏幕外缓冲中的内容复制到屏幕上的缓冲中。相反,它将图形卡重定向到新的缓冲区。双缓冲和翻页可以避免组件绘制时屏幕闪烁,从而提供更好的用户体验。

Swing 使用双缓冲来绘制所有组件。它允许您禁用组件的双缓冲。当您禁用双缓冲时,会有一个问题。有时候,禁用双缓冲可能真的没有任何作用。如果正在绘制一个容器,Swing 会检查该容器是否启用了双缓冲。如果为容器启用了双缓冲,那么它的所有子组件都将使用双缓冲。因此,简单地禁用组件上的双缓冲没有什么帮助。如果您想禁用双缓冲,您可能只想在容器层次结构的最顶层禁用它,即JRootPane。重画管理器还允许您为应用全局启用/禁用双缓冲,如下所示:

RepaintManager currentManager = RepaintManager.currentManager(component);

currentManager.setDoubleBufferingEnabled(false);

当启用双缓冲时,Swing 将创建一个离屏图像,并将该离屏图像的图形传递给JComponentpaintComponent()方法。当你在paintComponent()方法中使用Graphics对象绘制任何东西时,本质上你是在屏幕外的图像上绘制。最后,Swing 会将屏幕外的图像复制到屏幕上。

双缓冲还允许你在程序中创建一个离屏图像。您可以绘制该屏幕外图像,并在应用中任何需要的地方使用该图像。您需要使用组件的createImage()方法来创建一个离屏图像。下面的代码创建了一个名为OffScreenImagePanel的自定义JPanel。在它的paintComponent()方法中,它创建一个离屏图像,用红色填充该图像,并使用该图像绘制到JPanel。这是一个微不足道的例子。但是,它演示了在应用中使用离屏图像所需执行的步骤。

public class OffScreenImagePanel extends JPanel{

public OffScreenImagePanel() {

this.setPreferredSize(new Dimension(200, 200));

}

public void paintComponent(Graphics g) {

super.paintComponent(g);

// Create an offscreen image and fill a rectangle with red

int w = this.getWidth();

int h = this.getHeight();

Image offScreenImage = this.createImage(w, h);

Graphics imageGraphics = offScreenImage.getGraphics();

imageGraphics.setColor(Color.RED);

imageGraphics.fillRect(0, 0, w, h);

// Draw the offscreen image on the JPanel

g.drawImage(offScreenImage, 0, 0, null);

}

}

重新访问 JFrame

你已经在你写的几乎每个程序中使用了这一章中的 JFrames。在这一节中,我将讨论一些重要的事件和JFrame的性质。

您可以使用setExtendedState(int state)方法以编程方式设置JFrame的状态。使用JFrame类继承的java.awt.Frame类中定义的常量来指定状态。

// Display the JFrame maximized

frame.setExtendedState(JFrame.MAXIMIZED_BOTH);

通常,你可以使用标题栏角落里的状态按钮或状态菜单来改变JFrame的状态。表 2-21 列出了可用于改变JFrame状态的常数。

表 2-21。

The List of Constants That Define States of a JFrame

| JFrame 状态常数 | 描述 | | --- | --- | | `NORMAL` | `JFrame`以正常尺寸显示。 | | `ICONIFIED` | `JFrame`以最小化状态显示。 | | `MAXIMIZED_HORIZ` | `JFrame`在水平方向最大化显示,但在垂直方向以正常尺寸显示。 | | `MAXIMIZED_VERT` | `JFrame`垂直最大化显示,但水平以正常尺寸显示。 | | `MAXIMIZED_BOTH` | `JFrame`水平和垂直最大化显示。 |

有时你可能想在你的JFrameJDialog中使用一个默认按钮。默认按钮是JButton类的一个实例,当用户按下键盘上的一个键时就会被激活。激活默认按钮的键是由外观定义的。通常,激活默认按钮的键是Enter键。您可以为JRootPane设置一个默认按钮,该按钮出现在JFrameJDialogJWindowJAppletJInternalFrame中。通常,您将OK按钮设置为JDialog上的默认按钮。如果一个JRootPane有一个默认按钮集,按下Enter键将激活那个按钮,如果你有一个动作执行的事件处理程序添加到那个按钮,你的代码将被执行。

// Create a JButton

JButton okButton = new JButton("OK");

// Add an event handler to okButton here...

// Set okButton as the default button

frame.getRootPane().setDefaultButton(okButton);

您可以添加一个窗口监听器到一个JFrame或任何其他顶层 Swing 窗口,它将通知您窗口状态的七种变化。下面的代码片段向名为frameJFrame添加了一个窗口监听器。如果您对监听少量的窗口状态变化感兴趣,您可以使用WindowAdapter类来代替WindowListener接口。WindowAdapter类提供了WindowListener接口中所有七个方法的空实现。

frame.addWindowListener(new WindowListener() {

@Override

public void windowOpened(WindowEvent e) {

System.out.println("JFrame has been made visible first time");

}

@Override

public void windowClosing(WindowEvent e) {

System.out.println("JFrame is closing.");

}

@Override

public void windowClosed(WindowEvent e) {

System.out.println("JFrame is closed.");

}

@Override

public void windowIconified(WindowEvent e) {

System.out.println("JFrame is minimized.");

}

@Override

public void windowDeiconified(WindowEvent e) {

System.out.println("JFrame is restored.");

}

@Override

public void windowActivated(WindowEvent e) {

System.out.println("JFrame is activated.");

}

@Override

public void windowDeactivated(WindowEvent e) {

System.out.println("JFrame is deactivated.");

}

});

// Use the WindowAdapter class to intercept only the window closing event

frame.addWindowListener(new WindowAdapter() {

@Override

public void windowClosing(WindowEvent e) {

System.out.println("JFrame is closing.");

}

});

当你使用完一个窗口(JFrameJDialogJWindow)时,你应该调用它的dispose()方法,这将使它不可见,并释放资源给操作系统。请注意,dispose()方法并不销毁或垃圾收集窗口对象。只要你持有窗口的引用并且它是可访问的,Java 就不会破坏你的窗口,你可以通过调用它的setVisible(true)方法再次显示它。

摘要

Swing 提供了大量组件来开发 GUI 应用。大多数 Swing 组件都是轻量级组件,它们使用 Java 代码进行重绘,而无需使用本机对等组件。JComponent类是所有 Swing 组件的基类。可以包含其他组件的组件称为容器。Swing 提供了两种类型的容器:顶级容器和非顶级容器。顶级容器不包含在另一个容器中,它可以直接显示在桌面上。JFrame类的一个实例代表一个顶级容器。

JButton类的一个对象代表一个按钮。按钮也称为按钮或命令按钮。用户按下或点击一个JButton来执行一个动作。按钮可以显示文本和/或图标。

JPanel类的对象代表一个可以包含其他组件的容器。典型地,一个JPanel被用来将相关的组件组合在一起。一个JPanel是非顶级容器。

JLabel类的对象表示显示文本、图标或两者的标签组件。通常,JLabel中的文本描述了另一个组件。

Swing 提供了几个文本组件,允许您显示和编辑不同类型的文本。JTextField类的一个对象用于处理一行纯文本。JTextArea的一个对象用于处理多行纯文本。JPasswordField的一个对象用于处理单行文本,其中文本中的实际字符被替换为回显字符。JFormattedTextField的一个对象允许您使用一行纯文本,您可以指定文本的格式,例如以 mm/dd/yyy 格式显示日期。JEditorPane的一个对象可以让你处理 HTML 和 RTF 格式的文本。JTextPane的一个对象允许您处理带有嵌入图像和组件的样式化文档。您可以向文本组件添加输入验证器,以验证用户输入的文本。InputVerifier类的一个实例充当输入验证器。您可以使用JComponent类的setInputVerifier()方法为文本组件设置输入验证器。

Swing 提供了许多组件,允许您从项目列表中选择一个或多个项目。这些组件是JToggleButtonJCheckBoxJRadioButtonJComboBoxJList类的对象。ToggleButton可以处于按下或未按下状态,代表是/否选择。JCheckBox可用于表示是/否选择。有时一组CheckBox es 用于让用户选择零个或多个选项。一组JRadioButton用来呈现给用户一组互斥的选项。ComboBox用于为用户提供一组互斥的选项,用户可以选择输入新的选项值。与其他选项相比,ComboBox在屏幕上占用更少的空间,提供组件,因为它折叠了所有选项,用户必须打开选项列表才能做出选择。一个JList让用户从选项列表中选择零个或多个选项。用户可以看到JList中的所有选项。

一个JSpinner组件结合了一个JFormattedTextField和一个可编辑的JComboBox的优点。它允许您像在JComboBox中一样设置一个选择列表,同时,您还可以对显示的值应用一种格式。它一次只显示选项列表中的一个值。它允许您输入新值。

JScrollBar用于提供滚动功能,以查看尺寸大于可用空间的组件。一个JScrollBar可以垂直放置,也可以水平放置。沿着JScrollBar的轨迹拖动一个旋钮,就可以完成划水。您需要编写逻辑来使用JScrollBar组件提供批评功能。

ScollPane是一个容器,用于包装尺寸大于可用空间的组件。ScrollPane提供水平和垂直方向的自动弯曲能力。

一个JProgressBar用于显示任务的进度。它可以具有水平或垂直方向。它有三个相关的值:当前值、最小值和最大值。如果不知道任务的进度,就说JProgressBar处于不确定状态。

一个JSlider可以让你通过沿轨道滑动旋钮从两个整数之间的一组值中选择一个值。

当您想要在两个组件或两组组件之间添加分隔符时,JSeparator是一个方便的组件。通常,菜单中使用一个JSeparator来分隔相关菜单项的组。通常,它显示为水平或垂直实线。

菜单组件用于以紧凑的形式向用户提供动作列表。一个对象JMenuBar类代表一个菜单栏。一个JMenuJMenuItemJCheckBoxMenuItemJRadioButtonMenuItem类的对象代表一个菜单项。

工具栏是一组小按钮,在JFrame中为用户提供常用的操作。通常,您会提供一个工具栏和一个菜单。

JTable用于以表格形式显示和编辑数据。它以行和列的形式显示数据。每列都有一个列标题。行和列是使用从 0 开始的索引的引用。

一个JTree用于以树状结构显示分层数据。一个JTree中的每一项称为一个节点。有子节点的节点称为分支节点。没有子节点的节点称为叶节点。分支节点被称为其子节点的父节点。JTree中没有父节点的第一个节点称为根节点。

一个JTabbedPane组件充当其他 Swing 组件的容器,以选项卡的方式排列它们。它可以使用标题、图标或两者来显示选项卡。一次只能看到一个标签的内容。一个JTabbedPane让你共享多个标签之间的空间。

JSplitPane是一个分割器,可以用来分割两个组件之间的空间。拆分条可以水平或垂直显示。当可用空间小于显示两个组件所需的空间时,用户可以向上/向下或向左/向右移动拆分条,以便一个组件比另一个组件获得更多的空间。如果有足够的空间,两个组件都可以完全显示。

一个JDialog是顶级的 Swing 容器。它被用作一个临时的顶层容器(或作为一个弹出窗口)来帮助主窗口获得用户的注意或用户的输入。JOptionPane类提供了许多静态方法,使用JDialog类的实例向用户显示不同类型的对话框。

允许用户使用内置对话框从文件系统中选择文件/目录。is 允许用户使用内置对话框以图形方式选择颜色。

一个JWindow是一个未分解的顶级容器。它不是一个常用的顶级容器,除了作为一个启动窗口,当应用启动时显示一次,几秒钟后自动消失。

Swing 允许您设置组件的背景色和前景色。java.awt.Color类的对象代表一种颜色。您可以使用红色、绿色、蓝色和 alpha 分量,或者使用色调、饱和度和亮度分量来指定颜色。Color类是不可变的。它提供了几个代表常用颜色的常量,例如,Color.REDColor.BLUE常量代表红色和蓝色。

在 Swing 中,您可以在组件周围绘制一个边框。边界由一个Border接口的实例表示。存在不同类型的边框:斜面边框、柔和斜面边框、蚀刻边框、线条边框、标题边框、无光泽边框、空白边框和复合边框。BorderFactory类提供了创建所有类型边框的工厂方法。

Swing 允许您为组件中显示的文本设置字体。类的一个对象代表了 Java 程序中的一种字体。

组件可以是有效的,也可以是无效的。如果组件无效,组件的isValid()方法返回true。无效组件表示需要重新计算其位置和大小,并且需要重新布局。组件在第一次可见之前是有效的。添加/删除组件和更改属性可能会更改组件的位置和/或大小,这可能会使组件无效。调用validate()方法使组件再次有效。

Swing 可以让你画出多种形状(圆形、矩形、直线、多边形等等。)使用Graphics对象。通常,您使用JPanel作为画布来绘制形状。

Swing 提供了两种重画组件的方式:异步和同步。调用repaint()方法异步绘制组件,调用paintImmediately()方法立即绘制组件。

组件的喷涂可以在屏幕上进行,也可以在屏幕外进行。屏幕上的绘画可能会导致闪烁。绘画可以使用缓冲区在屏幕外进行,缓冲区可以一次性复制到屏幕上以避免闪烁。这种屏幕外绘画被称为双缓冲,它通过在屏幕上提供平滑的绘画来提供更好的用户体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值