java使用xml保存数据类型_【JavaFx教程】第五部分:将数据用 XML 格式存储

本教程介绍如何在JavaFX应用中使用XML进行数据持久化。通过Preferences类保存应用状态,如最后打开的文件路径。使用JAXB库将Person对象转换为XML,简化简单数据模型的存储。教程中详细展示了创建XML读写方法、处理菜单响应以及使用FileChooser的步骤。
摘要由CSDN通过智能技术生成

a65feda6da3621e6b2fdb2b5a24e51f9.png

第5部分的主题

持久化数据为XML

使用JavaFX的FileChooser

使用JavaFX的菜单

在用户设置中保存最后打开的文件路径。

现在我们的地址应用程序的数据只保存在内存中。每次我们关闭应用程序,数据将丢失,因此是时候开始考虑持久化存储数据了。

保存用户设置

Java允许我们使用Preferences类保存一些应用状态。依赖于操作系统,Perferences保存在不同的地方(例如:Windows中的注册文件)。

我们不能使用Preferences来保存全部地址簿。但是它允许我们保存一些简单的应用状态。一件这样事情是最后打开文件的路径。使用这个信息,我们能加载最后应用的状态,不管用户什么时候重启应用程序。

下面两个方法用于保存和检索Preference。添加它们到你的MainApp类的最后:

MainApp.java

/**

* Returns the person file preference, i.e. the file that was last opened.

* The preference is read from the OS specific registry. If no such

* preference can be found, null is returned.

*

* @return

*/

public File getPersonFilePath() {

Preferences prefs = Preferences.userNodeForPackage(MainApp.class);

String filePath = prefs.get("filePath", null);

if (filePath != null) {

return new File(filePath);

} else {

return null;

}

}

/**

* Sets the file path of the currently loaded file. The path is persisted in

* the OS specific registry.

*

* @param file the file or null to remove the path

*/

public void setPersonFilePath(File file) {

Preferences prefs = Preferences.userNodeForPackage(MainApp.class);

if (file != null) {

prefs.put("filePath", file.getPath());

// Update the stage title.

primaryStage.setTitle("AddressApp - " + file.getName());

} else {

prefs.remove("filePath");

// Update the stage title.

primaryStage.setTitle("AddressApp");

}

}

持久性数据到XML

为什么是XML?

持久性数据的一种最常用的方法是使用数据库。数据库通常包含一些类型的关系数据(例如:表),当我们需要保存的数据是对象时。这称object-relational impedance mismatch。匹配对象到关系型数据库表有很多工作要做。这里有一些框架帮助我们匹配(例如:Hibernate,最流行的一个)。但是它仍然需要相当多的设置工作。

对于简单的数据模型,非常容易使用XML。我们使用称为JAXB(Java Architecture for XML Binding)的库。只需要几行代码,JAXB将允许我们生成XML输出,如下所示:

示例XML输出

1999-02-21

some city

Hans

Muster

1234

some street

1999-02-21

some city

Anna

Best

1234

some street

使用JAXB

JAXB已经包含在JDK中。这意味着我们不需要包含任何其它的库。

JAXB提供两个主要特征:编列(marshal)Java对象到XML的能力,反编列(unmarshal)XML到Java对象。

为了让JAXB能够做转换,我们需要准备我们的模型。

准备JAXB的模型类

我们希望保持的数据位于MainApp类的personData变量中。JAXB要求使用@XmlRootElement注释作为最顶层的类。personData是ObservableList类,我们不能把任何注释放到ObservableList上。因此,我们需要创建另外一个类,它只用于保存Person列表,用于存储成XML文件。

创建的新类名为PersonListWrapper,把它放入到ch.makery.address.model包中。

PersonListWrapper.java

package ch.makery.address.model;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;

import javax.xml.bind.annotation.XmlRootElement;

/**

* Helper class to wrap a list of persons. This is used for saving the

* list of persons to XML.

*

* @author Marco Jakob

*/

@XmlRootElement(name = "persons")

public class PersonListWrapper {

private List persons;

@XmlElement(name = "person")

public List getPersons() {

return persons;

}

public void setPersons(List persons) {

this.persons = persons;

}

}

注意两个注释:

@XmlRootElement 定义根元素的名称。

@XmlElement 一个可选的名称,用来指定元素。

使用JAXB读写数据

我们让MainApp类负责读写人员数据。添加下面两个方法到MainApp.java的最后:

/**

* Loads person data from the specified file. The current person data will

* be replaced.

*

* @param file

*/

