创建定制 Java 桌面数据库应用程序
本教程指导您完成创建完整桌面数据库应用程序的过程,该应用程序使用户能够浏览和编辑客户记录和购买历史记录。生成的应用程序包括以下主要功能:
- 主视图,用户可用来浏览客户记录和客户购买情况。
- 客户记录搜索字段。
- 用于输入新记录或修改现有记录的单独对话框。
- 用于与多个相关数据库表进行交互的代码。
目录

要学完本教程,您需要具备以下软件和资源。
注意:可以随时下载本教程中创建的最终工作项目,并在 IDE 中打开该项目以查看这些类。如果要运行下载的项目,请确保在运行前清理并生成该项目。
简介
该应用程序利用了以下技术:
- Java 持久性 API (Java Persistence API, JPA),可帮助您使用 Java 代码与数据库进行交互。
- Bean 绑定,用于将 Swing 组件属性保持同步。
- Swing 应用程序框架,用于简化基本应用程序功能,如保留会话信息、处理操作以及管理资源。
本教程使用 IDE 向导和其他代码生成功能来提供大部分模板代码。本教程还说明了如何定制生成的代码以及手动编写应用程序其他部分的代码。
要完成本教程,大约需要 2 小时。有关介绍如何创建具有较少定制内容的用户界面的简短教程,请参见生成 Java 桌面数据库应用程序。
下面是完成本教程时后生成的工作应用程序屏幕快照。

