在 JavaFx 上保存/加载窗格绘图
Posted
技术标签:
【中文标题】在 JavaFx 上保存/加载窗格绘图【英文标题】:Save/Load a Pane drawing on JavaFx 【发布时间】:2020-11-13 16:19:44 【问题描述】:我创建了一个绘图应用程序,我在其中绘制形状并将它们作为节点添加到窗格中。类似于下面的代码:
currentShape = new Rectangle();
shapeList.add(currentShape);
pane.getChildren().addAll(shapelist);
我想保存一个绘图并能够加载该保存的绘图。我不知道如何解决这个问题。我查看了使用以下类型的快照/可写图像的示例。然而,这给了我一个Cannot Resolve SwingFXUtils
错误
save.setOnAction(event ->
FileChooser fileChooser = new FileChooser();
//Set extension filter
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("png files (*.png)", "*.png"));
//Prompt user to select a file
File f = fileChooser.showSaveDialog(null);
if(f != null)
try
//Pad the capture area
WritableImage writableImage = new WritableImage((int) pane.getWidth(), (int) pane.getHeight());
pane.snapshot(null, writableImage);
RenderedImage renderedImage = SwingFXUtils.fromFXImage(writableImage, null);
//Write the snapshot to the chosen file
ImageIO.write(renderedImage, "png", f);
catch (IOException ex)
ex.printStackTrace();
);
如果有人对如何在窗格上保存和加载绘图有更好的建议,我将不胜感激。
【问题讨论】:
您需要导入 SwingFXUtils,就像您要调用其方法的任何其他类一样。至于保存,如果您只是保存快照,您将丢失有关各个形状的所有信息;你能接受吗? 导入像import javafx.embed.swing.SwingFXUtils;
这样的SwingFXUtils 会给我以下错误Cannot resolve symbol embed
。不,我想保留这些信息,因为我需要加载相同的形状
如果您使用的是 JavaFX 9+,请注意 SwingFXUtils
位于 javafx.swing
模块中,该模块必须在模块路径上解析,如果您的代码是模块化的,则模块需要该模块。但是快照不是正确的方法。您应该设计了一个模型来表示屏幕上显示的内容。这是您保存的模型。你如何做到这一点取决于你。例如,您可以将模型状态保存到 XML 或 JSON 文件。当您加载 XML/JSON 文件时,您重建模型,然后然后您根据模型的状态创建视图。
相关问题:Serialize JavaFX components.
【参考方案1】:
Slaw 是对的:到目前为止,最好的方法是创建您自己的模型对象,这些对象代表您的应用程序中显示的内容。
但是,如果您想尝试直接编写和读取 JavaFX Shapes,您可以选择:XML bean 序列化。
XML bean 序列化由XMLEncoder 和XMLDecoder 类执行。
与常规 Java 序列化不同,它们不查看字段,只查看 bean 属性。 bean 属性由公共读取方法定义,它是一个以get
开头的零参数方法(或者,如果它返回原始boolean
,它可以选择以is
开头而不是get
)。
因此,getWidth()
方法的存在定义了一个名为 width
的属性。
如果有一个对应的set
方法(在上面的例子中是setWidth
),它只接受一个与get方法返回的相同类型的参数,则该属性被定义为可写属性.
(完整的规则比这复杂一点;我只描述了一般情况。完整的 JavaBeans 规范是here。)
如果您查看过 JavaFX 的 javadoc,您可能已经注意到 JavaFX 类定义了很多属性。这意味着您可以通过以下方式保存您的 pane
孩子:
private static final java.nio.file.Path SAVE_FILE_LOCATION =
Paths.get(System.getProperty("user.home"), "shapes.xml");
void save()
throws IOException
try (XMLEncoder encoder = new XMLEncoder(
new BufferedOutputStream(
Files.newOutputStream(SAVE_FILE_LOCATION))))
encoder.setExceptionListener(e ->
throw new RuntimeException(e);
);
encoder.writeObject(pane.getChildren().toArray(new Node[0]));
void load()
throws IOException
try (XMLDecoder decoder = new XMLDecoder(
new BufferedInputStream(
Files.newInputStream(SAVE_FILE_LOCATION))))
decoder.setExceptionListener(e ->
throw new RuntimeException(e);
);
pane.getChildren().setAll((Node[]) decoder.readObject());
然而,XMLEncoder 可以知道和直觉关于类的内容是有限的。例如,在上面的代码中,我使用了 Node
数组而不是原始的 ObservableList,因为 XMLEncoder 不知道如何序列化 ObservableList 的任何(未公开记录的)具体实现。但是,XMLEncoder 确实具有序列化数组的内置功能,只要它可以序列化数组元素本身。
一个更重要的问题是它不知道如何序列化某些属性并且会忽略它们。例如,Color 不是一个典型的 Java bean:它是只读的,所以虽然 XMLEncoder 可以读取它的数据,但没有设置方法,所以编码器不知道未来的 XMLDecoder 能够编写什么指令用于创建等效的 Color 对象。
我们可以通过providing it with custom PersistenceDelegates 自定义一个 XMLEncoder。方便的是,DefaultPersistenceDelegate 子类允许将 bean 属性名称传递给构造函数,该构造函数创建一个委托,该委托将告诉 XMLDecoder 寻找一个构造函数,该构造函数接受与原始写入数据中的每个属性对应的参数。
由于 Color 具有 a four-argument constructor,它采用 red、green、blue 和 opacity 属性的值,我们可以将 DefaultPersistenceDelegate 添加到 XMLEncoder,它指示未来的 XMLDecoders 使用这些属性' 重构 Color 对象时的值:
encoder.setPersistenceDelegate(Color.class,
new DefaultPersistenceDelegate(
new String[] "red", "green", "blue", "opacity" ));
上面的意思是:“当写一个Color对象时,写指令让未来的解码器在Color类中寻找一个接受四个双精度值的构造函数,然后通过调用Color对象的getRed写出实际值在未来传递, getGreen、getBlue 和 getOpacity 方法。”
如果您希望您的形状将包含 Text 对象,您可以为 Font 类添加持久性委托:
encoder.setPersistenceDelegate(Font.class,
new DefaultPersistenceDelegate(
new String[] "name", "size" ));
您还可以为其他 Paint 实现添加持久性委托:
encoder.setPersistenceDelegate(LinearGradient.class,
new DefaultPersistenceDelegate(new String[]
"startX", "startY", "endX", "endY",
"proportional", "cycleMethod", "stops"
));
encoder.setPersistenceDelegate(RadialGradient.class,
new DefaultPersistenceDelegate(new String[]
"focusAngle", "focusDistance", "centerX", "centerY",
"radius", "proportional", "cycleMethod", "stops"
));
(我特意省略了ImagePattern,因为虽然可以用XML表示一个Image,但它很丑而且效率很低。如果你打算存储图像,XML不是一个好的存储格式。)
因此,加载和存储方法的更新版本如下所示:
private static void addPersistenceDelegatesTo(Encoder encoder)
encoder.setPersistenceDelegate(Font.class,
new DefaultPersistenceDelegate(
new String[] "name", "size" ));
encoder.setPersistenceDelegate(Color.class,
new DefaultPersistenceDelegate(
new String[] "red", "green", "blue", "opacity" ));
encoder.setPersistenceDelegate(LinearGradient.class,
new DefaultPersistenceDelegate(new String[]
"startX", "startY", "endX", "endY",
"proportional", "cycleMethod", "stops"
));
encoder.setPersistenceDelegate(RadialGradient.class,
new DefaultPersistenceDelegate(new String[]
"focusAngle", "focusDistance", "centerX", "centerY",
"radius", "proportional", "cycleMethod", "stops"
));
private static final java.nio.file.Path SAVE_FILE_LOCATION =
Paths.get(System.getProperty("user.home"), "shapes.xml");
void save()
throws IOException
try (XMLEncoder encoder = new XMLEncoder(
new BufferedOutputStream(
Files.newOutputStream(SAVE_FILE_LOCATION))))
encoder.setExceptionListener(e ->
throw new RuntimeException(e);
);
addPersistenceDelegatesTo(encoder);
encoder.writeObject(pane.getChildren().toArray(new Node[0]));
void load()
throws IOException
try (XMLDecoder decoder = new XMLDecoder(
new BufferedInputStream(
Files.newInputStream(SAVE_FILE_LOCATION))))
decoder.setExceptionListener(e ->
throw new RuntimeException(e);
);
pane.getChildren().setAll((Node[]) decoder.readObject());
【讨论】:
以上是关于在 JavaFx 上保存/加载窗格绘图的主要内容,如果未能解决你的问题,请参考以下文章
使用 PixelWriter 在 JavaFX Canvas 上进行透明绘图