public void loadPersonDataFromFile(File file) {

try {

JAXBContext context = JAXBContext

.newInstance(PersonListWrapper.class);

Unmarshaller um = context.createUnmarshaller();

// Reading XML from the file and unmarshalling.

PersonListWrapper wrapper = (PersonListWrapper) um.unmarshal(file);

personData.clear();

personData.addAll(wrapper.getPersons());

// Save the file path to the registry.

setPersonFilePath(file);

} catch (Exception e) { // catches ANY exception

Dialogs.create()

.title("Error")

.masthead("Could not load data from file:\n" + file.getPath())

.showException(e);

}

}

/**

* Saves the current person data to the specified file.

*

* @param file

*/

public void savePersonDataToFile(File file) {

try {

JAXBContext context = JAXBContext

.newInstance(PersonListWrapper.class);

Marshaller m = context.createMarshaller();

m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

// Wrapping our person data.

PersonListWrapper wrapper = new PersonListWrapper();

wrapper.setPersons(personData);

// Marshalling and saving XML to the file.

m.marshal(wrapper, file);

// Save the file path to the registry.

setPersonFilePath(file);

} catch (Exception e) { // catches ANY exception

Dialogs.create().title("Error")

.masthead("Could not save data to file:\n" + file.getPath())

.showException(e);

}

}

*编组和解组*已经准备好,让我们创建保存和加载的菜单实际的使用它。

处理菜单响应

在我们RootLayout.fxml中,这里已经有一个菜单,但是我们没有使用它。在我们添加响应到菜单中之前,我们首先创建所有的菜单项。

在Scene Builder中打开RootLayout.fxml,从*library*组中拖曳必要的菜单到*Hierarchy*组的MemuBar中。创建New,Open…,Save,Save As…和Exit菜单项。

c09ff4a8ffefc3a00b4bdbe505e3d063.png

提示:使用*Properties*组下的*Accelerator*设置,你能设置菜单项的快捷键。

RootLayoutController

为了处理菜单动作,我们需要创建一个新的控制器类。在控制器包ch.makery.address.view中创建一个类RootLayoutController。

添加下面的内容到控制器中:

RootLayoutController.java

package ch.makery.address.view;

import java.io.File;

import javafx.fxml.FXML;

import javafx.stage.FileChooser;

import org.controlsfx.dialog.Dialogs;

import ch.makery.address.MainApp;

/**

* The controller for the root layout. The root layout provides the basic

* application layout containing a menu bar and space where other JavaFX

* elements can be placed.

*

* @author Marco Jakob

*/

public class RootLayoutController {

// Reference to the main application

private MainApp mainApp;

/**

* Is called by the main application to give a reference back to itself.

*

* @param mainApp

*/

public void setMainApp(MainApp mainApp) {

this.mainApp = mainApp;

}

/**

* Creates an empty address book.

*/

@FXML

private void handleNew() {

mainApp.getPersonData().clear();

mainApp.setPersonFilePath(null);

}

/**

* Opens a FileChooser to let the user select an address book to load.

*/

@FXML

private void handleOpen() {

FileChooser fileChooser = new FileChooser();

// Set extension filter

FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(

"XML files (*.xml)", "*.xml");

fileChooser.getExtensionFilters().add(extFilter);

// Show save file dialog

File file = fileChooser.showOpenDialog(mainApp.getPrimaryStage());

if (file != null) {

mainApp.loadPersonDataFromFile(file);

}

}

/**

* Saves the file to the person file that is currently open. If there is no

* open file, the "save as" dialog is shown.

*/

@FXML

private void handleSave() {

File personFile = mainApp.getPersonFilePath();

if (personFile != null) {

mainApp.savePersonDataToFile(personFile);

} else {

handleSaveAs();

}

}

/**

* Opens a FileChooser to let the user select a file to save to.

*/

@FXML

private void handleSaveAs() {

FileChooser fileChooser = new FileChooser();

// Set extension filter

FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(

"XML files (*.xml)", "*.xml");

fileChooser.getExtensionFilters().add(extFilter);

// Show save file dialog

File file = fileChooser.showSaveDialog(mainApp.getPrimaryStage());

if (file != null) {

// Make sure it has the correct extension

if (!file.getPath().endsWith(".xml")) {

file = new File(file.getPath() + ".xml");

}

mainApp.savePersonDataToFile(file);

}

}

/**

* Opens an about dialog.

*/

@FXML

private void handleAbout() {

Dialogs.create()

.title("AddressApp")

.masthead("About")

.message("Author: Marco Jakob\nWebsite: http://code.makery.ch")

.showInformation();

}

/**

* Closes the application.

*/

@FXML

private void handleExit() {

System.exit(0);

}

}