设置数据库
在创建任何应用程序代码之前,您需要先设置数据库。然后,可通过向导根据数据库结构生成大部分应用程序代码。
本教程中的说明基于使用此 SQL 脚本创建的 MySQL 数据库。
注意:您可以使用其他数据库管理软件,但这样做可能需要对该 SQL 脚本进行一些调整。此外,还需要在 IDE 外部创建数据库。
要设置 IDE 以使用 MySQL 数据库,请参见连接到 MySQL 数据库页。
创建数据库,请执行以下操作:
- 在“服务”窗口中,右键单击“MySQL 服务器”节点,然后选择“启动”。
- 右键单击“MySQL 服务器”节点,然后选择“创建数据库”。
如果未启用“创建数据库”项,请选择“连接”。然后,您可能需要输入口令。接下来,便会启用“创建数据库”项。
- 在“数据库名称”中键入
MyBusinessRecords,然后单击“确定”。
将会在数据库连接列表中显示一个名为 MyBusinessRecords 的节点。
- 右键单击 "MyBusinessRecords" 节点,然后选择“连接”。
- 如果出现“连接”对话框,请键入为数据库服务器设置的口令。
- 如果该对话框的“高级”标签打开,请单击“确定”以关闭对话框。
- 向下滚动至刚创建的连接节点。该节点应具有
图标。
- 右键单击该连接节点,然后选择“执行命令”。
- 复制 MyBusinessRecords SQL 脚本内容,并将其粘贴到源代码编辑器的“SQL 命令 1”标签中。
- 在源代码编辑器工具栏中,单击“运行 SQL”按钮 (
) 以运行该脚本。
“输出”窗口将会显示该脚本的输出。
- 再次右键单击该连接节点,然后选择“刷新”。
- 展开该节点,然后展开其“表”子节点。
将会看到列出了四个数据库表。
在设计数据库结构时,已考虑了标准化和引用完整性问题。下面是有关该结构的一些说明:
- SQL 脚本指定了 InnoDB 存储引擎以便处理该数据库中的外键。本教程没有使用 MySQL 的缺省存储引擎 MyISAM。
- 已将数据拆分成几个表以减少重复和不一致问题。一些表通过外键彼此关联。
- 所有这些表均使用 MySQL 的 AUTO_INCREMENT 属性,以便这些表中的每一行都有一个唯一的标识符。此标识符是由数据库管理软件创建的,因此,应用程序和/或应用程序用户不必创建该标识符。(IDE 将在表的实体类中为该列添加
@GeneratedValue(strategy=GenerationType.IDENTITY 标注,以便在应用程序中正确使用 AUTO_INCREMENT。这可确保在创建新记录时,应用程序不会尝试为该列提交值。)
- ORDERS 表中的外键用于将每个订单记录与客户相关联。在应用程序的用户界面中,仅为选定 CUSTOMER 显示 ORDER 记录。
- 指向 CUSTOMERS 类的外键的 ON CASCADE DELETE 属性可确保在删除客户时也会删除其订单。
- CUSTOMERS 表中的外键指向 COUNTRIES 表。将在应用程序中使用此关系,以使用户能够从组合框中选择客户所在的国家/地区。
- ORDERS 表具有一个指向 PRODUCTS 表的外键。在添加新订单记录时,用户可以从组合框中选择产品。
- COUNTRIES 和 PRODUCTS 表预填充了数据,这样,在应用程序用户添加客户和订单记录时,您可以从这些表中进行选择。
- 尽管本教程没有介绍这方面的内容,但您可能会发现创建单独应用程序以填充 COUNTRIES 和 PRODUCTS 表非常有用。可以使用“Java 桌面应用程序”项目模板创建此类应用程序,并且不需要额外的手动编码工作。
创建应用程序框架
您将使用 IDE 的“Java 桌面应用程序”项目模板创建应用程序的大部分基本代码。此模板为以下功能生成代码:
- 数据库连接。
- 主应用程序框架,其中包含客户详细信息和客户订单的表。
- 主应用程序类,用于处理基本应用程序生命周期功能,其中包括在会话之间保持窗口状态和资源注入。
- 标准数据库应用程序命令的操作(和相应按钮)。
对于某些数据库结构,可以在退出向导时立即生成工作应用程序。不过,本教程中使用了一些需要定制生成代码的结构,如 AUTO-INCREMENT 和一对多关系。
创建应用程序框架:
- 选择“文件”>“新建项目”。
- 选择 "Java" 类别,然后选择“Java 桌面应用程序”模板。然后,单击“下一步”。
- 在向导的“名称和位置”页中,执行以下步骤:
- 在“项目名称”字段中键入
CustomerRecords。
- 选择数据库应用程序 Shell。
- 单击“下一步”。
- 在向导的“主表”页中,执行以下步骤:
- 选择刚创建的 MyBusinessRecords 数据库的连接。
- 选择 CUSTOMERS 表。
- 将
ID 条目从“要包含的列”移至“可用列”。
- 单击“下一步”。
- 在“详细信息选项”页中,执行以下步骤:
- 单击“表”单选按钮,为 ORDERS 表创建 JTable。
- 将
ID 条目从“要包含的列”移至“可用列”。
- 单击“完成”退出向导。
将生成几个类,其中包括以下类:
此时,可以选择“运行”>“运行主项目”以查看主应用程序窗口。不过,应用程序目前还无法正常工作,因为向导还没有为数据库中包含的某些属性生成所需的代码。将在本教程的下一节中添加该代码。
更改主应用程序框架标题
在运行应用程序时,应用程序框架带有 "Database Application Example" 标题。对于简单 JFrame,通常可在组件的属性表单中修改 title 属性以更改标题。但此应用程序框架使用的是 Swing 应用程序框架的 FrameView 类,标题是一个常规应用程序属性。您可以在“项目属性”窗口中修改这些应用程序属性。
更改应用程序主框架标题:
- 在“项目”窗口中选择项目节点,然后选择“属性”。
- 在“项目属性”对话框中,选择“应用程序”节点。
- 将“标题”属性更改为
Customer and Order Records。
- 如果需要,还可以修改其他属性,如“描述”和“闪屏”。
定制主视图/详细视图
就本教程中创建的应用程序而言,目前仅生成了较简单的应用程序代码。数据库包含四个表,但向导仅为主视图/详细视图中显示的两个表生成了实体类。此外,向导没有考虑 Customers 表中指向 Countries 表的外键或 Orders 表中指向 Products 表的外键。唯一确认的表间关系是 Customers 和 Orders 表之间的主/详细关系。要解决这些问题,您需要执行以下操作:
- 为 COUNTRIES 和 PRODUCTS 表创建实体类。
- 定制 Customers 实体类以引用 Countries 实体类。
- 定制 Orders 实体类以引用 Products 实体类。
- 更新主窗体中的主表和详细表的绑定代码,以便使用 Countries 和 Products 实体类。
生成缺少的实体类
创建缺少的实体类:
- 右键单击
customerrecords 包并选择“新建”>“通过数据库生成实体类”,为 Countries 和 Products 表创建实体类。
- 选择 Countries 表,然后单击“添加”按钮。
- 选择 Products 表,然后单击“添加”按钮。
- 单击“下一步”。
- 在向导的“实体类”页中,单击“下一步”。
- 在“映射选项”页中,将“复合类型”更改为
java.util.List。
- 单击“完成”退出向导。
将会在项目中显示名为 Countries 和 Products 的新类。
在实体类之间建立关系
现在,您需要修改 Customers 和 Orders 实体类,以使其正确处理指向 Countries 和 Products 表的数据库外键。countryId 属性的类型为 Countries 而不是 Integer,并与 COUNTRIES 数据库表相关联。
在 Customers 和 Countries 实体类之间建立关系:
- 在 Customers 类中,将以下字段声明和标注
@Column(name = "COUNTRY_ID")
private Integer countryId;
替换为以下代码:
@JoinColumn(name = "COUNTRY_ID", referencedColumnName = "COUNTRY_ID")
@ManyToOne
private Countries countryId;
- 按 Ctrl-Shift-I 组合键,为粘贴的代码添加 import 语句。
- 将
getCountryId() 方法的类型由 Integer 更改为 Countries。
- 在
setCountryId() 方法中,将 countryId 和 oldCountryId 类型由 Integer 更改为 Countries。
在 Orders 和 Products 实体类之间建立关系:
- 在 Orders 类中,将以下字段声明和标注
@Basic(optional = false)
@Column(name = "PRODUCT_ID")
private int productId;
替换为以下代码:
@JoinColumn(name = "PRODUCT_ID", referencedColumnName = "PRODUCT_ID")
@ManyToOne
private Products productId;
- 按 Ctrl-Shift-I 组合键,为粘贴的代码添加 import 语句。
- 在
public Orders(Integer id, Products productId, int quantity) 构造函数中,将 productId 参数类型由 int 更改为 Products。
- 将
getProductId() 方法类型由 int 更改为 Products。
- 在
setProductId() 方法中,将 productId 参数和 oldProductId 变量的类型由 int 更改为 Products。
定制列绑定代码
您还需要更新 country 字段的列绑定,以使其引用 Countries 对象的 country 属性。(使用 country ID int 的代码是由项目生成的,因为框架是在没有 COUNTRIES 表的实体类的情况下生成的。如果在此处未进行更改,在运行应用程序时将会抛出 ClassCastException。)
在主表中修复列绑定:
- 打开 CustomerRecordsView 类。
- 在该类的“设计”视图中,右键单击顶部的表,然后选择“表内容”。
- 选择“列”标签。
- 在定制器中,选择 Country Id 行。
- 在“表达式”组合框中键入
${countryId.country},或者从下拉列表中选择 "countryId" > "country"。
country 属性是 Countries 对象的一部分,其中包含实际的国家/地区名称。通过使用该表达式,可以确保该列显示的是国家/地区名称,而不是国家/地区标识号码或 Countries 对象的 toString() 方法值。
- 将“标题”由 Country Id 更改为 Country。这会影响运行的应用程序中的列标题。
- 单击“关闭”以保存更改。
定制详细表
类似地,您需要更改详细表的 Product Id 列以避免抛出 ClassCastException。在定制详细表时,还需要执行一些额外的步骤。将在多个列中显示产品信息,但目前仅生成了一列。
Products 表中包含一个 productId 列,它基于 ORDERS 表中的 PRODUCT_ID 列。要显示产品型号并避免抛出 ClassCastException,只需修改绑定就够了。不过,在其他列中显示与产品对应的数据是非常有用的。
完成详细表中的列:
- 在 CustomerRecordsView 类的“设计”视图中,右键单击底部的表,然后选择“表内容”。
- 选择“列”标签。
- 在定制器中,选择 Product Id 行。
- 将“表达式”更改为
${productId.brand}。在执行此操作后,类型也会变为 String。
- 将“标题”由 productId 更改为 Brand。
- 单击“插入”按钮。
- 选择在表中刚添加的行。
- 在“标题”中键入 Type。
- 从“表达式”的下拉列表中选择 "productId" > "prodType"。
- 再次单击“插入”按钮,然后选择新添加的行。
- 在“标题”中键入 Model。
- 从“表达式”的下拉列表中选择 "productId" > "model"。
- 再次单击“插入”按钮,然后选择新添加的行。
- 在“标题”中键入 Price。
- 从“表达式”的下拉列表中选择 "productId" > "price"。
- 单击“关闭”以应用更改。
此时,应用程序可以部分正常工作。您可以运行应用程序并添加、编辑、删除和保存记录。不过,还无法正确修改基于 Countries 和 Products 实体类的字段。此外,还必须完成某些工作,以使 Order Date 字段以更用户友好的方式工作。
可以对 Country 和 Model 列进行一些调整,以使用绑定到其相应表的组合框。这样,用户便可选择这些字段,而无需手动输入内容。您将使用对话框作为这些表的数据输入方法,以使用户在浏览数据时更不容易误删数据。
添加对话框
在教程的本节中,将会完成以下操作:
- 添加所需的实用程序类。
- 在主窗体中调整这些操作。
- 创建对话框以编辑主窗体中的每个表的数据。
- 创建中间 Bean 以便在对话框和窗体之间传输数据。
- 将对话框中的字段绑定到中间 Bean。
- 为对话框中的按钮创建事件处理程序。
在项目中添加所需的实用程序类
本教程借助于几个实用程序类来指定内容,例如,如何呈现和验证某些值。
在项目中添加所需的所有实用程序类:
- 解压缩实用程序类 zip 文件,然后将其内容解压缩到系统上。
- 在系统上复制 zip 文件中的所有文件,并将其粘贴到包含项目
customerrecords 文件夹的文件夹中。
- 如果类位于
customerreccords 以外的包中,请在刚添加到项目中的每个文件内调整包语句。
调整操作详细信息
在应用程序中添加对话框之前,请更改新按钮的名称以使其更加清楚。将会在为这些按钮指定的操作中进行更改。
更改按钮文本:
- 打开 CustomerRecordsView 类并选择“设计”视图。
- 右键单击第一个新按钮,然后选择“设置操作”。
- 在“设置操作”对话框中,将“文本”属性更改为
New Customer。
- 将“工具提示”字段更改为
Create a new customer record。
- 单击“确定”。
- 右键单击第二个新按钮,然后选择“设置操作”。
- 在“设置操作”对话框中,将“文本”属性更改为
Enter Order。
- 将“工具提示”字段更改为
Create a new customer order record。
- 单击“确定”。
创建客户对话框
要创建并填充客户表的对话框,请执行以下步骤:
- 右键单击包含类的包,然后选择“新建”>“其他”。选择“Swing GUI 窗体”>“JDialog 窗体”模板,并将其命名为
CustomerEditor。
- 从“组件面板”窗口中,拖放并排列一些组件以显示客户的个人详细信息。
为每个以下字段添加标签:First Name、Last Name、Address、City、State、Zip Code、Country 和 Phone Number。
除了 Country 以外,为所有上述字段添加文本字段。
对于 Country,请添加一个组合框。
- 编辑 JLabel 的显示文本。
- 添加两个按钮,并将其命名为 Save 和 Cancel。
- (可选)将添加的所有组件重命名为更好记的名称,如
firstNameLabel。可以在“检查器”窗口中内联执行此操作。
- 选择整个 JDialog 窗体。
- 在“属性”窗口中,将“标题”属性更改为
Customer Editor。
生成的布局应如下所示。

注意:有关使用 GUI 编辑器布局功能的详细指南,请参见在 NetBeans IDE 中设计 Swing GUI。
绑定客户对话框字段
现在,您需要将各种字段绑定到表中的相应列。无法在“绑定”对话框中直接绑定到其他窗体中的组件,因此,必须创建一个 Customers 类型的中间 Bean 属性以保存记录。在用户单击 "New Customer" 时,将会为该 Bean 属性分配空记录值,用户可以在对话框中对其进行修改。在用户单击对话框中的 "Save" 时,该 Bean 属性中的值将传回到主窗体和数据库。
生成中间 Bean 属性:
- 在 CustomerEditor 窗体设计区域顶部,单击“源”标签。单击类中的某个位置,如构造函数下面的一行。
- 按 Alt-Insert 组合键(或单击鼠标右键并选择“插入代码”,然后选择“添加属性”。
- 在“添加属性”对话框中,将该属性命名为
currentRecord,将其类型指定为 Customers,选择“生成 getter 和 setter”,然后选择“生成属性更改支持”。
- 单击“确定”以生成该属性。
现在,您需要定制生成的 setCurrentRecord 方法以触发属性更改通知。
触发属性更改通知:
现在,您需要添加代码,以便在用户单击 "New Customer" 按钮时打开 "Customer Editor" 对话框。此外,还需要编写代码以清除 currentRecord 属性。
添加代码以从主视图中打开对话框:
- 打开 CustomerRecordsView 类并选择“源”视图。
- 导航至
newRecord() 方法。
- 在该方法底部粘贴以下代码。
JFrame mainFrame = CustomerRecordsApp.getApplication().getMainFrame();
CustomerEditor ce = new CustomerEditor(mainFrame, false);
ce.setCurrentRecord(c);
ce.setVisible(true);
现在,您可以继续绑定对话框的字段。将每个文本字段的 text 属性绑定到 currentRecord 表示的 Customers 对象的相应属性。类似地,将组合框的 selectedItem 属性绑定到 currentRecord 的 countryId 属性。将组合框的 elements 属性绑定到 Countries 实体列表。
将文本字段绑定到 currentRecord Bean 属性:
- 切换回 CustomerEditor 类的“设计”视图。
- 选择窗体上的 "First Name" 文本字段。
- 在“属性”窗口中,选择“绑定”类别。
- 单击
text 属性旁边的省略号 (...) 按钮。
- 在“绑定”对话框中,从“绑定源”中选择 "Form"。(请注意,"Form" 位于下拉列表底部。)
- 在“绑定表达式”下拉列表中,展开 "
currentRecord" 节点,然后选择所绑定的文本字段对应的属性。
- 单击“确定”关闭“绑定”对话框。
- 对于其他每个文本字段,重复步骤 1 至 6。
绑定 "Country" 组合框:
- 右键单击组合框,然后选择“绑定”> "elements"。
- 单击“将数据导入窗体”,选择数据库连接,然后选择 Countries 表。
countriesList 将显示为绑定源。单击“确定”。
- 再次右键单击组合框,然后选择“绑定”> "selectedItem"。
- 从“绑定源”中选择 "Form",然后从“绑定表达式”中选择 "currentRecord" > "countryId"。单击“确定”。
该组合框几乎可以直接在对话框中正确使用。该组合框设置为从 COUNTRIES 数据库表中提取值,然后将用户选择的项应用于当前记录中的 country 字段。不过,仍然需要定制该组合框的呈现方式,因为绑定到该组合框的值是 Countries 对象,而不是简单名称。可通过指定定制单元格呈现器来完成该操作。(对于 JTable 和 JList,可通过 Bean 绑定库来指定显示表达式,因而不再需要创建定制呈现器。不过,组合框还无法使用此功能。)
要使组合框呈现国家/地区名称,请执行以下操作:
- 在“项目”窗口中,右键单击 "CountryListCellRenderer",然后选择“编译文件”。通过编译文件,您可以在 IDE GUI 生成器中执行拖放操作,以将其作为 Bean 添加到窗体中。
- 在源代码编辑器中选择 CustomerEditor 窗体,然后选择“设计”视图。
- 将该类从“项目”窗口拖到窗体周围的空白区域中,如下面的屏幕快照所示。
这样做可将呈现器作为 Bean 添加到窗体中,这与从组件面板中拖动组件以添加到窗体中类似。
- 在窗体中选择该组合框。
- 在“属性”窗口中,选择“属性”类别。
- 滚动至
renderer 属性,然后从该属性的下拉列表中选择 countryListCellRenderer1。
现在,您应该可以运行应用程序,按第一个新按钮,然后在对话框中输入数据。还可以从 "Country" 组合框中选择一个国家/地区。对话框中的 "Save" 和 "Cancel" 按钮目前还无法正常工作,但可以保存主框架中的记录。将在后面编写这两个按钮的代码。
创建 Order Entry 对话框
现在,将为窗体的详细信息部分创建对话框。
创建 "Order Editor" 对话框:
- 右键单击
customerrecords 包,然后选择“新建”>“JDialog 窗体”。
- 将该类命名为
OrderEditor,然后单击“完成”。
- 将以下组件拖放到窗体中:
- Date、Product、Quantity、Price 和 Total 的标签组件。
- 日期、价格和总计的带格式字段组件。
- 显示需要输入的日期格式的标签。为该标签输入文本
(MMM DD, YYYY,例如,Apr 17, 2008)。如果打算使用其他日期格式,请添加与要使用的格式对应的文本。
- Product 字段的组合框。
- Quantity 字段的文本字段。
- 保存和取消命令对应的两个按钮。
- 选择整个 JDialog 窗体。
- 在“属性”窗口中,将“标题”属性更改为
Order Editor。
Order Editor 现在具有了可视设计,您需要执行以下操作:
- 创建一个中间 Bean 以将记录值传回到主窗体。
- 指定带格式文本字段的格式。
- 绑定各种字段。
- 设置 Date 的带格式字段的行为。
- 处理 Price 和 Total 字段的货币格式。
将 "Order Editor" 对话框与主窗体相关联
与 "CustomerEditor" 对话框一样,必须创建一个 Orders 类型的中间 Bean 属性以保存记录。在用户按 "Enter Order" 时,将为该属性指定当前选择的订单记录的值。
创建 Bean 属性:
- 在 OrderEditor 窗体设计区域顶部,单击“源”标签。
- 单击类中的某个位置,如构造函数下面的一行。
- 按 Alt-Insert 组合键(或单击鼠标右键并选择“插入代码”,然后选择“添加属性”。
- 在“添加属性”对话框中,将该属性命名为
currentOrderRecord,将其类型指定为 Orders,选择“生成 getter 和 setter”,然后选择“生成属性更改支持”。
- 单击“确定”以生成该属性。
- 在
setCurrentOrderRecord() 方法中,将 this.currentOrderRecord = currentOrderRecord; 行替换为以下三行:
Orders oldRecord = this.currentOrderRecord;
this.currentOrderRecord = currentOrderRecord;
propertyChangeSupport.firePropertyChange("currentOrderRecord", oldRecord, currentOrderRecord);
现在,您需要添加代码,以便在用户单击 "Enter Order" 按钮时打开 "Order Editor" 对话框。此外,还需要编写代码以清除 currentOrderRecord 属性。
将该对话框与 "Enter Order" 按钮相关联:
- 打开 CustomerRecordsView 类并选择“源”视图。
- 导航至
newDetailRecord() 方法。
- 在该方法底部粘贴以下代码。
JFrame mainFrame = CustomerRecordsApp.getApplication().getMainFrame();
OrderEditor oe = new OrderEditor(mainFrame, false);
oe.setCurrentOrderRecord(o);
oe.setVisible(true);
绑定 Order Editor 的字段
注意:对于带格式的文本字段,将绑定到 value 属性,而不是 text 属性。有关详细信息,请参见 Java 教程。
绑定 Order 对话框的字段:
- 在“设计”视图中打开 OrderEditor 类。
- 选择 Date 的文本字段。
- 在“属性”窗口中,选择“绑定”类别。
- 选择
value 属性,然后单击该属性旁边的省略号 (...) 按钮。
- 在“绑定”对话框中,从“绑定源”中选择 "Form",然后从“绑定表达式”中选择 "currentOrderRecord" > "orderDate"。
- 选择 Product 的组合框,然后单击
elements 属性的省略号 (...) 按钮。
- 在“绑定”对话框中,单击“将数据导入到窗体”。
- 选择 MyBusinessRecords 数据库连接,然后选择
products 表。
在将该数据导入到窗体时,将生成 Products 的 List 对象的代码。然后,将该对象 (listProducts) 设置为绑定源。
- 将绑定表达式保留为
null,然后单击“确定”。
- 单击组合框的
selectedItem 属性的省略号 (...) 按钮。
- 从“绑定源”中选择 "Form",然后从“绑定表达式”中选择 "currentOrderRecord" > "productId"。
- 选择 Quantity 文本字段。
- 单击
text 属性的省略号 (...) 按钮。
- 从“绑定源”中选择 "Form",然后从“绑定表达式”中选择 "currentOrderRecord" > "quantity"。
- 选择 Price 的带格式字段。
- 单击
value 属性的省略号 (...) 按钮。
- 从“绑定源”中选择 "Form",然后从“绑定表达式”中选择 "
${currentOrderRecord.productId.price}"。
- 选择 Total 的带格式字段。
- 单击
value 属性的省略号 (...) 按钮。
- 从“绑定源”中选择 "Form",然后在“绑定表达式”中键入
${currentOrderRecord.productId.price*currentOrderRecord.quantity}。
通过使用该定制绑定表达式,您可以将选定项的价格与用户选择的数量相乘以生成订单的总价格。
设置日期和货币格式
对于订单日期、价格和总计,必须添加带格式的文本字段,这可以轻松地提供格式。
为这些字段设置日期和货币格式:
- 选择 Date 的带格式字段。
- 在“属性”窗口中,选择“属性”类别。
- 单击
formatterFactory 属性旁边的省略号 (...) 按钮。
- 在 formatterFactory 属性编辑器中,选择“类别”列中的
date。然后选择“格式”列中的 Default。
- 单击“确定”退出该对话框。
- 选择 Price 的带格式字段。
- 在“属性”窗口中,清除
editable 属性。(您不希望用户编辑此字段。将从在 Product 组合框中选择的项的 price 属性派生该字段的值。)
- 在 formatterFactory 属性编辑器中,选择“类别”列中的
currency。然后选择“格式”列中的 Default。
- 单击“确定”退出该对话框。
- 对于 Total 带格式字段,请执行步骤 6-9。
现在,运行应用程序并选择一项时,Price 和 Currency 字段将会以货币形式进行显示。
呈现 Product 组合框
现在,您需要修改 Product 组合框的呈现方式。与 "New Customer" 对话框中的 Country 组合框一样,您需要使用定制单元格呈现器。在该单元格呈现器中,将会合并多个属性中的信息,以使用户能够看到为每项列出的类型、品牌和型号。
将单元格呈现器应用于产品:
- 右键单击 ProductListCellRenderer 类,然后选择“编译文件”。
- 在“设计”视图中打开 OrderEditor 窗体。
- 使用与 CountryListCellRenderer 相同的方式,将该类从“项目”窗口拖到窗体周围的空白区域中。
- 在窗体中选择该组合框。
- 在“属性”窗口中,滚动至
renderer 属性,然后从该属性的下拉列表中选择 "productListCellRenderer1"。
在 Order 对话框中预填充数据
由于该对话框用于输入新订单,应用程序用户可能会输入当前日期的订单。为省去用户输入该日期的麻烦,可以在该字段中预填充当前日期。
类似地,应该更改 Quantity 缺省值,因为 0 是无效的值。您可以在此处创建一个验证器,但目前最好直接设置一个合理的缺省值,这样既简便又实用。
预填充 "Order Editor" 对话框中的 Date 和 Quantity 字段:
- 打开 CustomerRecordsView 类并选择“源”视图。
- 导航至
newDetailRecord() 方法。
- 在
customerrecords.Orders o = new customerrecords.Orders(); 行下面,粘贴以下几行:
o.setOrderDate(new java.util.Date());
o.setQuantity(1);
现在,运行应用程序时,将会在打开的对话框上的 Date 字段中显示当前日期,并且缺省数量应该为 1。
激活对话框中的 "Save" 和 "Cancel" 按钮
现在,您需要完成为对话框和主窗体之间关联编写代码的工作。
激活 Customer Editor 的 "Save" 和 "Cancel" 按钮
首先,将 CustomerEditor 对话框中的按钮与相应的事件处理代码关联。您已经具有随框架应用程序提供的 save() 和 refresh() 操作。可以为该对话框编写代码,以使这些按钮重用这些操作。可通过在对话框中设置一个布尔属性来完成此操作:在按 "Save Record" 按钮时,该属性返回 true;在选择 "Cancel" 时,该属性返回 false。根据关闭对话框时返回的值,将从 CustomerRecordsView 类中运行 save() 或 refresh() 操作。
要设置该属性,请执行以下操作:
- 打开 CustomerEditor 文件并选择“源”视图。
- 将光标放在
propertyChangeSupport 字段的上方。
- 按 Alt-Insert 组合键,然后选择“添加属性”。
- 在“添加属性”对话框中,键入
customerConfirmed 作为属性名称。
- 将类型设置为布尔类型。
- 确保选中了“生成 getter 和 setter”复选框。
- 单击“确定”关闭该对话框并生成代码。
将在这些按钮的事件处理代码中设置该属性的值。
创建事件侦听程序和处理程序:
- 切换到 CustomerEditor 类的“设计”视图。
- 在 CustomerEditor 窗体中选择 "Save" 按钮。
- 在“属性”窗口中,单击“事件”按钮。
- 单击
actionPerformed 属性旁边的省略号 (...) 按钮。
- 在“actionPerformed 的处理程序”对话框中,添加一个名为
saveCustomer 的处理程序。
- 在源代码编辑器中的
saveCustomer 方法内(创建新处理程序后光标跳转到的位置),键入以下代码:
setCustomerConfirmed(true);
setVisible(false);
- 对于 "Cancel" 按钮,重复步骤 2-5,并将其处理程序命名为
cancelCustomer。
- 在
cancelCustomer 方法中,键入以下内容:
setCustomerConfirmed(false);
setVisible(false);
在 CustomerRecordsView 类中,导航至 newRecord() 方法,并在该方法底部添加以下代码:
if (ce.isCustomerConfirmed()) {
save().run();
} else {
refresh().run();
}
由于 save() 和 refresh() 操作应用于在应用程序会话期间进行的任何更改,因此,应该将对话框设置为模态对话框,并将主窗体中的表设置为不可编辑。将对话框设置为模态对话框的另一个原因是:在用户按 "Save" 或 "Cancel" 按钮时,setVisible() 方法在运行事件处理程序(它包括 setCustomerConfirmed 方法)后才会返回内容。
将对话框设置为模态对话框:
- 打开 CustomerEditor 类的“设计”视图。
- 选择该对话框。
- 在“属性”窗口中,单击“属性”,然后选中
modal 属性的复选框。
将主窗体的 Customers 表设置为不可编辑:
- 打开 CustomerRecordsView 类并选择“设计”视图。
- 右键单击顶部的表,然后选择“表内容”。
- 在“定制器”对话框中,选择“列”标签。
- 对于每个列,请清除“可编辑”复选框。
- 单击“关闭”。
现在,您应该能够在 Customer Editor 中创建和保存新记录了。还应该可以在 Customer Editor 中创建和取消新记录。
在 RefreshTask 内部类中,将调用四次 Thread.sleep 以减慢回滚代码的速度,以便更好地说明 Swing 应用程序框架任务的工作方式。此应用程序不需要使用该代码,因此,请删除这四条语句。类似地,也不需要使用此处的 try/catch 块,因此,也要删除 try 和 catch 语句(但要保留 try 块主体的其余内容)。
激活 Order Editor 的 "Save" 和 "Cancel" 按钮
与 Customer Editor 一样,您需要将 OrderEditor 对话框中的按钮与随框架应用程序提供的 save() 和 refresh() 操作相关联。与前面执行的操作一样,可通过在对话框中设置一个布尔属性来完成此操作:在按 "Save Record" 按钮时,该属性返回 true;在选择 "Cancel" 时,该属性返回 false。根据关闭对话框时返回的值,将从 CustomerRecordsView 类中运行 save() 或 refresh() 操作。
要设置该属性,请执行以下操作:
- 打开 OrderEditor 文件并选择“源”视图。
- 将光标放在
propertyChangeSupport 字段的上方。
- 按 Alt-Insert 组合键,然后选择“添加属性”。
- 在“添加属性”对话框中,键入
orderConfirmed 作为属性名称。
- 将类型设置为布尔类型。
- 确保选中了“生成 getter 和 setter”复选框。
- 单击“确定”关闭该对话框并生成代码。
将在这些按钮的事件处理代码中设置该属性的值。
为这些按钮创建事件处理代码:
- 切换到 OrderEditor 类的“设计”视图。
- 在 OrderEditor 窗体中选择 "Save" 按钮。
- 在“属性”窗口中,单击“事件”按钮。
- 单击 actionPerformed 属性旁边的省略号 (...) 按钮。
- 在“actionPerformed 的处理程序”对话框中,添加一个名为
saveOrder 的处理程序。
- 在源代码编辑器中的
saveOrder 方法内(创建新处理程序后光标跳转到的位置),键入以下代码:
setOrderConfirmed(true);
setVisible(false);
- 对于 "Cancel" 按钮,重复步骤 2-5,并将其处理程序命名为
cancelOrder。
- 在
cancelOrder 方法中,键入以下内容:
setOrderConfirmed(false);
setVisible(false);
在 CustomerRecordsView 类中,导航至 newDetailRecord() 方法,并在该方法底部添加以下代码:
if (oe.isOrderConfirmed()) {
save().run();
} else {
refresh().run();
}
将对话框设置为模态对话框:
- 打开 OrderEditor 类的“设计”视图。
- 选择该对话框。
- 在“属性”窗口中,单击“属性”,然后选中 modal 属性的复选框。
将主窗体的 Orders 表设置为不可编辑:
- 在源代码编辑器中打开 CustomerRecordsView 类,然后选择“设计”视图。
- 右键单击底部的表,然后选择“表内容”。
- 在“定制器”对话框中,选择“列”标签。
- 对于每个列,请清除“可编辑”复选框。
- 单击“关闭”。
验证记录删除
您已经将大多数主要操作的访问位置从主窗体移至对话框。唯一的例外是 Delete 操作。以前,在应用程序中通过按 "Save" 来确认删除以及按 "Refresh" 来取消删除。由于 "Save" 和 "Refresh" 按钮不再位于主窗体中,因此需要替换此功能。可通过在 deleteRecord() 和 deleteDetailRecord() 方法中添加确认对话框来实现此操作。如果用户单击 "OK",则会调用 save() 方法,因而将永久删除记录。如果用户单击 "Cancel",则会调用 refresh() 方法并恢复记录。
在 "Delete" 按钮中添加确认对话框:
- 在“源”视图中打开主窗体。
- 删除
deleteRecord() 方法并将其替换为以下代码:
@Action(enabledProperty = "recordSelected")
public void deleteRecord() {
Object[] options = {"OK", "Cancel"};
int n = JOptionPane.showConfirmDialog(null, "Delete the records permanently?", "Warning",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null);
if (n == JOptionPane.YES_OPTION) {
int[] selected = masterTable.getSelectedRows();
List<customerrecords.Customers> toRemove = new ArrayList<customerrecords.Customers>(selected.length);
for (int idx = 0; idx < selected.length; idx++) {
customerrecords.Customers c = list.get(masterTable.convertRowIndexToModel(selected[idx]));
toRemove.add(c);
entityManager.remove(c);
}
list.removeAll(toRemove);
save().run();
} else {
refresh().run();
}
}
- 按 Ctrl-Shift-I 组合键以添加缺少的 import 语句。
- 删除
deleteDetailRecord() 方法并将其替换为新版本:
@Action(enabledProperty = "detailRecordSelected")
public void deleteDetailRecord() {
Object[] options = {"OK", "Cancel"};
int n = JOptionPane.showConfirmDialog(null, "Delete the records permanently?", "Warning",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null);
if (n == JOptionPane.YES_OPTION) {
int index = masterTable.getSelectedRow();
customerrecords.Customers c = list.get(masterTable.convertRowIndexToModel(index));
List<customerrecords.Orders> os = c.getOrdersCollection();
int[] selected = detailTable.getSelectedRows();
List<customerrecords.Orders> toRemove = new ArrayList<customerrecords.Orders>(selected.length);
for (int idx = 0; idx < selected.length; idx++) {
selected[idx] = detailTable.convertRowIndexToModel(selected[idx]);
int count = 0;
Iterator<customerrecords.Orders> iter = os.iterator();
while (count++ < selected[idx]) {
iter.next();
}
customerrecords.Orders o = iter.next();
toRemove.add(o);
entityManager.remove(o);
}
os.removeAll(toRemove);
masterTable.clearSelection();
masterTable.setRowSelectionInterval(index, index);
list.removeAll(toRemove);
save().run();
} else {
refresh().run();
}
}
在对话框中添加编辑功能
现在,您可以运行应用程序并单击 "New Customers" 以添加新记录。在 "New Customers" 对话框中按 "Save" 时,将保存记录。按 "Cancel" 时,将回滚已更改的新记录。
不过,无法再编辑现有记录,因为已禁止编辑主窗体中的表。为解决该问题,将在主客户窗体中添加编辑按钮,以便编辑现有记录。对于事件处理,您将利用 Swing 应用程序框架的“操作”工具。
将客户记录设置为可编辑
要添加按钮及其相应的事件处理代码,请执行以下操作:
- 打开 CustomerRecordsView 类并选择“设计”视图。
- 将 "New Customers" 按钮略微向左拖动。
- 将一个按钮从组件面板拖到刚挪出的空白区域。
- 右键单击按钮,然后选择“设置操作”。
- 在“操作”字段中选择“创建新操作”。
- 在“操作方法”中键入
editCustomer。
- 在“文本”中键入
Edit Customer。
- 单击“高级”标签,然后从“启用的属性”中选择
recordSelected。
这会生成一个标注属性,以确保仅在选择记录时才会启用操作的按钮和任何其他触发器(如菜单项)。
- 单击“确定”关闭该对话框。
将会显示该文件的“源”视图,并且光标位于新生成的 editCustomer() 方法中。
- 在该方法中粘贴以下代码:
setSaveNeeded(true);
JFrame mainFrame = CustomerRecordsApp.getApplication().getMainFrame();
CustomerEditor ce = new CustomerEditor(mainFrame, false);
ce.setCurrentRecord(list.get(masterTable.convertRowIndexToModel(masterTable.getSelectedRow())));
ce.setVisible(true);
if (ce.isCustomerConfirmed()) {
save().run();
} else {
refresh().run();
}
该代码的绝大部分是直接从 newRecord 操作中复制的。主要差别是 ce.setCurrentRecord(list.get(masterTable.convertRowIndexToModel(masterTable.getSelectedRow()))); 行,它使用当前选择的记录填充对话框中的当前记录。

几乎完全设置了应用程序的 Customer 部分。现在,您应该能够使用创建的专用 GUI,在 CUSTOMERS 表中任意添加、编辑和删除记录。
将订单记录设置为可编辑
要添加 "Edit Orders" 按钮及其相应的事件处理代码,请执行以下操作:
- 在 CustomerRecordsView 类的“设计”视图中,删除 "Refresh" 和 "Save" 按钮。
- 向右拖动 "Delete" 按钮。
- 将一个按钮从组件面板拖到刚挪出的空白区域。
- 右键单击按钮,然后选择“设置操作”。
- 在“操作”字段中选择“创建新操作”。
- 在“操作方法”中键入
editOrder。
- 在“文本”中键入
Edit Order。
- 单击“高级”标签,然后选择“启用的属性”中的
detailRecordSelected。
- 单击“确定”关闭该对话框。
将会显示该文件的“源”视图,并且光标位于新的 editOrder() 方法中。
- 在
editOrder 方法中粘贴以下代码:
setSaveNeeded(true);
int index = masterTable.getSelectedRow();
customerrecords.Customers c = list.get(masterTable.convertRowIndexToModel(index));
List<customerrecords.Orders> os = c.getOrdersCollection();
JFrame mainFrame = CustomerRecordsApp.getApplication().getMainFrame();
OrderEditor oe = new OrderEditor(mainFrame, false);
oe.setCurrentOrderRecord(os.get(detailTable.getSelectedRow()));
oe.setVisible(true);
if (oe.isOrderConfirmed()) {
save().run();
} else {
refresh().run();
}
现在,在运行应用程序时,所有关键元素均已创建完毕。您可以创建、检索、更新和删除客户和订单的记录。在下面的屏幕快照中,您可以看到在选择记录并按 "Edit Order" 按钮时显示的 "Order Editor" 对话框。

下一节介绍了可以执行的其他一些操作,以改进和微调应用程序。
货币呈现、日期验证和搜索
现在,您已经创建了一个完全正常工作的应用程序。不过,仍然可以使用很多方法来改进应用程序。下面举例说明了一些可改进应用程序的方法。
在主视图中呈现货币
您已经在 Orders Editor 中处理了日期和货币的格式,但还没有对 CustomerRecordsView 类进行相应的处理。您不需要对日期字段执行任何操作。该格式将会在 Order Editor 和主窗体之间有效地进行传递。不过,这不适用 Price 字段。您需要添加货币呈现器类以正确呈现该字段。
在主视图中使用货币格式呈现 Price 字段:
- 在“项目”窗口中,右键单击
CurrencyCellRenderer 类,然后选择“编译文件”。
- 打开 CustomerRecordsView 类并切换到“设计”视图。
- 将
CurrencyCellRenderer 类从“项目”窗口拖放到窗体周围的空白区域中。
将会在“检查器”窗口中显示一个名为 currencyCellRenderer1 的节点。
- 右键单击窗体下方的表,然后选择“表内容”。
- 单击“列”标签。
- 选择
price 列。
- 在“呈现器”组合框中,选择
currencyCellRenderer1。
现在,运行应用程序时,将以美元符号 ($) 显示价格、带有小数点和两位小数。
添加搜索功能
现在,将为客户表添加搜索功能。所使用的是 Swing 和 Bean 绑定库中已存在的机制。将会在主表的 rowSorter 属性和搜索字符串的文本字段之间创建绑定。对于这种绑定,您需要使用绑定转换器,以使表知道如何响应搜索字符串。
首先,为搜索字段添加一个标签和文本字段,如下所示。

现在,将在项目中添加一个转换器类。
- 在“项目”窗口中,右键单击
RowSorterToStringConverter 类,然后选择“编译文件”。
- 将该类从“项目”窗口拖放到窗体周围的空白区域中。
- 在“检查器”窗口中,选择
rowSorterToStringConverter1 节点,并将其 table 属性设置为 masterTable。
在创建绑定时,将会使用该转换器。
创建绑定:
- 在主窗体中,右键单击 "Search" 文本字段,然后选择“绑定”> "text"。
- 在“绑定”对话框中,从“绑定源”中选择
masterTable,然后从“绑定表达式”中选择 rowSorter。
- 单击对话框的“高级”标签。
- 从“转换器”组合框中,选择
rowSorterToStringConverter1。
- 单击“确定”关闭该对话框并生成绑定代码。
现在,运行应用程序时,您应该能够在搜索过滤器字段中键入内容,并且会看到行列表范围缩小,而仅显示包含的文本与键入内容匹配的行。

不过,添加此搜索功能也会带来副作用。如果使用搜索并随后单击 "New Customer",则会抛出异常,因为用于选择新行的代码是根据数据库表中的记录数确定的,而不是根据表中当前显示的记录数确定的。
修复异常:
验证日期格式
尽管 "Order Editor" 对话框中包含日期格式设置程序,但尚未指定在用户未正确输入日期时执行的操作。缺省行为是返回以前输入的有效日期。要定制此行为,可以为该字段指定一个验证器。您可以使用此项目的实用程序类中包含的 DateVerifier 类。
将日期验证器与 "Order Date" 列相关联:
- 在“项目”窗口中,右键单击 DateVerifier 类,然后选择“编译文件”。
- 打开 OrderEditor 并切换到“设计”视图。
- 将 DateVerifier 类从“项目”窗口拖到窗体周围的空白区域中。
- 选择 Date 的带格式文本字段。
- 在“属性”窗口中,单击“属性”标签,然后从“输入验证器”属性的组合框中选择
dateVerifier1。此属性将显示在该标签的“其他属性”类别中。
另请参见
有关使用 IDE 的 GUI 生成器的更一般性介绍,请参见生成 GUI 应用程序简介。
有关更全面的 GUI 生成器设计功能指南(包括各种功能的视频演示),请参见在 NetBeans IDE 中设计 Swing GUI。
要了解可以如何使用 Java 桌面应用程序项目模板生成带有主视图/详细视图的数据库应用程序,请参见生成 Java 桌面数据库应用程序。
有关在 IDE 中开发 Java 应用程序的其他信息,请参见基本 IDE 和 Java 编程学习资源。
有关 Bean 绑定的详细信息,请参见 java.net 上的 Bean 绑定项目页。
有关 IDE 中 Bean 绑定的详细信息,请参见在桌面应用程序中绑定 Bean 和数据。
有关将 Hibernate 用于 Swing 应用程序的持久性层的信息,请参见在 Java Swing 应用程序中使用 Hibernate。
有关在 NetBeans IDE 中使用 GUI 生成器的一般提示和技巧,请参见 GUI 编辑器常见问题解答和 Patrick Keegan 的 Web 日志。