设置包版本信息
原文:
docs.oracle.com/javase/tutorial/deployment/jar/packageman.html
您可能需要在 JAR 文件的 MANIFEST.MF 中包含包版本信息。您可以使用 MANIFEST.MF 中的以下头部提供此信息:
清单中的头部
头部 | 定义 |
---|---|
Name |
规范的名称。 |
Specification-Title |
规范的标题。 |
Specification-Version |
规范的版本。 |
Specification-Vendor |
规范的供应商。 |
Implementation-Title |
实现的标题。 |
Implementation-Version |
实现的构建编号。 |
Implementation-Vendor |
实现的供应商。 |
每个包可以分配一组这样的头部。版本头部应直接出现在包的Name
头部下方。此示例显示了所有版本头部:
Name: java/util/
Specification-Title: Java Utility Classes
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: java.util
Implementation-Version: build57
Implementation-Vendor: Example Tech, Inc.
关于包版本头的更多信息,请参阅包版本规范。
一个示例
我们希望在 MyJar.jar 的 MANIFEST.MF 中包含上述示例中的头部。
首先创建一个名为Manifest.txt
的文本文件,内容如下:
Name: java/util/
Specification-Title: Java Utility Classes
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: java.util
Implementation-Version: build57
Implementation-Vendor: Example Tech, Inc.
警告: 文本文件必须以新行或回车符结束。如果最后一行没有以新行或回车符结束,将无法正确解析最后一行。
然后通过输入以下命令创建名为MyJar.jar
的 JAR 文件:
jar cfm MyJar.jar Manifest.txt MyPackage/*.class
这将创建带有以下内容的 MANIFEST.MF 文件的 JAR 文件:
Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)
Name: java/util/
Specification-Title: Java Utility Classes
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: java.util
Implementation-Version: build57
Implementation-Vendor: Example Tech, Inc.
在 JAR 文件中封装包
原文:
docs.oracle.com/javase/tutorial/deployment/jar/sealman.html
JAR 文件中的包可以选择性地封装,这意味着该包中定义的所有类必须存档在同一个 JAR 文件中。例如,您可能希望封装一个包,以确保软件中的类之间的版本一致性。
通过在 MANIFEST.MF 中添加Sealed
头部来封装 JAR 文件中的包,其一般形式为:
Name: myCompany/myPackage/
Sealed: true
值myCompany/myPackage/
是要封装的包的名称。
请注意,包名必须以“/”结尾。
一个示例
我们想要在 JAR 文件MyJar.jar
中封装两个包firstPackage
和secondPackage
。
首先,我们创建一个名为Manifest.txt
的文本文件,内容如下:
Name: myCompany/firstPackage/
Sealed: true
Name: myCompany/secondPackage/
Sealed: true
警告: 文本文件必须以新行或回车符结束。如果最后一行没有以新行或回车符结束,将无法正确解析最后一行。
然后,我们通过输入以下命令创建一个名为MyJar.jar
的 JAR 文件:
jar cfm MyJar.jar Manifest.txt MyPackage/*.class
这将创建一个带有以下内容的 MANIFEST.MF 文件的 JAR 文件:
Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)
Name: myCompany/firstPackage/
Sealed: true
Name: myCompany/secondPackage/
Sealed: true
封装 JAR 文件
如果要确保包中的所有类来自同一代码源,请使用 JAR 封装。封装的 JAR 指定该 JAR 定义的所有包都是封装的,除非在每个包的基础上进行覆盖。
要封装一个 JAR 文件,请使用值为 true 的Sealed
MANIFEST.MF 头部。例如,
Sealed: true
指定此存档中的所有包都是封装的,除非在 MANIFEST.MF 条目中使用Sealed
属性明确覆盖特定包。
使用清单属性增强安全性
原文:
docs.oracle.com/javase/tutorial/deployment/jar/secman.html
以下 JAR 文件清单属性可帮助确保您的 applet 或 Java Web Start 应用程序的安全性。只有Permissions
属性是必需的。
-
Permissions
属性用于确保应用程序仅请求在用于调用应用程序的 applet 标记或 JNLP 文件中指定的权限级别。使用此属性可帮助防止有人重新部署使用您的证书签名的应用程序,并以不同的特权级别运行。此属性在主 JAR 文件的清单中是必需的。有关更多信息,请参阅 Java 平台标准版部署指南中的Permissions Attribute。
-
Codebase
属性用于确保 JAR 文件的代码库受限于特定域。使用此属性可防止有人出于恶意目的在另一个网站上重新部署您的应用程序。有关更多信息,请参阅 Java 平台标准版部署指南中的Codebase Attribute。 -
Application-Name
属性用于提供签名应用程序安全提示中显示的标题。有关更多信息,请参阅 Java 平台标准版部署指南中的Application-Name Attribute。 -
Application-Library-Allowable-Codebase
属性用于标识您的应用程序预期被找到的位置。使用此属性可减少 JAR 文件位于不同位置时在安全提示中显示的位置数量。有关更多信息,请参阅 Java 平台标准版部署指南中的Application-Library-Allowable-Codebase Attribute。 -
Caller-Allowable-Codebase
属性用于标识可以从中 JavaScript 代码调用您的应用程序的域。使用此属性可防止未知的 JavaScript 代码访问您的应用程序。有关更多信息,请参阅 Java 平台标准版部署指南中的Caller-Allowable-Codebase Attribute。 -
Entry-Point
属性用于标识允许用作 RIA 入口点的类。使用此属性可防止未经授权的代码从 JAR 文件中的其他可用入口点运行。有关更多信息,请参阅 Java 平台标准版部署指南中的Entry-Point Attribute。 -
Trusted-Only
属性用于防止加载不受信任的组件。有关更多信息,请参阅 Java 平台标准版部署指南中的Trusted-Only Attribute。 -
Trusted-Library
属性用于允许特权 Java 代码和沙箱 Java 代码之间的调用,而无需提示用户进行权限确认。有关更多信息,请参阅 Java 平台标准版部署指南中的Trusted-Library Attribute。
有关如何将这些属性添加到清单文件中的信息,请参阅修改清单文件。
签署和验证 JAR 文件
原文:
docs.oracle.com/javase/tutorial/deployment/jar/signindex.html
您可以选择使用您的电子“签名”对 JAR 文件进行签名。验证您签名的用户可以授予您的 JAR 捆绑软件安全权限,这些权限通常是没有的。反之,您也可以验证您想要使用的已签名 JAR 文件的签名。
本课程将向您展示如何使用 JDK 提供的工具来签署和验证 JAR 文件:
理解签名和验证
如果您对签名和验证的概念不熟悉,本节将帮助您快速了解。它包含了相关术语的定义,签名提供的一些好处的解释,以及 Java 平台与 JAR 文件相关的签名机制的概要。
签署 JAR 文件
在这一部分,您将学习如何使用 JDK™工具对您的 JAR 文件进行数字签名。
验证已签名的 JAR 文件
本节将向您展示如何使用 JDK 工具集来验证已签名的 JAR 文件。
理解签署和验证
原文:
docs.oracle.com/javase/tutorial/deployment/jar/intro.html
Java™ 平台使你能够数字签名 JAR 文件。你数字签名一个文件的原因与你可能用钢笔在纸上签署文件的原因相同--让读者知道你写了这个文件,或者至少这个文件得到了你的批准。
当你签署一封信时,例如,所有认识你签名的人都可以确认你写了这封信。同样地,当你数字签名一个文件时,任何“认识”你数字签名的人都知道这个文件来自你。识别电子签名的过程称为验证。
当 JAR 文件被签名时,你还可以选择给签名加上时间戳。类似于在纸质文件上写日期,时间戳签名标识了 JAR 文件签名的时间。时间戳可用于验证签署 JAR 文件时使用的证书在签署时是有效的。
签署和验证文件的能力是 Java 平台安全架构的重要组成部分。安全性由运行时生效的安全策略控制。你可以配置策略以授予小程序和应用程序安全权限。例如,你可以授予小程序执行通常被禁止的操作的权限,如读写本地文件或运行本地可执行程序。如果你下载了由受信任实体签名的代码,你可以将这一事实作为决定为代码分配哪些安全权限的标准之一。
一旦你(或你的浏览器)验证了一个小程序来自受信任的来源,你可以让平台放宽安全限制,让小程序执行通常被禁止的操作。一个受信任的小程序可以根据运行时生效的策略文件拥有特定的自由。
Java 平台通过使用称为公钥和私钥的特殊数字来实现签署和验证。公钥和私钥是成对出现的,它们扮演互补的角色。
私钥是你可以用来签署文件的电子“笔”。顾名思义,你的私钥只有你知道,这样别人就无法“伪造”你的签名。用你的私钥签署的文件只能通过相应的公钥进行验证。
然而,仅有公钥和私钥还不足以真正验证一个签名。即使你已经验证了一个签名文件包含匹配的密钥对,你仍然需要一种方式来确认公钥确实来自它所声称来自的签署者。
因此,要使签名和验证工作,还需要另一个元素。这个额外的元素是签名者在签名的 JAR 文件中包含的证书。证书是来自被认可的认证机构的数字签名声明,指示谁拥有特定的公钥。认证机构是受到行业信任的实体(通常是专门从事数字安全的公司),它们负责为密钥及其所有者签署和发布证书。在签名的 JAR 文件中,证书指示谁拥有 JAR 文件中包含的公钥。
当您签署一个 JAR 文件时,您的公钥将与相关证书一起放置在存档中,以便任何想要验证您签名的人可以轻松使用。
总结数字签名:
-
签名者使用私钥对 JAR 文件进行签名。
-
相应的公钥与其证书一起放置在 JAR 文件中,以便任何想要验证签名的人可以使用。
摘要和签名文件
当您签署一个 JAR 文件时,存档中的每个文件都会在存档的清单中给出一个摘要条目。以下是这样一个条目可能看起来的示例:
Name: test/classes/ClassOne.class
SHA1-Digest: TD1GZt8G11dXY2p4olSZPc5Rj64=
摘要值是文件内容的哈希值或编码表示,这些值是在签名时文件的内容。只有当文件本身发生变化时,文件的摘要值才会改变。
当对 JAR 文件进行签名时,会自动生成一个签名文件,并将其放置在 JAR 文件的META-INF
目录中,该目录与存档的清单文件位于同一目录中。签名文件的文件名具有.SF
扩展名。以下是签名文件内容的示例:
Signature-Version: 1.0
SHA1-Digest-Manifest: h1yS+K9T7DyHtZrtI+LxvgqaMYM=
Created-By: 1.7.0_06 (Oracle Corporation)
Name: test/classes/ClassOne.class
SHA1-Digest: fcav7ShIG6i86xPepmitOVo4vWY=
Name: test/classes/ClassTwo.class
SHA1-Digest: xrQem9snnPhLySDiZyclMlsFdtM=
Name: test/images/ImageOne.gif
SHA1-Digest: kdHbE7kL9ZHLgK7akHttYV4XIa0=
Name: test/images/ImageTwo.gif
SHA1-Digest: mF0D5zpk68R4oaxEqoS9Q7nhm60=
如您所见,签名文件包含存档文件的摘要条目,这些条目看起来类似于清单中的摘要值条目。然而,清单中的摘要值是从文件本身计算出来的,而签名文件中的摘要值是从清单中的相应条目计算出来的。签名文件还包含整个清单的摘要值(请参见上面示例中的SHA1-Digest-Manifest
头)。
当验证已签名的 JAR 文件时,将重新计算每个文件的摘要,并将其与清单中记录的摘要进行比较,以确保 JAR 文件的内容自签名以来没有发生变化。作为额外的检查,还将重新计算清单文件本身的摘要值,并将其与签名文件中记录的值进行比较。
您可以在 JDK™文档的清单格式页面上阅读有关签名文件的其他信息。
签名块文件
除了签名文件外,当对 JAR 文件进行签名时,签名块文件会自动放置在META-INF
目录中。与清单文件或签名文件不同,签名块文件不是人类可读的。
签名块文件包含两个用于验证的关键元素:
-
使用签名者的私钥生成的 JAR 文件的数字签名
-
包含签名者公钥的证书,供任何想要验证已签名 JAR 文件的人使用
签名块文件的文件名通常会有一个.DSA
扩展名,表示它们是由默认数字签名算法创建的。如果使用与其他标准算法相关的密钥进行签名,则可能会有其他文件扩展名。
相关文档
有关密钥、证书和认证机构的其他信息,请参见
有关 Java 平台安全架构的更多信息,请参阅此相关文档:
-
Java SE 中的安全功能
签署 JAR 文件
原文:
docs.oracle.com/javase/tutorial/deployment/jar/signing.html
你使用 JAR 签名和验证工具来签署 JAR 文件并为签名加上时间戳。你通过使用 jarsigner
命令来调用 JAR 签名和验证工具,因此我们简称为 "Jarsigner"。
要签署一个 JAR 文件,你首先必须拥有一个私钥。私钥及其关联的公钥证书存储在受密码保护的数据库中,称为 keystores。一个 keystore 可以保存许多潜在签署者的密钥。keystore 中的每个密钥可以通过一个 alias 来标识,通常是拥有该密钥的签署者的名称。例如,属于 Rita Jones 的密钥可能具有别名 "rita"。
签署 JAR 文件的命令的基本形式是
jarsigner *jar-file alias*
在这个命令中:
-
jar-file
是要签署的 JAR 文件的路径名。 -
alias
是标识要用于签署 JAR 文件和密钥关联证书的私钥的别名。
Jarsigner 工具会提示你输入 keystore 和别名的密码。
这个命令的基本形式假定要使用的 keystore 存储在你的主目录中名为 .keystore
的文件中。它将创建名称分别为 x.SF
和 x.DSA
的签名和签名块文件,其中 x
是别名的前八个字母,全部转换为大写。这个基本命令将覆盖原始 JAR 文件,用已签名的 JAR 文件替换。
在实践中,你可能希望使用其中一个或多个可用的命令选项。例如,鼓励为签名加上时间戳,以便部署应用程序的任何工具都可以验证用于签署 JAR 文件的证书在签署文件时是有效的。如果未包含时间戳,则 Jarsigner 工具会发出警告。
选项位于jar-file
路径名之前。以下表格描述了可用的选项:
Jarsigner 命令选项
选项 | 描述 |
---|---|
-keystore url |
指定要使用的 keystore,如果你不想使用默认的 .keystore 数据库。 |
-sigfile file |
指定 .SF 和 .DSA 文件的基本名称,如果你不希望基本名称取自你的别名。 file 必须仅由大写字母(A-Z)、数字(0-9)、连字符(-)和下划线(_)组成。 |
-signedjar file |
指定要生成的已签名 JAR 文件的名称,如果你不希望原始未签名文件被已签名文件覆盖。 |
-tsa url |
使用由 URL 标识的时间戳机构(TSA)为签名生成时间戳。 |
-tsacert alias |
使用由 alias 标识的 TSA 的公钥证书为签名生成时间戳。 |
-altsigner class |
表示要使用另一种签名机制来时间戳签名。完全限定的类名标识所使用的类。 |
-altsignerpath classpathlist |
提供altsigner 选项标识的类的路径以及类所依赖的任何 JAR 文件。 |
示例
让我们看几个使用 Jarsigner 工具签署 JAR 文件的示例。在这些示例中,我们假设以下情况:
-
您的别名是"johndoe"。
-
您要使用的密钥库位于当前工作目录中名为"mykeys"的文件中。
-
您要用于时间戳签名的 TSA 位于
http://tsa.url.example.com
。
在这些假设下,您可以使用此命令对名为app.jar
的 JAR 文件进行签名:
jarsigner -keystore mykeys -tsa http://tsa.url.example.com app.jar johndoe
您将被提示输入密钥库和别名的密码。因为此命令不使用-sigfile
选项,因此创建的.SF 和.DSA 文件将命名为JOHNDOE.SF
和JOHNDOE.DSA
。因为该命令不使用-signedjar
选项,所以生成的签名文件将覆盖app.jar
的原始版本。
让我们看看如果您使用不同的选项组合会发生什么:
jarsigner -keystore mykeys -sigfile SIG -signedjar SignedApp.jar
-tsacert testalias app.jar johndoe
签名和签名块文件将分别命名为SIG.SF
和SIG.DSA
,已签名的 JAR 文件SignedApp.jar
将放置在当前目录中。原始未签名的 JAR 文件将保持不变。此外,签名将使用 TSA 的公钥证书时间戳,标识为testalias
。
附加信息
JAR 签名和验证工具的完整参考页面在线查看:安全工具概述
注意:当证书是自签名时,应用程序的发布者将显示为UNKNOWN
。有关更多信息,请参阅从列为UNKNOWN
的发布者运行应用程序是否安全?。
验证已签名的 JAR 文件
原文:
docs.oracle.com/javase/tutorial/deployment/jar/verify.html
通常,验证已签名的 JAR 文件将由您的 Java™运行环境负责。您的浏览器将验证下载的已签名小程序。使用解释器的-jar
选项调用的已签名应用程序将由运行环境验证。
然而,您可以使用jarsigner
工具自行验证已签名的 JAR 文件。例如,您可能想要这样做来测试您准备的已签名 JAR 文件。
用于验证已签名的 JAR 文件的基本命令是:
jarsigner -verify *jar-file*
这个命令将验证 JAR 文件的签名,并确保存档中的文件在签名后没有发生变化。如果验证成功,您将看到以下消息:
jar verified.
如果您尝试验证一个未签名的 JAR 文件,将会出现以下消息:
jar is unsigned. (signatures missing or not parsable)
如果验证失败,将显示相应的消息。例如,如果 JAR 文件的内容自签名以来发生了变化,当您尝试验证文件时,将会出现类似以下消息:
jarsigner: java.lang.SecurityException: invalid SHA1
signature file digest for test/classes/Manifest.class
注意: 如果已签名的 JAR 文件使用了jdk.jar.disabledAlgorithms
安全属性中指定的任何算法(其中*java.home*
是您安装 JRE 的目录),JDK 将把已签名的 JAR 文件视为未签名。
使用与 JAR 相关的 API
原文:
docs.oracle.com/javase/tutorial/deployment/jar/apiindex.html
Java 平台包含几个用于处理 JAR 文件的类。其中一些 API 包括:
为了让您了解这些新 API 所打开的可能性,本课程将引导您了解一个名为 JarRunner 的示例应用程序的内部工作原理。
一个示例 - JarRunner 应用程序
JarRunner 允许您通过在命令行上指定 JAR 文件的 URL 来运行打包在 JAR 文件中的应用程序。例如,如果一个名为TargetApp
的应用程序被打包在http://www.example.com/TargetApp.jar
的 JAR 文件中,您可以使用以下命令运行该应用程序:
java JarRunner http://www.example.com/TargetApp.jar
为了使JarRunner
正常工作,它必须能够执行以下任务,所有这些任务都是通过使用新的 API 完成的:
-
访问远程 JAR 文件并与其建立通信链接。
-
检查 JAR 文件的清单,查看存档中哪个类是主类。
-
加载 JAR 文件中的类。
JarRunner
应用程序由两个类组成,JarRunner
和JarClassLoader
。JarRunner
将大部分 JAR 处理任务委托给JarClassLoader
类。JarClassLoader
扩展了java.net.URLClassLoader
类。在继续本课程之前,您可以查看JarRunner
和JarClassLoader
类的源代码:
-
JarRunner.java
-
JarClassLoader.java
本课程分为两部分:
JarClassLoader 类
本节展示了JarClassLoader
如何使用一些新的 API 来执行 JarRunner 应用程序所需的任务。
JarRunner 类
本节总结了包含JarRunner
应用程序的JarRunner
类。
JarClassLoader
类
原文:
docs.oracle.com/javase/tutorial/deployment/jar/jarclassloader.html
JarClassLoader
类扩展了java.net.URLClassLoader
。顾名思义,URLClassLoader
被设计用于加载通过搜索一组 URL 访问的类和资源。这些 URL 可以是目录或 JAR 文件。
除了继承URLClassLoader
,JarClassLoader
还利用了另外两个新的与 JAR 相关的 API 特性,即java.util.jar
包和java.net.JarURLConnection
类。在本节中,我们将详细查看JarClassLoader
的构造函数和两个方法。
JarClassLoader
构造函数
构造函数以java.net.URL
的实例作为参数。传递给这个构造函数的 URL 将在JarClassLoader
的其他地方使用,以找到要加载类的 JAR 文件。
public JarClassLoader(URL url) {
super(new URL[] { url });
this.url = url;
}
URL
对象被传递给父类URLClassLoader
的构造函数,该构造函数接受一个URL[]
数组作为参数,而不是单个URL
实例。
getMainClassName
方法
一旦使用 JAR 捆绑应用程序的 URL 构造了一个JarClassLoader
对象,它将需要一种方法来确定 JAR 文件中哪个类是应用程序的入口点。这就是getMainClassName
方法的工作:
public String getMainClassName() throws IOException {
URL u = new URL("jar", "", url + "!/");
JarURLConnection uc = (JarURLConnection)u.openConnection();
Attributes attr = uc.getMainAttributes();
return attr != null
? attr.getValue(Attributes.Name.MAIN_CLASS)
: null;
}
你可能还记得之前的一节课中提到过,JAR 捆绑应用程序的入口点是由 JAR 文件清单的Main-Class
头部指定的。为了理解getMainClassName
如何访问Main-Class
头部的值,让我们详细看一下这个方法,特别关注它使用的新的 JAR 处理特性:
JarURLConnection
类和 JAR URL
getMainClassName
方法使用了java.net.JarURLConnection
类指定的 JAR URL 格式。JAR 文件的 URL 语法如下例所示:
jar:http://www.example.com/jarfile.jar!/
终止的!/
分隔符表示 URL 指向整个 JAR 文件。分隔符后面的任何内容都指向特定的 JAR 文件内容,就像这个例子中一样:
jar:http://www.example.com/jarfile.jar!/mypackage/myclass.class
getMainClassName
方法中的第一行是:
URL u = new URL("jar", "", url + "!/");
这个语句构造了一个新的URL
对象,表示一个 JAR URL,将JarClassLoader
实例创建时使用的 URL 后面添加了!/
分隔符。
java.net.JarURLConnection
类
这个类表示应用程序和 JAR 文件之间的通信链接。它有用于访问 JAR 文件清单的方法。getMainClassName
的第二行是:
JarURLConnection uc = (JarURLConnection)u.openConnection();
在这个语句中,第一行创建的URL
实例打开了一个URLConnection
。然后将URLConnection
实例转换为JarURLConnection
,以便利用JarURLConnection
的 JAR 处理特性。
获取清单属性:java.util.jar.Attributes
通过打开到 JAR 文件的JarURLConnection
,您可以使用JarURLConnection
的getMainAttributes
方法访问 JAR 文件 MANIFEST 中的头信息。此方法返回一个java.util.jar.Attributes
的实例,该类将 JAR 文件 MANIFEST 中的头名称与其关联的字符串值进行映射。getMainClassName
中的第三行创建了一个Attributes
对象:
Attributes attr = uc.getMainAttributes();
要获取 MANIFEST 的Main-Class
头的值,getMainClassName
的第四行调用了Attributes.getValue
方法:
return attr != null
? attr.getValue(Attributes.Name.MAIN_CLASS)
: null;
方法的参数Attributes.Name.MAIN_CLASS
指定了您想要的Main-Class
头的值。(Attributes.Name
类还提供了静态字段,如MANIFEST_VERSION
、CLASS_PATH
和SEALED
,用于指定其他标准 MANIFEST 头。)
invokeClass 方法
我们已经看到JarURLClassLoader
如何在打包为 JAR 的应用程序中识别主类。要考虑的最后一个方法是JarURLClassLoader.invokeClass
,它使得可以调用主类来启动打包为 JAR 的应用程序:
public void invokeClass(String name, String[] args)
throws ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException
{
Class c = loadClass(name);
Method m = c.getMethod("main", new Class[] { args.getClass() });
m.setAccessible(true);
int mods = m.getModifiers();
if (m.getReturnType() != void.class || !Modifier.isStatic(mods) ||
!Modifier.isPublic(mods)) {
throw new NoSuchMethodException("main");
}
try {
m.invoke(null, new Object[] { args });
} catch (IllegalAccessException e) {
// This should not happen, as we have disabled access checks
}
}
invokeClass
方法接受两个参数:应用程序入口点类的名称和要传递给入口点类的main
方法的字符串参数数组。首先,加载主类:
Class c = loadClass(name);
loadClass
方法是从java.lang.ClassLoader
继承的。
一旦主类加载完成,就会使用java.lang.reflect
包的反射 API 将参数传递给类并启动它。您可以参考反射 API 上的教程来复习反射。
JarRunner
类
原文:
docs.oracle.com/javase/tutorial/deployment/jar/jarrunner.html
JarRunner
应用程序是以这种形式启动的:
java JarRunner *url [arguments]*
在前一节中,我们已经看到JarClassLoader
如何能够从给定的 URL 标识和加载 JAR 捆绑应用程序的主类。因此,要完成JarRunner
应用程序,我们需要能够从命令行获取 URL 和任何参数,并将它们传递给JarClassLoader
的实例。这些任务属于JarRunner
类,是JarRunner
应用程序的入口点。
它首先通过命令行指定的 URL 创建一个java.net.URL
对象:
public static void main(String[] args) {
if (args.length < 1) {
usage();
}
URL url = null;
try {
url = new URL(args[0]);
} catch (MalformedURLException e) {
fatal("Invalid URL: " + args[0]);
}
如果args.length < 1
,这意味着在命令行中没有指定 URL,因此会打印出使用消息。如果第一个命令行参数是一个有效的 URL,则会创建一个新的URL
对象来表示它。
接下来,JarRunner
创建了一个新的JarClassLoader
实例,并将在命令行中指定的 URL 传递给构造函数:
JarClassLoader cl = new JarClassLoader(url);
正如我们在前一节中看到的,通过JarClassLoader
,JarRunner
利用 JAR 处理 API。
传递给JarClassLoader
构造函数的 URL 是您想要运行的 JAR 捆绑应用程序的 URL。JarRunner
接下来调用类加载器的getMainClassName
方法来识别应用程序的入口点类:
String name = null;
try {
name = cl.getMainClassName();
} catch (IOException e) {
System.err.println("I/O error while loading JAR file:");
e.printStackTrace();
System.exit(1);
}
if (name == null) {
fatal("Specified jar file does not contain a 'Main-Class'" +
" manifest attribute");
}
关键语句已用粗体标出。其他语句用于错误处理。
一旦JarRunner
确定了应用程序的入口点类,只剩下两个步骤:将任何参数传递给应用程序并实际启动应用程序。JarRunner
使用以下代码执行这些步骤:
// Get arguments for the application
String[] newArgs = new String[args.length - 1];
System.arraycopy(args, 1, newArgs, 0, newArgs.length);
// Invoke application's main class
try {
cl.invokeClass(name, newArgs);
} catch (ClassNotFoundException e) {
fatal("Class not found: " + name);
} catch (NoSuchMethodException e) {
fatal("Class does not define a 'main' method: " + name);
} catch (InvocationTargetException e) {
e.getTargetException().printStackTrace();
System.exit(1);
}
回想一下,第一个命令行参数是 JAR 捆绑应用程序的 URL。因此,要传递给该应用程序的任何参数都在args
数组中的元素1
及以后。JarRunner
获取这些元素,并创建一个名为newArgs
的新数组传递给应用程序(上面的粗体行)。然后,JarRunner
将入口点的类名和新参数列表传递给JarClassLoader
的invokeClass
方法。正如我们在前一节中看到的,invokeClass
将加载应用程序的入口点类,传递任何参数,并启动应用程序。
问题和练习:JAR
原文:
docs.oracle.com/javase/tutorial/deployment/jar/QandE/questions.html
问题
-
如何调用打包为 JAR 文件的小程序?
-
-e
选项在jar
命令中的目的是什么? -
JAR 文件中 MANIFEST 的重要性是什么?
-
如何修改 JAR 的 MANIFEST 文件?
检查你的答案。
教程:使用 Swing 创建 GUI
也被称为Swing 教程
本教程告诉您如何为应用程序和小程序创建图形用户界面(GUI),使用 Swing 组件。如果您想将 JavaFX 集成到您的 Swing 应用程序中,请参阅将 JavaFX 集成到 Swing 应用程序中。
是一个快速入门课程。首先它为您提供了关于 Swing 的一些背景知识。然后告诉您如何编译和运行使用 Swing 组件的程序。
是开始使用 Swing 的最快最简单的方法。本课程探讨了 NetBeans IDE 的 GUI 构建器,这是一个强大的功能,让您可以直观地构建您的图形用户界面。
告诉您如何使用 Swing 组件中的每个部分 按钮、表格、文本组件等等。它还告诉您如何使用边框和图标。
讨论了并发如何应用于 Swing 编程。包括有关事件分派线程和 SwingWorker 类的信息。
告诉您如何使用动作、定时器和系统托盘;如何与桌面类集成,如何支持辅助技术,如何打印表格和文本,如何创建启动画面,以及如何在对话框中使用模态。
告诉您如何选择布局管理器,如何使用 Java 平台提供的每个布局管理器类,如何使用绝对定位而不是布局管理器,以及如何创建自己的布局管理器。
告诉您如何指定 Swing 组件的外观和感觉。
告诉您实现应用程序中数据传输所需的知识。
告诉您如何在程序中处理事件。
提供了关于绘制自己的 Swing 组件的信息。它讨论了特定于 Swing 组件的绘制问题,概述了绘制概念,并提供了自定义组件自行绘制的示例。
其他与 UI 相关的路径
虽然这是学习 GUI 的主要路径,但并不是唯一涉及 UI 相关信息的路径。
-
2D 图形,描述了 JDK 中可用的 2D 图形功能。
-
声音,讨论了 JDK 中可用的声音功能。
-
Java Applets,描述了仅适用于小程序的 API。
-
基本 Java 类,涵盖了许多主题,包括属性和标准 I/O 流。
-
JavaFX 文档,描述了如何使用 JavaFX 构建 UI。
-
Bonus 路径包含 全屏独占模式 API,这是一个介绍如何使用 v1.4 中引入的 API 直接向屏幕渲染图形的课程。
课程:开始使用 Swing
示例索引
这节课简要介绍了如何使用 Swing。在介绍 Swing 之后,它会带领你编译和运行一个使用 Swing 包的程序。
接下来的课程,使用 NetBeans IDE 学习 Swing,将在这些第一步的基础上构建,帮助你创建几个逐渐复杂的示例。现在,让我们从基础知识开始。
关于 JFC 和 Swing
这部分为你提供了 Swing 的概述。
编译和运行 Swing 程序
这部分为你提供了详细的说明,告诉你从哪里获取最新的 JDK,以及如何创建、编译和运行使用 Swing 组件的程序。
如果你有兴趣使用 JavaFX 包来创建你的 UI,请查看JavaFX 文档。要部署,请参阅Java 平台标准版部署指南。
关于 JFC 和 Swing
JFC 是 Java Foundation Classes 的缩写,它包含了一组用于构建图形用户界面(GUI)和为 Java 应用程序添加丰富的图形功能和交互性的特性。它被定义为包含下表所示的特性。
功能 | 描述 |
---|---|
Swing GUI 组件 | 包括从按钮到分割窗格到表格的所有内容。许多组件能够进行排序、打印和拖放等功能,这只是支持的功能之一。 |
Pluggable Look-and-Feel Support | Swing 应用程序的外观和感觉是可插拔的,允许选择外观和感觉。例如,同一个程序可以使用 Java 或 Windows 外观和感觉。此外,Java 平台支持 GTK+ 外观和感觉,使得数百种现有的外观和感觉可用于 Swing 程序。许多其他外观和感觉包可以从各种来源获取。 |
Accessibility API | 允许辅助技术,如屏幕阅读器和盲文显示器,从用户界面获取信息。 |
Java 2D API | 允许开发人员在应用程序和小程序中轻松地整合高质量的 2D 图形、文本和图像。Java 2D 包括广泛的 API,用于生成和发送高质量的输出到打印设备。 |
国际化 | 允许开发人员构建可以与全球用户以其自己的语言和文化习俗交互的应用程序。借助输入法框架,开发人员可以构建接受使用数千个不同字符的语言(如日语、中文或韩语)的文本的应用程序。 |
本教程集中讨论 Swing 组件。我们将帮助您选择适合您的 GUI 的组件,告诉您如何使用它们,并为您提供使用它们的有效背景信息。我们还将讨论其他与 Swing 组件相关的功能。
我应该使用哪些 Swing 包?
Swing API 强大、灵活 —— 也是庞大的。Swing API 有 18 个公共包:
javax.accessibility |
javax.swing.plaf |
javax.swing.text |
---|---|---|
javax.swing |
javax.swing.plaf.basic |
javax.swing.text.html |
javax.swing.border |
javax.swing.plaf.metal |
javax.swing.text.html.parser |
javax.swing.colorchooser |
javax.swing.plaf.multi |
javax.swing.text.rtf |
javax.swing.event |
javax.swing.plaf.synth |
javax.swing.tree |
javax.swing.filechooser |
javax.swing.table |
javax.swing.undo |
幸运的是,大多数程序只使用 API 的一个小子集。本教程为您整理了 API,为您提供常见代码示例,并指导您可能需要的方法和类。本教程中的大部分代码仅使用一个或两个 Swing 包:
-
javax.swing
-
javax.swing.event
(不总是必需的)
编译和运行 Swing 程序
原文:
docs.oracle.com/javase/tutorial/uiswing/start/compile.html
本节介绍如何从命令行编译和运行一个 Swing 应用程序。有关使用 NetBeans IDE 编译和运行 Swing 应用程序的信息,请参阅在 NetBeans IDE 中运行教程示例。编译说明适用于所有 Swing 程序 包括小程序和应用程序。以下是你需要遵循的步骤:
-
如果你还没有安装最新版本的 Java SE 平台,那么现在就安装吧。
-
创建一个使用 Swing 组件的程序。
-
编译程序。
-
运行程序。
安装最新版本的 Java SE 平台
你可以免费从www.oracle.com/technetwork/java/javase/downloads/index.html
下载 JDK 的最新版本。
创建一个使用 Swing 组件的程序
你可以使用我们提供的一个简单程序,名为 HelloWorldSwing,它会显示如下图所示的 GUI。该程序在一个单独的文件中,HelloWorldSwing.java
。当你保存这个文件时,你必须确保其名称的拼写和大小写完全匹配。
HelloWorldSwing.java
示例,就像我们所有的 Swing 教程示例一样,都是在一个包内创建的。如果你查看源代码,你会在文件开头看到以下行:
package start;
这意味着你必须将HelloWorldSwing.java
文件放在start
目录内。你需要从start
目录的上一级目录编译和运行示例。使用 Swing 组件课程中的教程示例位于components
包内,编写事件监听器课程中的示例位于events
包内,依此类推。更多信息,你可能想看看Packages
课程。
编译程序
你的下一步是编译程序。要编译示例,从HelloWorldSwing.java
文件的上一级目录开始:
javac start/HelloWorldSwing.java
如果你愿意,你可以在start
目录中编译示例:
javac HelloWorldSwing.java
但是你必须记住离开start
目录才能执行程序。
如果你无法编译,请确保你正在使用 Java 平台的最新版本中的编译器。你可以使用以下命令验证你的编译器或 Java 运行环境(JRE)的版本。
javac -version
java -version
更新了 JDK 后,你应该可以在不进行任何更改的情况下使用本教程中的程序。另一个常见错误是安装了 JRE 而不是编译这些程序所需的完整 Java 开发工具包(JDK)。请参考入门指南来帮助你解决遇到的任何编译问题。另一个资源是Java™ SE 6 桌面技术故障排除指南。
运行程序
编译程序成功后,你可以运行它。从start
目录的上一级目录开始:
java start.HelloWorldSwing
课程:使用 NetBeans IDE 学习 Swing
示例索引
本课程介绍了使用 Swing 和 NetBeans IDE 进行图形用户界面(GUI)编程。正如您在"Hello World!"课程中学到的,NetBeans IDE 是一个免费的、开源的、跨平台的集成开发环境,内置支持 Java 编程语言。与使用文本编辑器编码相比,它提供了许多优势;我们建议尽可能使用它。如果您还没有阅读上述课程,请立即花点时间阅读。它提供了有关下载和安装 JDK 和 NetBeans IDE 的宝贵信息。
本课程的目标是通过设计一个简单的应用程序,将温度从摄氏度转换为华氏度来介绍 Swing API。其 GUI 将是基本的,只关注可用的 Swing 组件的子集。我们将使用 NetBeans IDE GUI 构建器,它使用户界面的创建变得简单易行,只需拖放即可。其自动生成代码功能简化了 GUI 开发过程,让您可以专注于应用程序逻辑,而不是底层基础设施。
因为这节课是一个逐步检查特定操作的清单,我们建议您运行 NetBeans IDE,并在阅读过程中执行每个步骤。这将是开始使用 Swing 进行编程的最快最简单的方法。如果您无法这样做,仅仅阅读也应该是有用的,因为每个步骤都有截图说明。
如果您更喜欢传统的手动编程每个组件的方法(不使用 IDE 的帮助),请将这节课视为进入已经在教程其他地方提供的低级讨论的入口点。每个讨论中的超链接将带您前往相关课程,如果您希望学习这些低级细节。
该应用程序的完成 GUI 将如下所示:
CelsiusConverter 应用程序。
单击“启动”按钮以使用Java™ Web Start运行 CelsiusConverter(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
从最终用户的角度来看,使用方法很简单:在文本框中输入温度(摄氏度),点击“转换”按钮,然后观看转换后的温度(华氏度)显示在屏幕上。最小化、最大化和关闭按钮将按预期运行,并且应用程序还将在窗口顶部显示一个标题。
从程序员的角度来看,我们将应用程序分为两个主要阶段。首先,我们将用各种 Swing 组件填充 GUI 并按照上面显示的方式排列它们。然后,我们将添加应用程序逻辑,以便当用户按下“转换”按钮时,程序实际执行转换操作。
如果你有兴趣使用 JavaFX 来创建你的 GUI,请查看JavaFX 文档和JavaFX - NetBeans Wiki。
设置 CelsiusConverter 项目
原文:
docs.oracle.com/javase/tutorial/uiswing/learn/settingup.html
如果您以前使用过 NetBeans IDE,那么本节的大部分内容看起来会很熟悉,因为最初的步骤对大多数项目都是相似的。不过,接下来的步骤描述了特定于此应用程序的设置,因此请务必仔细遵循。
步骤 1:创建一个新项目
要创建一个新项目,请启动 NetBeans IDE 并从“文件”菜单中选择“新项目”:
创建一个新项目
每个命令的键盘快捷键显示在每个菜单项的最右侧。NetBeans IDE 的外观和感觉可能在不同平台上有所不同,但功能将保持不变。
步骤 2:选择 General -> Java Application
接下来,从类别列中选择“General”,从项目列中选择“Java Application”:
*此图已经缩小以适应页面。
点击图像以查看其自然大小。*
您可能会在描述窗格中看到“J2SE”的提及;那是现在被称为“Java SE”平台的旧名称。按下标有“下一步”的按钮继续。
步骤 3:设置项目名称
现在将“CelsiusConverterProject”输入为项目名称。您可以将项目位置和项目文件夹字段保留为默认值,或者单击“浏览”按钮选择系统上的其他位置。
*此图已经缩小以适应页面。
点击图像以查看其自然大小。*
确保取消选中“创建主类”复选框;保留此选项会生成一个新类作为应用程序的主入口点,但我们的主 GUI 窗口(在下一步中创建)将担任此职责,因此不需要选中此框。完成后点击“完成”按钮。
*此图已经缩小以适应页面。
点击图像以查看其自然大小。*
当 IDE 完成加载时,您将看到类似上述的屏幕。所有窗格都将为空,除了左上角的项目窗格,显示了新创建的项目。
步骤 4:添加一个 JFrame 表单
*此图已经缩小以适应页面。
点击图像以查看其自然大小。*
现在右键点击CelsiusConverterProject
名称,选择 New -> JFrame Form(JFrame
是负责应用程序主窗体的 Swing 类)。你将在本课程后面学习如何将这个类指定为应用程序的入口点。
步骤 5:命名 GUI 类
接下来,将CelsiusConverterGUI
作为类名,learn
作为包名。实际上,你可以将这个包命名为任何你想要的名称,但在这里我们遵循教程的惯例,将包命名为所在课程的名称。
*此图已经缩小以适应页面。
点击图片查看其原始大小。*
其余字段应自动填写,如上所示。完成后点击 Finish 按钮。
*此图已经缩小以适应页面。
点击图片查看其原始大小。*
当 IDE 加载完成时,右侧窗格将显示CelsiusConverterGUI
的设计时、图形化视图。在这个屏幕上,你将可以直观地拖放和操作各种 Swing 组件。
NetBeans IDE 基础知识
原文:
docs.oracle.com/javase/tutorial/uiswing/learn/netbeansbasics.html
在探索 NetBeans IDE 的 GUI 创建功能之前,不需要学习其每个功能。实际上,您真正需要理解的只有调色板、设计区域、属性编辑器和检查器。我们将在下面讨论这些功能。
调色板
调色板包含 Swing API 提供的所有组件。即使这是您第一次使用它们(JLabel
是文本标签,JList
是下拉列表等),您也可能已经猜到许多这些组件的用途。
*此图已经缩小以适应页面。
单击图像以查看其自然大小。*
从此列表中,我们的应用程序仅使用JLabel
(基本文本标签)、JTextField
(用户输入温度)和JButton
(将温度从摄氏度转换为华氏度)。
设计区域
设计区域是您将视觉构建 GUI 的地方。它有两个视图:源视图和设计视图。设计视图是默认视图,如下所示。您可以随时通过单击它们各自的选项卡切换视图。
*此图已经缩小以适应页面。
单击图像以查看其自然大小。*
上图显示了一个JFrame
对象,由带有蓝色边框的大阴影矩形表示。IDE 自动生成常见的预期行为(例如在用户单击“关闭”按钮时退出)并显示在源代码视图中,位于称为受保护块的不可编辑蓝色代码块之间。
*此图已经缩小以适应页面。
单击图像以查看其自然大小。*
从源代码视图快速查看可以发现,IDE 创建了一个名为initComponents
的私有方法,用于初始化 GUI 的各个组件。它还告诉应用程序在关闭时“退出”,执行一些特定于布局的任务,然后将(即将添加的)组件打包在屏幕上。
不必详细了解此代码;我们在这里提到它只是为了探索源选项卡。有关这些组件的更多信息,请参见:
如何创建框架(主窗口) 和 在容器内布局组件。
属性编辑器
属性编辑器的功能如其名称所示:它允许您编辑每个组件的属性。属性编辑器易于使用;您将看到一系列行 - 每个属性一行 - 您可以单击并编辑,而无需直接输入源代码。以下图显示了新添加的JFrame
对象的属性编辑器:
*此图已缩小以适应页面。
单击图像以查看其自然大小。
上面的截图显示了此对象的各种属性,如背景颜色、前景颜色、字体和光标。
检查器
在本课程中我们将使用 NetBeans IDE 的最后一个组件是检查器:
检查器
检查器提供了应用程序组件的图形表示。我们只会使用检查器一次,将一些变量名称更改为非默认名称。
创建 CelsiusConverter GUI
原文:
docs.oracle.com/javase/tutorial/uiswing/learn/creatinggui.html
本节介绍如何使用 NetBeans IDE 创建应用程序的 GUI。当您将每个组件从工具栏拖动到设计区域时,IDE 会自动生成适当的源代码。
步骤 1:设置标题
首先,通过在检查器中单击JFrame
来将应用程序的JFrame
的标题设置为“摄氏度转换器”:
选择 JFrame
然后,使用属性编辑器设置其标题:
设置标题
您可以通过双击标题属性并直接输入新文本,或单击按钮并在提供的字段中输入标题来设置标题。或者,作为快捷方式,您可以单击检查器中的JFrame
并直接输入其新文本,而无需使用属性编辑器。
步骤 2:添加一个 JTextField
接下来,从工具栏拖动一个JTextField
到设计区域的左上角。当接近左上角时,GUI 构建器会提供视觉线索(虚线),提示适当的间距。根据这些线索指导,将JTextField
放置在窗口的左上角,如下所示:
添加一个 JTextField
您可能会想要删除默认文本“JTextField1”,但现在就让它保持原样。稍后在本课程中,随着对每个组件的最终调整,我们将替换它。有关此组件的更多信息,请参见如何使用文本字段。
步骤 3:添加一个 JLabel
接下来,在设计区域拖动一个JLabel
。将其放置在JTextField
的右侧,再次注意视觉线索,以确定适当的间距。确保此组件的文本基线与JTextField
的文本基线对齐。IDE 提供的视觉线索应该很容易确定。
添加一个 JLabel
有关此组件的更多信息,请参见如何使用标签。
步骤 4:添加一个 JButton
接下来,从工具栏拖动一个JButton
并将其放置在JTextField
的左侧和下方。再次,视觉线索有助于将其放置在正确的位置。
添加一个 JButton
你可能会想要手动调整JButton
和JTextField
的宽度,但现在就让它们保持原样。稍后在本课程中,您将学习如何正确调整这些组件。有关此组件的更多信息,请参见如何使用按钮。
步骤 5:添加第二个 JLabel
添加第二个 JLabel
最后,添加第二个JLabel
,重复第 2 步中的过程。将这第二个标签放置在JButton
的右侧,如上所示。
调整 CelsiusConverter GUI
原文:
docs.oracle.com/javase/tutorial/uiswing/learn/adjustinggui.html
现在,GUI 组件已经就位,是时候进行最后的调整了。有几种不同的方法可以做到这一点;这里建议的顺序只是一种可能的方法。
步骤 1:设置组件文本
首先,双击JTextField
和JButton
以更改 IDE 插入的默认文本。当你从JTextField
中擦除文本时,它会如下所示地缩小。将JButton
的文本从“JButton1”更改为“Convert”。还将顶部JLabel
的文本更改为“Celsius”,底部更改为“Fahrenheit”。
设置组件文本
步骤 2:设置组件大小
接下来,按住JTextField
和JButton
组件并进行 shift 点击。这将突出显示它们各自被选中。右键点击(Mac 用户为 control 点击)选择“相同大小” -> “相同宽度”。现在,这些组件的宽度将相同,如下所示。在执行此步骤时,请确保JFrame
本身没有被选中。如果选中了,相同大小菜单将不可用。
设置 JTextField 和 JButton 大小
步骤 3:移除额外空间
最后,拖动JFrame
的右下角并调整其大小以消除任何额外的空白。请注意,如果消除了所有额外的空间(如下所示),标题(仅在运行时显示)可能不会完全显示。最终用户可以根据需要调整应用程序的大小,但你可能希望在右侧留一些额外的空间,以确保一切都正确地适合。尝试一下,并使用完成 GUI 的截图作为指南。
完成的 GUI
此应用程序的 GUI 部分现在已经完成!如果 NetBeans IDE 完成了它的工作,你应该觉得创建这个 GUI 是一个简单的,甚至是微不足道的任务。但是花点时间点击源选项卡;你可能会对生成的代码量感到惊讶。
*此图已经缩小以适应页面。
点击图像以查看其自然大小。*
要查看完整的代码,请根据需要在 IDE 中上下滚动。你可以通过点击源编辑器左侧的 + 或 - 符号来展开或折叠某些代码块(如方法体)。
添加应用程序逻辑
现在是时候添加应用程序逻辑了。
步骤 1:更改默认变量名称
下图显示了当前在检查器中显示的默认变量名称。对于每个组件,变量名称首先出现,然后是方括号中的对象类型。例如,jTextField1 [JTextField]
表示“jTextField1”是变量名称,“JTextField”是其类型。
默认变量名称
在此应用程序的上下文中,默认名称并不是非常相关,因此将它们从默认值更改为更有意义的内容是有意义的。右键单击每个变量名称,选择“更改变量名称”。完成后,变量名称应如下所示:
新的变量名称
新的变量名称是“tempTextField”、“celsiusLabel”、“convertButton”和“fahrenheitLabel”。您在检查器中进行的每个更改都将自动传播回源代码中。您可以放心,编译不会因手动编辑时常见的拼写错误或类似错误而失败。
步骤 2:注册事件监听器
当最终用户与 Swing GUI 组件交互(例如单击“Convert”按钮)时,该组件将生成一种特殊类型的对象 —— 称为事件对象 —— 然后将其广播到先前注册为该事件的监听器的任何其他对象。NetBeans IDE 使事件监听器注册变得非常简单:
*此图已经缩小以适应页面。
单击图像以查看其自然大小。*
在设计区域中,单击“Convert”按钮以选择它。确保只有“Convert”按钮被选中(如果 JFrame 本身也被选中,则此步骤将无效)。右键单击“Convert”按钮,选择事件 -> 动作 -> actionPerformed。这将生成所需的事件处理代码,使您可以在其中添加自己的功能的空方法体:
*此图已经缩小以适应页面。
单击图像以查看其自然大小。*
有许多不同类型的事件,代表着用户可以执行的各种操作(点击鼠标会触发一种类型的事件,键盘输入会触发另一种,移动鼠标则会触发另一种,依此类推)。我们的应用程序只关注ActionEvent;有关事件处理的更多信息,请参阅编写事件监听器。
步骤 3:添加温度转换代码
最后一步只需将温度转换代码粘贴到空方法体中。以下代码是将温度从摄氏度转换为华氏度所需的全部内容:
注意:
此示例不可本地化,因为parseDouble
方法不可本地化。此代码片段仅用于说明目的。更健壮的实现将使用Scanner类来解析用户输入。
//Parse degrees Celsius as a double and convert to Fahrenheit.
int tempFahr = (int)((Double.parseDouble(tempTextField.getText()))
* 1.8 + 32);
fahrenheitLabel.setText(tempFahr + " Fahrenheit");
只需复制此代码并将其粘贴到下面所示的convertButtonActionPerformed
方法中:
*此图已经缩小以适应页面。
点击图像以查看其自然大小。*
完成温度转换代码后,应用程序现在已经完成。
步骤 4:运行应用程序
运行应用程序只是在 NetBeans IDE 中选择运行 -> 运行主项目的简单事情。第一次运行此应用程序时,会提示您设置CelsiusConverterGUI
为此项目的主类。点击“确定”按钮,当程序编译完成后,您应该会看到应用程序在自己的窗口中运行。
恭喜!您已经完成了您的第一个 Swing 应用程序!
问题和练习:使用 NetBeans IDE 学习 Swing
原文:
docs.oracle.com/javase/tutorial/uiswing/QandE/questions-learn.html
问题
-
在创建这个新项目时,我们取消了“创建主类”复选框的选择。为什么?
-
_____ 包含了 Swing API 提供的所有组件。
-
设计区域的目的是什么?
-
解释源代码和设计标签之间的区别。
-
列举一些使用属性编辑器编辑对象的优势(与直接在源代码中编辑相比)。
-
这节课使用了三种不同类型的 Swing 对象。它们是什么?
-
描述在设计区域中设置两个组件宽度相同的过程。
练习
- 使用属性编辑器来改变这个应用程序的外观。尝试改变一些数值,看看你的改变有什么效果。
检查你的答案
教程:使用 Swing 组件
原文:
docs.oracle.com/javase/tutorial/uiswing/components/index.html
示例索引
本课程为您提供了使用 Swing 组件所需的背景信息,然后描述了每个 Swing 组件。它假定您已成功编译和运行了使用 Swing 组件的程序,并且熟悉基本的 Swing 概念。这些先决条件在开始使用 Swing 和使用 NetBeans IDE 学习 Swing 中有所涵盖。
使用顶层容器
讨论了如何使用JFrame
、JDialog
和JApplet
类共享的功能——内容窗格、菜单栏和根窗格。还讨论了包含层次结构,指的是顶层容器包含的组件树。
JComponent 类
告诉你JComponent
为其子类(几乎所有 Swing 组件)提供的功能,并提供如何利用这些功能的提示。本节以 API 表结束,描述了JComponent
及其超类Container
和Component
定义的常用 API。
使用文本组件
描述了所有从JTextComponent
继承的组件共享的功能和 API。如果您只是使用文本字段(格式化或非格式化)或文本区域,您可能不需要阅读本节。
如何...
按字母顺序列出如何使用每个 Swing 组件的部分。我们不希望您按顺序阅读这些部分。相反,我们建议在准备开始在自己的程序中使用 Swing 组件时阅读相关的“如何”部分。例如,如果您的程序需要一个框架、一个标签、一个按钮和一个颜色选择器,您应该阅读如何制作框架、如何使用标签、如何使用按钮和如何使用颜色选择器。
在 Swing 组件中使用 HTML
描述了如何通过使用 HTML 标签来改变 Swing 组件显示的文本的字体、颜色或其他格式。
使用模型
告诉您关于 Swing 模型架构。这种 Model-View-Controller(MVC)的变体意味着您可以(如果愿意)指定 Swing 组件的数据和状态如何存储和检索。其好处是能够在组件之间共享数据和状态,并大大提高显示大量数据的表格等组件的性能。
使用边框
边框非常方便,用于在组件的边缘周围绘制线条、标题和空白空间。(您可能已经注意到,本教程中的示例使用了许多边框。)本节告诉您如何向任何JComponent
添加边框。
使用图标
许多 Swing 组件可以显示图标。通常,图标是作为ImageIcon
类的实例实现的。
解决常见组件问题
本节讨论了常见组件相关问题的解决方案。
如果你对使用 JavaFX 创建 GUI 感兴趣,请参阅使用 JavaFX 图表和使用 JavaFX UI 控件。
使用顶级容器
原文:
docs.oracle.com/javase/tutorial/uiswing/components/toplevel.html
正如我们之前提到的,Swing 提供了三个通常有用的顶级容器类:JFrame
、JDialog
和JApplet
。在使用这些类时,您应该记住以下事实:
-
要在屏幕上显示,每个 GUI 组件必须是包含层次结构的一部分。包含层次结构是一个组件树,其根是一个顶级容器。我们稍后会展示给你。
-
每个 GUI 组件只能被包含一次。如果一个组件已经在一个容器中,并且您尝试将其添加到另一个容器中,该组件将从第一个容器中移除,然后添加到第二个容器中。
-
每个顶级容器都有一个内容窗格,一般来说,内容窗格包含(直接或间接地)该顶级容器 GUI 中可见的组件。
-
您可以选择向顶级容器添加一个菜单栏。按照惯例,菜单栏位于顶级容器内部,但在内容窗格之外。一些外观和感觉,如 Mac OS 外观和感觉,可以让您选择将菜单栏放在更适合外观和感觉的其他位置,例如屏幕顶部。
注意:虽然JInternalFrame
模仿JFrame
,但内部框架实际上不是顶级容器。
这是一个应用程序创建的框架的图片。框架包含一个绿色的菜单栏(没有菜单),在框架的内容窗格中有一个大的空白黄色标签。
您可以在TopLevelDemo.java
中找到此示例的完整源代码。虽然示例在独立应用程序中使用了JFrame
,但相同的概念也适用于JApplet
和JDialog
。
这是此示例 GUI 的包含层次结构:
如椭圆形所示,我们在这个图表中略去了一些细节。我们稍后会透露这些缺失的细节。这部分讨论以下主题:
-
顶级容器和包含层次结构
-
向内容窗格添加组件
-
添加菜单栏
-
根窗格(又名缺失的细节)
顶级容器和包含层次结构
使用 Swing 组件的每个程序至少有一个顶级容器。这个顶级容器是包含层次结构的根 - 包含所有出现在顶级容器内部的 Swing 组件的层次结构。
通常情况下,具有基于 Swing 的 GUI 的独立应用程序至少有一个以 JFrame
为根的包含层次结构。例如,如果一个应用程序有一个主窗口和两个对话框,则该应用程序有三个包含层次结构,因此有三个顶级容器。一个包含层次结构以 JFrame
为根,另外两个分别以 JDialog
对象为根。
一个基于 Swing 的小程序至少有一个包含层次结构,其中有一个由 JApplet
对象作为根。例如,弹出对话框的小程序有两个包含层次结构。浏览器窗口中的组件位于由 JApplet
对象作为根的包含层次结构中。对话框有一个由 JDialog
对象作为根的包含层次结构。
向内容窗格添加组件
以下是前面示例中使用的代码,用于获取框架的内容窗格并将黄色标签添加到其中:
frame.getContentPane().add(yellowLabel, BorderLayout.CENTER);
如代码所示,您可以通过调用 getContentPane
方法找到顶级容器的内容窗格。默认内容窗格是一个简单的中间容器,继承自 JComponent
,并使用 BorderLayout
作为其布局管理器。
定制内容窗格很容易例如设置布局管理器或添加边框。但是,有一个小问题。getContentPane
方法返回的是一个 Container
对象,而不是 JComponent
对象。这意味着如果您想利用内容窗格的 JComponent
功能,您需要要么对返回值进行类型转换,要么创建自己的组件作为内容窗格。我们的示例通常采用第二种方法,因为这样更清晰。我们有时采用的另一种方法是简单地向内容窗格添加一个定制组件,完全覆盖内容窗格。
请注意,JPanel
的默认布局管理器是 FlowLayout
;您可能需要更改它。
要将组件设置为内容窗格,请使用顶级容器的 setContentPane
方法。例如:
//Create a panel and add components to it.
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(*someBorder*);
contentPane.add(*someComponent*, BorderLayout.CENTER);
contentPane.add(*anotherComponent*, BorderLayout.PAGE_END);
*topLevelContainer*.setContentPane(contentPane);
注意:
为方便起见,add
方法及其变体,remove
和 setLayout
已被重写以根据需要转发到 contentPane
。这意味着您可以编写
frame.add(child);
并且子组件将被添加到 contentPane
。
请注意,只有这三种方法可以实现这一点。这意味着 getLayout()
不会返回使用 setLayout()
设置的布局。
添加菜单栏
理论上,所有顶级容器都可以容纳菜单栏。然而,在实践中,菜单栏通常只出现在框架和小程序中。要向顶级容器添加菜单栏,请创建一个 JMenuBar
对象,填充它的菜单,然后调用 setJMenuBar
。TopLevelDemo
使用以下代码向其框架添加菜单栏:
frame.setJMenuBar(greenMenuBar);
欲了解更多关于实现菜单和菜单栏的信息,请参阅如何使用菜单。
根窗格
每个顶层容器依赖于一个名为根窗格的隐秘中间容器。根窗格管理内容窗格和菜单栏,以及其他几个容器。通常情况下,你不需要了解根窗格来使用 Swing 组件。然而,如果你需要拦截鼠标点击或在多个组件上绘制,你应该熟悉根窗格。
这里是根窗格为框架(以及每个其他顶层容器)提供的组件列表:
我们已经告诉过你关于内容窗格和可选菜单栏。根窗格添加的另外两个组件是分层窗格和玻璃窗格。分层窗格包含菜单栏和内容窗格,并且可以对其他组件进行 Z 轴排序。玻璃窗格通常用于拦截发生在顶层容器上的输入事件,并且还可以用于在多个组件上绘制。
更多详情,请参见如何使用根窗格。
JComponent 类
原文:
docs.oracle.com/javase/tutorial/uiswing/components/jcomponent.html
除了顶级容器之外,所有以"J"开头命名的 Swing 组件都是从JComponent
类继承的。例如,JPanel
、JScrollPane
、JButton
和JTable
都继承自JComponent
。但是,JFrame
和JDialog
不是因为它们实现了顶级容器。
JComponent
类扩展了Container
类,而Container
类本身又扩展了Component
类。Component
类包括从提供布局提示到支持绘制和事件的所有内容。Container
类支持向容器添加组件和对它们进行布局。本节的 API 表格总结了Component
、Container
和JComponent
最常用的方法。
JComponent 特性
JComponent
类为其子类提供以下功能:
-
工具提示
-
绘制和边框
-
应用程序范围内可插拔的外观和感觉
-
自定义属性
-
布局支持
-
支持辅助功能
-
拖放支持
-
双缓冲
-
键绑定
工具提示
通过使用setToolTipText
方法指定一个字符串,您可以为组件的用户提供帮助。当光标停留在组件上时,指定的字符串将显示在靠近组件的小窗口中。更多信息请参见如何使用工具提示。
绘制和边框
setBorder
方法允许您指定组件显示在边缘周围的边框。要绘制组件的内部,请重写paintComponent
方法。有关详细信息,请参见如何使用边框和执行自定义绘制。
应用程序范围内可插拔的外观和感觉
在幕后,每个JComponent
对象都有一个对应的ComponentUI
对象,负责执行所有绘图、事件处理、大小确定等工作。使用哪个ComponentUI
对象取决于当前的外观和感觉,您可以使用UIManager.setLookAndFeel
方法进行设置。详细信息请参见如何设置外观和感觉。
自定义属性
您可以将一个或多个属性(名称/对象对)与任何JComponent
关联。例如,布局管理器可以使用属性将约束对象与其管理的每个JComponent
关联起来。您可以使用putClientProperty
和getClientProperty
方法来放置和获取属性。有关属性的一般信息,请参阅属性。
布局支持
虽然Component
类提供了布局提示方法,如getPreferredSize
和getAlignmentX
,但除了创建子类并覆盖这些方法之外,它并没有提供任何设置这些布局提示的方法。为了提供另一种设置布局提示的方法,JComponent
类添加了设置器方法 - setMinimumSize
、setMaximumSize
、setAlignmentX
和setAlignmentY
。详情请参阅在容器中布局组件。
辅助功能支持
JComponent
类提供了 API 和基本功能,以帮助辅助技术(如屏幕阅读器)从 Swing 组件中获取信息。有关辅助功能的更多信息,请参阅如何支持辅助技术。
拖放支持
JComponent
类提供了 API 来设置组件的传输处理程序,这是 Swing 拖放支持的基础。详情请参阅拖放简介。
双缓冲
双缓冲平滑屏幕绘制。详情请参阅执行自定义绘制。
按键绑定
此功能使组件在用户按下键盘上的键时做出反应。例如,在许多外观中,当按钮获得焦点时,按下空格键等同于鼠标单击按钮。外观会自动设置按下和释放空格键之间的绑定以及按钮上的结果效果。有关按键绑定的更多信息,请参阅如何使用按键绑定。
JComponent API
JComponent
类提供了许多新方法,并继承了许多方法从Component
和Container
。以下表格总结了我们最常使用的方法。
-
自定义组件外观
-
设置和获取组件状态
-
处理事件
-
绘制组件
-
处理包含关系
-
组件布局
-
获取大小和位置信息
-
指定绝对大小和位置
自定义组件外观
方法 | 目的 |
---|---|
void setBorder(Border) Border getBorder() | 设置或获取组件的边框。详情请参阅如何使用边框。 |
void setForeground(Color) void setBackground(Color) | 为组件设置前景色或背景色。前景色通常是组件中绘制文本所使用的颜色。背景色(不出所料)是组件背景区域的颜色,假设组件是不透明的。 |
Color getForeground() Color getBackground() | 获取组件的前景色或背景色。 |
void setOpaque(boolean) boolean isOpaque() | 设置或获取组件是否是不透明的。不透明组件会用其背景颜色填充其背景。 |
void setFont(Font) Font getFont() | 设置或获取组件的字体。如果组件没有设置字体,则返回其父组件的字体。 |
void setCursor(Cursor) Cursor getCursor() | 设置或获取显示在组件及其所有包含组件上的光标(除了具有自己设置光标的子组件)。示例:aPanel.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR)); |
设置和获取组件状态
方法 | 目的 |
---|
| void setComponentPopupMenu(JPopupMenu) | 为这个JComponent
设置JPopupMenu
。UI 负责注册绑定并添加必要的监听器,以便在适当的时候显示JPopupMenu
。JPopupMenu
何时显示取决于外观和感觉:有些可能在鼠标事件上显示它,有些可能启用键绑定。
如果popup
为 null,并且getInheritsPopupMenu
返回true
,那么getComponentPopupMenu
将被委托给父组件。这提供了一种让所有子组件继承父组件的popupmenu
的方法。
void setTransferHandler(TransferHandler) TransferHandler getTransferHandler() | 设置或移除transferHandler 属性。TransferHandler 支持通过剪切、复制或粘贴到/从剪贴板以及拖放来交换数据。有关更多详细信息,请参阅拖放简介。 |
---|---|
void setToolTipText(String) | 设置要显示在工具提示中的文本。有关更多信息,请参阅如何使用工具提示。 |
void setName(String) String getName() | 设置或获取组件的名称。当您需要将文本与不显示文本的组件关联时,这可能很有用。 |
boolean isShowing() | 确定组件是否显示在屏幕上。这意味着组件必须可见,并且必须在一个可见且显示的容器中。 |
void setEnabled(boolean) boolean isEnabled() | 设置或获取组件是否启用。启用的组件可以响应用户输入并生成事件。 |
void setVisible(boolean) boolean isVisible() | 设置或获取组件是否可见。组件最初是可见的,顶层组件除外。 |
处理事件
(详细信息请参阅编写事件侦听器)
方法 | 目的 |
---|
| void addHierarchyListener(hierarchyListener l) void removeHierarchyListener(hierarchyListener l)
| 设置组件 Z 轴顺序(component comp, int index) | 将指定的组件移动到容器中指定的 Z 轴顺序索引位置。
如果组件是其他容器的子组件,则在添加到此容器之前,它将从该容器中移除。此方法与java.awt.Container.add(Component, int)
之间的重要区别在于,此方法在将组件从其先前的容器中移除时,除非必要且允许底层本机窗口系统,否则不会调用组件的removeNotify
。这样,如果组件具有键盘焦点,则在移动到新位置时会保持焦点。
注意: Z 轴顺序决定了组件绘制的顺序。具有最高 Z 轴顺序的组件首先绘制,具有最低 Z 轴顺序的组件最后绘制。在组件重叠的情况下,具有较低 Z 轴顺序的组件会覆盖具有较高 Z 轴顺序的组件。 |
获取组件 Z 轴顺序(component comp) | 返回容器内组件的 Z 轴顺序索引。组件在 Z 轴顺序层次结构中越高,其索引越低。具有最低 Z 轴顺序索引的组件最后绘制,位于所有其他子组件之上。 |
---|
绘画组件
(详见执行自定义绘画)
方法 | 目的 |
---|---|
void 重绘() void 重绘(int, int, int, int) | 请求重绘组件的全部或部分。四个int 参数指定要绘制的矩形的边界(x、y、宽度、高度,按顺序)。 |
void 重绘(Rectangle) | 请求重绘组件内指定区域。 |
void 重新验证() | 请求重新布局组件及其受影响的容器。通常情况下,除非在组件可见后显式更改组件的大小/对齐提示或在其可见后更改包含层次结构,否则不应调用此方法。在revalidate 后始终调用repaint 。 |
void 绘制组件(Graphics) | 绘制组件。重写此方法以实现自定义组件的绘制。 |
处理包含层次结构
(查看使用顶层容器获取更多信息)
方法 | 目的 |
---|
| 添加组件(Component) 添加组件(Component, int)
void 添加(Component, Object) | 将指定组件添加到此容器。此方法的单参数版本将组件添加到容器的末尾。当存在时,int
参数指示新组件在容器中的位置。当存在时,Object
参数为当前布局管理器提供布局约束。 |
| void 移除(int) void 移除(Component)
void 移除全部() | 从此容器中移除一个或全部组件。当存在时,int
参数指示要移除的组件在容器中的位置。 |
获取根窗格() | 获取包含组件的根窗格。 |
---|---|
获取顶层祖先容器 | 获取组件的最顶层容器 一个Window 、Applet ,或者如果组件尚未添加到任何容器中则为 null。 |
获取父容器() | 获取组件的直接容器。 |
获取组件数量() | 获取此容器中的组件数量。 |
获取组件(int) 获取组件(Component[]) | 获取此容器中的一个或所有组件。int 参数表示要获取的组件的位置。 |
获取组件 Z 顺序索引(int) 获取组件 Z 顺序索引(Component[]) | 返回容器内组件的 Z 顺序索引。组件在 Z 顺序层次结构中越高,其索引越低。具有最低 Z 顺序索引的组件最后绘制,位于所有其他子组件之上。 |
组件布局
(查看在容器内布局组件获取更多信息)
方法 | 目的 |
---|
| 设置首选大小(Dimension) 设置最大大小(Dimension)
设置最小尺寸(Dimension) | 设置组件的首选、最大或最小尺寸,以像素为单位。首选尺寸表示组件的最佳尺寸。组件的大小不应大于其最大尺寸,也不应小于其最小尺寸。请注意,这些仅是提示,可能会被某些布局管理器忽略。 |
| 获取首选大小(Dimension) 获取最大大小(Dimension)
获取最小尺寸(Dimension) | 获取组件的首选、最大或最小尺寸,以像素为单位。许多 JComponent 类都有设置器和获取器方法。对于那些没有相应设置器方法的非JComponent
子类,可以通过创建子类并覆盖这些方法来设置组件的首选、最大或最小尺寸。 |
void setAlignmentX(float) void setAlignmentY(float) | 设置组件沿着x-或y-轴的对齐方式。这些值表示组件希望相对于其他组件对齐的方式。该值应为介于 0 和 1 之间的数字,其中 0 表示沿着原点对齐,1 表示与原点最远对齐,0.5 表示居中,依此类推。请注意,这仅仅是提示,某些布局管理器可能会忽略这些值。 |
---|---|
float getAlignmentX() float getAlignmentY() | 获取组件沿着x-或y-轴的对齐方式。对于非JComponent 子类,没有相应的设置方法,您可以通过创建子类并重写这些方法来设置组件的对齐方式。 |
void setLayout(LayoutManager) LayoutManager getLayout() | 设置或获取组件的布局管理器。布局管理器负责调整和定位容器中的组件。 |
void applyComponentOrientation(ComponentOrientation) void setComponentOrientation(ComponentOrientation) | 设置此容器及其中包含的所有组件的ComponentOrientation 属性。有关更多信息,请参见设置容器的方向。 |
获取大小和位置信息
方法 | 目的 |
---|---|
int getWidth() int getHeight() | 获取组件当前以像素为单位的宽度或高度。 |
Dimension getSize() Dimension getSize(Dimension) | 获取组件当前以像素为单位的大小。在使用此方法的单参数版本时,调用者负责创建返回结果的Dimension 实例。 |
int getX() int getY() | 获取组件原点相对于父级左上角以像素为单位的当前x或y坐标。 |
Rectangle getBounds() Rectangle getBounds(Rectangle) | 获取以像素为单位的组件边界。边界指定组件的宽度、高度和相对于其父级的原点。在使用此方法的单参数版本时,调用者负责创建返回结果的Rectangle 实例。 |
| Point getLocation() Point getLocation(Point)
获取组件相对于父级左上角以像素为单位的当前位置。在使用getLocation 方法的单参数版本时,调用者负责创建返回结果的Point 实例。 |
---|
Point getLocationOnScreen() |
Insets getInsets() |
指定绝对大小和位置
(查看不使用布局管理器(绝对定位)获取更多信息)
方法 | 目的 |
---|---|
void setLocation(int, int) void setLocation(Point) | 设置组件的位置,以像素为单位,相对于父级的左上角。两个int 参数按顺序指定x和y。在不使用布局管理器时,使用这些方法来定位组件。 |
void setSize(int, int) void setSize(Dimension) | 设置以像素为单位的组件大小。两个int 参数按顺序指定宽度和高度。在不使用布局管理器时,使用这些方法来调整组件大小。 |
void setBounds(int, int, int, int) void setBounds(Rectangle) | 设置组件相对于父组件左上角的大小和位置(以像素为单位)。四个int 参数按顺序指定x、y、宽度和高度。在不使用布局管理器时,使用这些方法来定位和调整组件的大小。 |
使用文本组件
原文:
docs.oracle.com/javase/tutorial/uiswing/components/text.html
本节提供了在使用 Swing 文本组件时可能需要的背景信息。如果你打算使用未经样式化的文本组件 一个文本字段、密码字段、格式化文本字段或文本区域 请转到其操作页面,仅在必要时返回此处。如果你打算使用样式化文本组件,请参阅如何使用编辑窗格和文本窗格,并阅读本节。如果你不知道需要哪种组件,请继续阅读。
Swing 文本组件显示文本,并可选择允许用户编辑文本。程序需要文本组件来执行从简单任务(输入一个单词并按 Enter 键)到复杂任务(在亚洲语言中显示和编辑带有嵌入图像的样式文本)的各种任务。
Swing 提供了六个文本组件,以及支持各种复杂文本需求的支持类和接口。尽管它们用途和功能不同,但所有 Swing 文本组件都继承自相同的超类JTextComponent
,为文本操作提供了高度可配置和强大的基础。
以下图显示了JTextComponent
的层次结构。
以下图片显示了一个名为TextSamplerDemo
的应用程序,该应用程序使用了每个 Swing 文本组件。
试一试:
-
点击“启动”按钮以使用Java™ Web Start运行 TextSamplerDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
在文本字段中输入一些文本,然后按 Enter 键。在密码字段中也做同样操作。当你按 Enter 键时,字段下方的标签会更新。
-
尝试在格式化文本字段中输入有效和无效日期。请注意,只有日期有效时,当你按 Enter 键时,字段下方的标签才会更新。
-
在文本区域和文本窗格中选择和编辑文本。使用键盘绑定,Ctrl-X、Ctrl-C 和 Ctrl-V,分别进行剪切、复制和粘贴文本。
-
尝试编辑已通过
setEditable
调用而变为不可编辑的编辑窗格中的文本。 -
在文本窗格中查找一个嵌入组件和一个嵌入图标的示例。
TextSamplerDemo
示例以非常基本的方式使用文本组件。以下表格告诉您可以使用每种文本组件做什么。
组 | 描述 | Swing 类 |
---|---|---|
文本控件 | 也简称为文本字段,文本控件只能显示一行可编辑文本。像按钮一样,它们会生成动作事件。使用它们从用户那里获取少量文本信息,并在文本输入完成后执行操作。 | JTextField 及其子类 JPasswordField 和 JFormattedTextField |
普通文本区域 | JTextArea 可以显示多行可编辑文本。虽然文本区域可以显示任何字体的文本,但所有文本都是相同字体。使用文本区域允许用户输入任意长度的未格式化文本或显示未格式化的帮助信息。 |
JTextArea |
| 样式文本区域 | 样式文本组件可以使用多种字体显示可编辑文本。一些样式文本组件允许嵌入图像甚至嵌入组件。样式文本组件是功能强大且多面向的组件,适用于高端需求,并提供比其他文本组件更多的自定义途径。由于它们如此强大和灵活,样式文本组件通常需要更多的初始编程来设置和使用。一个例外是编辑器窗格可以轻松地从 URL 加载格式化文本,这使它们非常适用于显示不可编辑的帮助信息。 | JEditorPane
及其子类
本教程提供了关于JTextComponent
类奠定的基础的信息,并告诉您如何完成一些常见的与文本相关的任务。
要了解更多关于 JavaFX 中文本组件的信息,请参阅在 JavaFX 中使用文本和文本效果和使用 JavaFX UI 控件:文本字段教程。
文本组件功能
原文:
docs.oracle.com/javase/tutorial/uiswing/components/generaltext.html
JTextComponent
类是 Swing 文本组件的基础。该类为其所有后代提供以下可定制功能:
-
一个称为文档的模型,用于管理组件的内容。
-
一个视图,用于在屏幕上显示组件。
-
一个称为编辑器工具包的控制器,用于读取和写入文本,并使用操作实现编辑功能。
-
支持无限的撤销和重做。
-
可插拔的插入符和支持插入符更改侦听器和导航过滤器。
查看名为TextComponentDemo
的示例以探索这些功能。尽管TextComponentDemo
示例包含一个自定义的JTextPane
实例,但本节讨论的功能被所有JTextComponent
子类继承。
上方文本组件是自定义文本窗格。下方文本组件是JTextArea
的一个实例,用作报告对文本窗格内容所做的所有更改的日志。窗口底部的状态行报告选择的位置或插入符的位置,具体取决于是否选择了文本。
试试这个:
-
单击“启动”按钮以使用Java™ Web Start运行 TextComponentDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
-
使用鼠标选择文本并将光标放置在文本窗格中。有关选择和光标的信息显示在窗口底部。
-
通过键盘输入文本。您可以使用键盘上的箭头键或四个 emacs 键绑定(Ctrl-B(向后移动一个字符),Ctrl-F(向前移动一个字符),Ctrl-N(向下移动一行)和 Ctrl-P(向上移动一行))来移动插入符。
-
打开编辑菜单,并使用其菜单项在文本窗格中编辑文本。在窗口底部的文本区域中进行选择。由于文本区域不可编辑,因此只有一些编辑菜单的命令,如复制到剪贴板,才有效。但需要注意的是,该菜单同时适用于文本组件。
-
使用“样式”菜单中的项目将不同的样式应用于文本窗格中的文本。
使用TextComponentDemo
示例作为参考点,本节涵盖以下主题:
-
将文本操作与菜单和按钮关联
-
将文本操作与按键绑定
-
实现撤销和重做
-
概念:关于文档
-
实现文档过滤器
-
监听文档更改
-
监听插入符和选择更改
-
概念:关于编辑工具包
将文本操作与菜单和按钮关联
所有 Swing 文本组件都支持标准的编辑命令,如剪切、复制、粘贴和插入字符。每个编辑命令由一个Action
对象表示和实现。(要了解更多关于操作的信息,请参阅如何使用操作。)操作允许您将命令与 GUI 组件(如菜单项或按钮)关联起来,从而围绕文本组件构建 GUI。
您可以在任何文本组件上调用getActions
方法,以接收包含此组件支持的所有操作的数组。还可以将操作数组加载到HashMap
中,以便您的程序可以按名称检索操作。以下是从TextComponentDemo
示例中获取文本窗格中的操作并将其加载到HashMap
中的代码。
private HashMap<Object, Action> createActionTable(JTextComponent textComponent) {
HashMap<Object, Action> actions = new HashMap<Object, Action>();
Action[] actionsArray = textComponent.getActions();
for (int i = 0; i < actionsArray.length; i++) {
Action a = actionsArray[i];
actions.put(a.getValue(Action.NAME), a);
}
return actions;
}
以下是从哈希映射中按名称检索操作的方法:
private Action getActionByName(String name) {
return actions.get(name);
}
您可以在程序中逐字使用这两种方法。
以下代码显示了如何创建剪切菜单项并将其与从文本组件中删除文本的操作关联起来。
protected JMenu createEditMenu() {
JMenu menu = new JMenu("Edit");
...
menu.add(getActionByName(DefaultEditorKit.cutAction));
...
此代码通过之前展示的方便方法按名称获取操作。然后将操作添加到菜单中。这就是你需要做的一切。菜单和操作会处理其他所有事情。请注意,操作的名称来自DefaultEditorKit
。该工具包提供了基本文本编辑的操作,并且是 Swing 提供的所有编辑工具包的超类。因此,除非被自定义覆盖,否则其功能对所有文本组件都可用。
为了效率,文本组件共享操作。通过getActionByName(DefaultEditorKit.cutAction)
返回的Action
对象由窗口底部的不可编辑JTextArea
共享。这种共享特性有两个重要的影响:
-
通常,您不应修改从编辑工具包获取的
Action
对象。如果这样做,更改会影响程序中的所有文本组件。 -
Action
对象可以在程序中操作其他文本组件,有时会超出您的意图。在这个例子中,即使它是不可编辑的,JTextArea
也与JTextPane
共享动作。(在文本区域中选择一些文本,然后选择剪切到剪贴板菜单项。您会听到一声哔声,因为文本区域是不可编辑的。)如果您不想共享,请自行实例化Action
对象。DefaultEditorKit
定义了许多有用的Action
子类。
以下是创建样式菜单并将粗体菜单项放入其中的代码:
protected JMenu createStyleMenu() {
JMenu menu = new JMenu("Style");
Action action = new StyledEditorKit.BoldAction();
action.putValue(Action.NAME, "Bold");
menu.add(action);
...
StyledEditorKit
提供了Action
子类来实现对带样式文本的编辑命令。您会注意到,这段代码创建了BoldAction
类的一个实例,而不是从编辑器工具包中获取动作。因此,这个动作不会与任何其他文本组件共享,更改其名称不会影响任何其他文本组件。
将文本动作与键盘快捷键关联
除了将动作与 GUI 组件关联外,还可以通过使用文本组件的输入映射将动作与键盘快捷键关联。输入映射在如何使用键绑定中有描述。
TextComponentDemo
示例中的文本窗格支持四个默认未提供的键绑定。
-
按下 Ctrl-B 将光标向后移动一个字符
-
按下 Ctrl-F 将光标向前移动一个字符
-
按下 Ctrl-N 将光标向下移动一行
-
按下 Ctrl-P 将光标向上移动一行
以下代码将 Ctrl-B 键绑定添加到文本窗格。添加上面列出的其他三个绑定的代码类似。
InputMap inputMap = textPane.getInputMap();
KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B,
Event.CTRL_MASK);
inputMap.put(key, DefaultEditorKit.backwardAction);
首先,代码获取文本组件的输入映射。接下来,它找到代表 Ctrl-B 键序列的KeyStroke
对象。最后,代码将该键序列绑定到将光标向后移动的Action
。
实现撤销和重做
实现撤销和重做有两个部分:
-
记住可撤销的编辑。
-
实现撤销和重做命令 并为其提供用户界面。
第一部分:记住可撤销的编辑
要支持撤销和重做,文本组件必须记住每次编辑的发生,编辑的顺序以及撤销每次编辑所需的内容。示例程序使用UndoManager
类的一个实例来管理其可撤销编辑的列表。撤销管理器是在声明成员变量的地方创建的:
protected UndoManager undo = new UndoManager();
现在,让我们看看程序如何发现可撤销的编辑并将其添加到撤销管理器中。
当文档内容发生可撤销编辑时,文档会通知感兴趣的监听器。实现撤销和重做的一个重要步骤是在文本组件的文档上注册一个可撤销编辑监听器。以下代码向文本窗格的文档添加了MyUndoableEditListener
的一个实例:
doc.addUndoableEditListener(new MyUndoableEditListener());
我们示例中使用的可撤销编辑侦听器将编辑添加到撤销管理器的列表中:
protected class MyUndoableEditListener
implements UndoableEditListener {
public void undoableEditHappened(UndoableEditEvent e) {
//Remember the edit and update the menus
undo.addEdit(e.getEdit());
undoAction.updateUndoState();
redoAction.updateRedoState();
}
}
请注意,此方法更新两个对象:undoAction
和redoAction
。这些是分别附加到撤销和重做菜单项的操作对象。下一步将向您展示如何创建菜单项以及如何实现这两个操作。有关可撤销编辑侦听器和可撤销编辑事件的一般信息,请参阅如何编写可撤销编辑侦听器。
注意:
默认情况下,每个可撤销编辑都会撤消一个字符输入。通过一些努力,可以将编辑分组,以便一系列按键组合成一个可撤销编辑。以这种方式分组编辑需要您定义一个类,拦截文档的可撤销编辑事件,如果适当则将它们组合并将结果转发给您的可撤销编辑侦听器。
第 2 部分:实现撤销和重做命令
实现撤销和重做的第一步是创建要放入编辑菜单中的操作。
JMenu menu = new JMenu("Edit");
//Undo and redo are actions of our own creation
undoAction = new UndoAction();
menu.add(undoAction);
redoAction = new RedoAction();
menu.add(redoAction);
...
撤销和重做操作由自定义的AbstractAction
子类实现:UndoAction
和RedoAction
,分别。这些类是示例主类的内部类。
当用户调用undo
命令时,将调用UndoAction
类的actionPerformed
方法:
public void actionPerformed(ActionEvent e) {
try {
undo.undo();
} catch (CannotUndoException ex) {
System.out.println("Unable to undo: " + ex);
ex.printStackTrace();
}
updateUndoState();
redoAction.updateRedoState();
}
此方法调用撤销管理器的undo
方法,并更新菜单项以反映新的撤销/重做状态。
类似地,当用户调用redo
命令时,将调用RedoAction
类的actionPerformed
方法:
public void actionPerformed(ActionEvent e) {
try {
undo.redo();
} catch (CannotRedoException ex) {
System.out.println("Unable to redo: " + ex);
ex.printStackTrace();
}
updateRedoState();
undoAction.updateUndoState();
}
此方法类似于撤销,只是调用撤销管理器的redo
方法。
UndoAction
和RedoAction
类中的大部分代码专门用于根据当前状态启用和禁用操作,并更改菜单项的名称以反映要撤消或重做的编辑。
注意:
TextComponentDemo
示例中的撤销和重做实现取自随 JDK 软件提供的NotePad
演示。许多程序员也可以直接复制此撤销/重做实现而无需修改。
概念:关于文档
与其他 Swing 组件一样,文本组件将其数据(称为模型)与数据的视图分开。如果您尚未熟悉 Swing 组件使用的模型-视图分离,请参考使用模型。
文本组件的模型称为文档,是实现Document
接口的类的实例。文档为文本组件提供以下服务:
-
包含文本。文档将文本内容存储在
Element
对象中,这些对象可以表示任何逻辑文本结构,如段落,或共享样式的文本运行。我们在这里不描述Element
对象。 -
通过
remove
和insertString
方法支持编辑文本。 -
通知文档侦听器和可撤销编辑侦听器文本更改。
-
管理
Position
对象,跟踪文本中的特定位置,即使文本被修改。 -
允许您获取有关文本的信息,例如其长度以及文本段作为字符串。
Swing 文本包包含Document
的一个子接口,StyledDocument
,它添加了使用样式标记文本的支持。一个JTextComponent
子类,JTextPane
,要求其文档是StyledDocument
而不仅仅是Document
。
javax.swing.text
包提供了以下文档类层次结构,这些类实现了各种JTextComponent
子类的专门文档:
PlainDocument
是文本字段、密码字段和文本区域的默认文档。PlainDocument
提供一个基本容器,其中所有文本以相同字体显示。即使编辑器窗格是一个样式化文本组件,但默认情况下它使用PlainDocument
的一个实例。标准JTextPane
的默认文档是DefaultStyledDocument
的一个实例,用于以无特定格式显示样式文本的容器。但是,任何特定编辑器窗格或文本窗格使用的文档实例取决于绑定到其上的内容类型。如果使用setPage
方法将文本加载到编辑器窗格或文本窗格中,则窗格使用的文档实例可能会更改。有关详细信息,请参阅如何使用编辑器窗格和文本窗格。
虽然你可以设置文本组件的文档,但通常更容易允许它自动设置,并在必要时使用文档过滤器来更改文本组件的数据设置方式。您可以通过安装文档过滤器或将文本组件的文档替换为自己的文档来实现某些自定义。例如,TextComponentDemo
示例中的文本窗格具有一个限制文本窗格可以包含字符数量的文档过滤器。
实现文档过滤器
要实现文档过滤器,请创建DocumentFilter
的子类,然后使用AbstractDocument
类中定义的setDocumentFilter
方法将其附加到文档上。虽然可能存在不是从AbstractDocument
继承的文档,但默认情况下,Swing 文本组件使用AbstractDocument
子类作为其文档。
TextComponentDemo
应用程序有一个文档过滤器,DocumentSizeFilter
,限制文本窗格可以包含的字符数。以下是创建过滤器并将其附加到文本窗格文档的代码:
*...//Where member variables are declared:*
JTextPane textPane;
AbstractDocument doc;
static final int MAX_CHARACTERS = 300;
...
textPane = new JTextPane();
...
StyledDocument styledDoc = textPane.getStyledDocument();
if (styledDoc instanceof AbstractDocument) {
doc = (AbstractDocument)styledDoc;
doc.setDocumentFilter(new DocumentSizeFilter(MAX_CHARACTERS));
}
为了限制文档中允许的字符数,DocumentSizeFilter
覆盖了DocumentFilter
类的insertString
方法,每次插入文本到文档时都会调用该方法。它还覆盖了replace
方法,当用户粘贴新文本时最有可能被调用。一般来说,文本插入可能发生在用户键入或粘贴新文本时,或者调用setText
方法时。这是DocumentSizeFilter
类对insertString
方法的实现:
public void insertString(FilterBypass fb, int offs,
String str, AttributeSet a)
throws BadLocationException {
if ((fb.getDocument().getLength() + str.length()) <= maxCharacters)
super.insertString(fb, offs, str, a);
else
Toolkit.getDefaultToolkit().beep();
}
replace
的代码类似。DocumentFilter
类定义的方法的FilterBypass
参数只是一个使文档能够以线程安全方式更新的对象。
因为前面的文档过滤器涉及文档数据的添加,它仅覆盖了insertString
和replace
方法。大多数文档过滤器还会覆盖DocumentFilter
的remove
方法。
监听文档的更改
您可以在文档上注册两种不同类型的监听器:文档监听器和可撤销编辑监听器。本小节描述了文档监听器。有关可撤销编辑监听器的信息,请参阅实现撤销和重做。
文档会通知已注册的文档监听器文档的更改。使用文档监听器在文档中插入或删除文本,或文本样式更改时创建反应。
TextComponentDemo
程序使用文档监听器在文本窗格发生更改时更新更改日志。以下代码行将MyDocumentListener
类的一个实例注册为文本窗格文档的监听器:
doc.addDocumentListener(new MyDocumentListener());
这是MyDocumentListener
类的实现:
protected class MyDocumentListener implements DocumentListener {
public void insertUpdate(DocumentEvent e) {
displayEditInfo(e);
}
public void removeUpdate(DocumentEvent e) {
displayEditInfo(e);
}
public void changedUpdate(DocumentEvent e) {
displayEditInfo(e);
}
private void displayEditInfo(DocumentEvent e) {
Document document = (Document)e.getDocument();
int changeLength = e.getLength();
changeLog.append(e.getType().toString() + ": "
+ changeLength + " character"
+ ((changeLength == 1) ? ". " : "s. ")
+ " Text length = " + document.getLength()
+ "." + newline);
}
}
监听器实现了处理三种不同类型文档事件的三种方法:插入、删除和样式更改。StyledDocument
实例可以触发这三种类型的事件。PlainDocument
实例仅触发插入和删除事件。有关文档监听器和文档事件的一般信息,请参阅如何编写文档监听器。
请记住,此文本窗格的文档过滤器限制了文档中允许的字符数。如果尝试添加的文本超出文档过滤器允许的范围,文档过滤器将阻止更改,并且监听器的insertUpdate
方法不会被调用。只有在更改已经发生时,文档监听器才会被通知更改。
您可能希望在文档监听器中更改文档的文本。但是,您绝对不应该在文档监听器中修改文本组件的内容。如果这样做,程序很可能会死锁。相反,您可以使用格式化文本字段或提供文档过滤器。
监听插入符和选择更改
TextComponentDemo
程序使用一个插入符监听器来显示插入符的当前位置,或者如果选择了文本,则显示选择的范围。
此示例中的插入符监听器类是一个JLabel
子类。以下是创建插入符监听器标签并将其设置为文本窗格的插入符监听器的代码:
//Create the status area
CaretListenerLabel caretListenerLabel = new CaretListenerLabel(
"Caret Status");
...
textPane.addCaretListener(caretListenerLabel);
插入符监听器必须实现一个方法,即caretUpdate
,每次插入符移动或选择更改时都会调用该方法。以下是CaretListenerLabel
对caretUpdate
的实现:
public void caretUpdate(CaretEvent e) {
//Get the location in the text
int dot = e.getDot();
int mark = e.getMark();
if (dot == mark) { // no selection
try {
Rectangle caretCoords = textPane.modelToView(dot);
//Convert it to view coordinates
setText("caret: text position: " + dot +
", view location = [" +
caretCoords.x + ", " + caretCoords.y + "]" +
newline);
} catch (BadLocationException ble) {
setText("caret: text position: " + dot + newline);
}
} else if (dot < mark) {
setText("selection from: " + dot + " to " + mark + newline);
} else {
setText("selection from: " + mark + " to " + dot + newline);
}
}
如您所见,此监听器会更新其文本标签以反映插入符或选择的当前状态。监听器从插入符事件对象获取要显示的信息。有关插入符监听器和插入符事件的一般信息,请参阅如何编写插入符监听器。
与文档监听器一样,插入符监听器是被动的。它对插入符或选择的更改做出反应,但不会更改插入符或选择本身。如果要更改插入符或选择,请使用导航过滤器或自定义插入符。
实现导航过滤器类似于实现文档过滤器。首先,编写一个NavigationFilter
的子类。然后使用setNavigationFilter
方法将子类的实例附加到文本组件。
您可能会创建一个自定义插入符来自定义插入符的外观。要创建自定义插入符,请编写一个实现Caret
接口的类,可能是通过扩展DefaultCaret
类。然后将您的类的实例作为参数提供给文本组件的setCaret
方法。
概念:关于编辑器工具包
文本组件使用EditorKit
将文本组件的各个部分联系在一起。编辑器工具包提供视图工厂、文档、插入符和操作。编辑器工具包还读取和写入特定格式的文档。尽管所有文本组件都使用编辑器工具包,但某些组件会隐藏它们的编辑器工具包。您无法设置或获取文本字段或文本区域使用的编辑器工具包。编辑窗格和文本窗格提供getEditorKit
方法以获取当前编辑器工具包,并提供setEditorKit
方法以更改它。
对于所有组件,JTextComponent
类提供了 API,让您间接调用或自定义一些编辑工具包的功能。例如,JTextComponent
提供了read
和write
方法,这些方法调用编辑工具包的read
和write
方法。JTextComponent
还提供了一个方法,getActions
,它返回组件支持的所有操作。
Swing 文本包提供以下编辑工具包:
读取和写入纯文本,并提供一组基本的编辑命令。有关文本系统如何处理换行符的详细信息可以在DefaultEditorKit
API 文档中找到。简而言之,'\n'字符在内部使用,但在写入文件时使用文档或平台的换行符。所有其他编辑工具包都是DefaultEditorKit
类的子类。
读取和写入样式文本,并为样式文本提供一组最小的操作。这个类是DefaultEditorKit
的子类,是JTextPane
默认使用的编辑工具包。
读取、写入和编辑 HTML。这是StyledEditorKit
的子类。
上述每个编辑工具包都已注册到JEditorPane
类,并与工具包读取、写入和编辑的文本格式相关联。当文件加载到编辑窗格中时,窗格会检查文件的格式与其注册的工具包是否匹配。如果找到支持该文件格式的注册工具包,则窗格将使用该工具包来读取文件、显示和编辑。因此,编辑窗格有效地将自身转换为该文本格式的编辑器。您可以通过为其创建编辑工具包,然后使用JEditorPane
的registerEditorKitForContentType
将您的工具包与您的文本格式相关联,从而扩展JEditorPane
以支持您自己的文本格式。