FileChooser

注意在上面的RootLayoutController中使用FileCooser的方法。首先,创建新的FileChooser类对象的,然后,添加扩展名过滤器,以至于只显示以.xml结尾的文件。最后,文件选择器显示在主Stage的上面。

如果用户没有选择一个文件关闭对话框,返回null。否则,我们获得选择的文件,我们能传递它到MainApp的loadPersonDataFromFile(…)或savePersonDataToFile()方法中。

连接fxml视图到控制器

在Scene Builder中打开RootLayout.fxml。在*Controller*组中选择RootLayoutController作为控制器类。

回到*Hierarchy*组中,选择一个菜单项。在*Code*组中On Action下,应该看到所有可用控制器方法的选择。为每个菜单项选择响应的方法。

b12aca0fb2d0f56e4b66fee0d0726eb5.png

为每个菜单项重复第2步。

关闭Scene Builder,并且在项目的根目录上按下刷新F5。这让Eclipse知道在Scene Builder中所做的修改。

连接MainApp和RootLayoutController

在几个地方,RootLayoutController需要引用MainApp类。我们也没有传递一个MainApp的引用到RootLayoutController。

打开MainApp类,使用下面的替代initRootLayout()方法:

/**

* Initializes the root layout and tries to load the last opened

* person file.

*/

public void initRootLayout() {

try {

// Load root layout from fxml file.

FXMLLoader loader = new FXMLLoader();

loader.setLocation(MainApp.class

.getResource("view/RootLayout.fxml"));

rootLayout = (BorderPane) loader.load();

// Show the scene containing the root layout.

Scene scene = new Scene(rootLayout);

primaryStage.setScene(scene);

// Give the controller access to the main app.

RootLayoutController controller = loader.getController();

controller.setMainApp(this);

primaryStage.show();

} catch (IOException e) {

e.printStackTrace();

}

// Try to load last opened person file.

File file = getPersonFilePath();

if (file != null) {

loadPersonDataFromFile(file);

}

}

注意两个修改:一行*给控制器访问MainApp*和最后三行*加载最新打开的人员文件*。

测试

做应用程序的测试驱动,你应该能够使用菜单保存人员数据到文件中。

当你在编辑器中打开一个xml文件,你将注意到生日没有正确保存,这是一个空的标签。原因是JAXB不只奥如何转换LocalDate到XML。我们必须提供一个自定义的LocalDateAdapter定义这个转换。

在ch.makery.address.util中创建新的类,称为LocalDateAdapter,内容如下:

LocalDateAdapter.java

package ch.makery.address.util;

import java.time.LocalDate;

import javax.xml.bind.annotation.adapters.XmlAdapter;

/**

* Adapter (for JAXB) to convert between the LocalDate and the ISO 8601

* String representation of the date such as '2012-12-03'.

*

* @author Marco Jakob

*/

public class LocalDateAdapter extends XmlAdapter {

@Override

public LocalDate unmarshal(String v) throws Exception {

return LocalDate.parse(v);

}

@Override

public String marshal(LocalDate v) throws Exception {

return v.toString();

}

}

然后打开Person.jar,添加下面的注释到getBirthday()方法上:

@XmlJavaTypeAdapter(LocalDateAdapter.class)

public LocalDate getBirthday() {

return birthday.get();

}

现在,再次测试。试着保存和加载XML文件。在重启之后,它应该自动加载最后使用的文件。

它如何工作

让我们看下它是如何一起工作的:

应用程序使用MainApp中的main(…)方法启动。

调用public MainApp()构造函数添加一些样例数据。

调用MainApp的start(…)方法,调用initRootLayout()从RootLayout.fxml中初始化根布局。fxml文件有关于使用控制器的信息,连接视图到RootLayoutController。

MainApp从fxml加载器中获取RootLayoutController,传递自己的引用到控制器中。使用这些引用,控制器随后可以访问MainApp的公开方法。

在initRootLayout方法结束,我们试着从Perferences中获取*最后打开的人员文件*。如果Perferences知道有这样一个XML文件,我们将从这个XML文件中加载数据。这显然会覆盖掉构造函数中的样例数据。

下一步是什么?

在本教程的第6部分,我们添加一个*生日统计图表*。

--------------------- 本文来自 jobbible 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/moshenglv/article/details/82877718?utm_source=copy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值