在 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 上进行透明绘图

处理绘图/绘画/保存多个位图

IOS:用他的触摸和点击能力保存子视图

qwt 保存绘图配置?

在android中录制视频时如何在视频上绘图,并保存视频和绘图?

如何将绘图保存为磁盘上的图像?