【JavaFx教程】第五部分:将数据用 XML 格式存储
第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输出
<persons>
<person>
<birthday>1999-02-21</birthday>
<city>some city</city>
<firstName>Hans</firstName>
<lastName>Muster</lastName>
<postalCode>1234</postalCode>
<street>some street</street>
</person>
<person>
<birthday>1999-02-21</birthday>
<city>some city</city>
<firstName>Anna</firstName>
<lastName>Best</lastName>
<postalCode>1234</postalCode>
<street>some street</street>
</person>
</persons>
使用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<Person> persons; @XmlElement(name = "person")
public List<Person> getPersons() {
return persons;
} public void setPersons(List<Person> 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菜单项。
提示:使用*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下,应该看到所有可用控制器方法的选择。为每个菜单项选择响应的方法。
为每个菜单项重复第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
文件,你将注意到生日没有正确保存,这是一个空的<birthday/>
标签。原因是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<String, LocalDate> { @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
【JavaFx教程】第五部分:将数据用 XML 格式存储的更多相关文章
- Java POI 读取Excel数据转换为XML格式
1.首先要下载poi相关的包:http://poi.apache.org/ ,以下是所需的jar包 2.贴上详细的代码 public class ExcelToXml { /** * 将excel的 ...
- yii2.0 数据生成 XML 格式。
return 部分的数组就是你要生成 xml 的数据. 生成的格式如下: yii2.0 中使用 xml 就是这么简单!
- 调用webservice,解析返回数据为xml格式的字符串,进行数据绑定
DataSet ds = new DataSet(); byte[] byteArray = System.Text.Encoding.Unicode.GetBytes("<?xml ...
- C# 获取接口数据(xml格式)转为json格式
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net ...
- Oracle表数据转换为XML格式数据
转自:https://blog.csdn.net/smile_caijx/article/details/83352927 使用DBMS_XMLGEN可以解决问题 SELECT DBMS_XMLGEN ...
- python--利用列表推导式快速生成xml格式数据
在接口自动化测试中,我们经常将要发送的数据放到excel里. json数据放至excel方便,但最近的一个测试,数据是xml格式发送的 如下: 属性 必选/可选 描述 1. Message Eleme ...
- 使用jackson转换xml格式数据进行响应
最近在做微信扫码支付的功能,按照微信开发文档与支付平台进行数据交互只能使用XML格式的数据,调用别人定义的接口就需要按规则来嘛,没办法.自己之前使用jackson,主要是因为SpringMVC默认将j ...
- WCF入门教程(五)配置文件
WCF入门教程(五)配置文件 服务协定以及实现写好后,需要将相关服务公布出去,就需要HOST来承载,供客户端来调用. 承载服务有两种方式,一种通过配置文件,一种通过代码进行配置.上一章已经介绍了代码方 ...
- Netty4.x中文教程系列(五)编解码器Codec
Netty4.x中文教程系列(五)编解码器Codec 上一篇文章详细解释了ChannelHandler的相关构架设计,版本和设计逻辑变更等等. 这篇文章主要在于讲述Handler里面的Codec,也就 ...
随机推荐
- Java基础巩固——反射
什么是反射 反射机制就是指程序运行时能够获取自身的信息.在Java中,只要给出类的名字,就可以通过反射机制来获取类的信息 哪里用的到反射机制 在jdbc中就是使用的反射来实例化对象,比如:Class. ...
- .NetCore WebApi + Vue +MySql搭建博客
因为我是一直写C#的,所以最近闲暇时间一直在学习.NET Core,该博客的后端使用的就是.NET Core WebApi然后加前端Vue. 首先后端.NET Core搭的框架是一个仓储层+服务层+A ...
- 关于nginx部署vue项目的两个问题
首先我使用的是后端接口+前端vue的形式,这样就涉及到跨域的问题.我是这样配置的: server { listen 80; server_name www.liangyp.xyz;//访问网址 loc ...
- 【Atcoder】 AGC032赛后总结
比赛前 emmm,今天是场AGC,想起上次我的惨痛经历(B都不会),这次估计要凉,可能A都不会Flag1 比赛中 看场看了波\(A\),咦,这不是很呆的题目吗?倒着扫一遍就好了. 然后切了就开始看B, ...
- 【BZOJ1052】 [HAOI2007]覆盖问题
BZOJ1052 [HAOI2007]覆盖问题 前言 小清新思维题. 最近肯定需要一些思维题挽救我这种碰到题目只会模板的菜鸡. 这题腾空出世? Solution 考虑一下我们二分答案怎么做? 首先转换 ...
- UOJ#419. 【集训队作业2018】圆形(格林公式)
题面 传送门 题解 首先您得会用格林公式计算圆的面积并 这里只需要动态维护一下圆弧就可以了 时间复杂度\(O(n^2\log n)\) //minamoto #include<bits/stdc ...
- SO_REUSEADDR SO_REUSEPORT
http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they ...
- JDK中ConcurrentHashMap效率测试
比较HashMap HashTable 和ConcurrentHashMap的效率. 一般情况下,达到一定的数量之后JDK1.5之后提供的ConcurrentHashMap集合类的效率是前两者的3~4 ...
- Hadoop环境搭建及wordcount程序
目的: 前期学习了一些机器学习基本算法,实际企业应用中算法是核心,运行的环境和数据处理的平台是基础. 手段: 搭建简易hadoop集群(由于机器限制在自己的笔记本上通过虚拟机搭建) 一.基础环境介绍 ...
- Identity Server4学习系列一
一.前言 今天开始学习Identity Server4,顺便了解下.Net Core,以便于完善技术栈,最主要的是要跟上.Net的发展潮流,顺便帮助各位整理下官方文档,加上一些我自己对他的理解. 这是 ...