如何将FileChooser上传的文件保存到你项目的某个目录下?

How to save a file uploaded by FileChooser to a directory in your project?

正如 post 的标题所述,我正在寻求一种方法,让我可以将使用 FileChooser 上传到项目目录的文件说出来。

更具体地说,我正在上传图片,我想将它们保存在我的项目中,以便将来使用。这是一个示例代码:

控制器:

package org.example;

import javafx.fxml.FXML;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.FileChooser;

import java.io.File;

public class PrimaryController {

    @FXML
    private ImageView image;

    @FXML
    void handleUploadImage() {

        FileChooser fileChooser = new FileChooser();
        fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Image Files", "*.jpg", "*.jpeg", "*.png", "*.svg"));
        File file = fileChooser.showOpenDialog(null);
        if (file != null) {
            image.setImage(new Image(String.valueOf(file)));
        } else {
            System.out.println("It's null");
        }
    }
}

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.Cursor?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: linear-gradient(to right, #1c92d2, #f2fcfe);" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.PrimaryController">
   <children>
      <ImageView fx:id="image" fitHeight="200.0" fitWidth="200.0" layoutX="200.0" layoutY="100.0" onMouseClicked="#handleUploadImage" pickOnBounds="true" preserveRatio="true">
         <image>
            <Image url="@../../images/upload.png" />
         </image>
         <cursor>
            <Cursor fx:constant="HAND" />
         </cursor>
      </ImageView>
   </children>
</AnchorPane>

我的目录:

我想将图像保存在/resources/images中。我该怎么做?

背景信息

文件没有被“上传”。该术语通常用于将文件发送到托管云服务或 FTP 站点等操作。您的代码通过本地计算机的文件系统选择一个可用的文件。然后你想用它做点什么。可能将其复制到本地的另一个位置。我想您可能会选择“上传”这个过程,但是,对我来说,这个词似乎不太合适。

通常,当部署一个 JavaFX 应用程序时,它的所有代码和资源都会打包在一起,例如在 jar 文件、安装程序或 jlink 图像中。你的项目源代码的资源目录不会在那里,但它的内容会被复制到包中。在运行时向那个位置添加额外的资源是一个未定义的操作,因为你真的不知道资源会去哪里。

建议的备选方案:保存到已知的本地文件位置

您可能想要做的是将文件复制到计算机上的 well-known 位置并从那里访问它。一种方法是在用户的主目录下创建一个目录(例如 .myapp),将所选文件复制到该目录,然后根据需要从那里读取和写入文件。您可以使用 java system properties.

确定用户的主目录

备选方案:使用首选项 API

您可以使用 Java Preferences API.

而不是在用户主目录下使用自定义应用程序目录

This package allows applications to store and retrieve user and system preference and configuration data. This data is stored persistently in an implementation-dependent backing store. There are two separate trees of preference nodes, one for user preferences and one for system preferences.

关于如何做到这一点的详细讨论超出了本答案的范围,但网络上有许多关于它的教程。

使用 Java 首选项 API 来自 JavaFX 应用程序的示例在 makery tutorial.

常见问题解答

It doesn't really make any sense. What I must do then if I'm willing to allow the user to upload an image and then save the image somewhere on my pc or in my project?

据推测,您的“项目”是您在问题中显示的源代码目录。如前所述,该目录在本地开发环境之外的运行时不可用。所以你不能在运行时将文件保存到项目中,因为它不存在。相反,您需要“将图像保存在我的电脑上的某处”。一种方法是按照我提供的关于将图像放置在您的应用程序在用户主目录中创建的 app-specific sub-directory 中的建议。

Does this solution works globally? like the project isn't just for me meaning it'll be sent to some fellows so they won't have the directory I saved the file to.

他们将有一个用户目录,因为这就是操作系统的工作方式。这可以从我链接的系统属性中获得。如果您按照需要在用户目录下创建和使用 app-specific sub-directory 应用程序的路线,那么您可以确信所需的目录将存在。对于不同的位置,操作系统中还有其他存储约定,但我真的建议只使用用户目录下的 app-specific 目录,除非你需要做其他事情。还有其他选项,例如上传到云托管图像服务,但除非您需要,否则不要这样做。

Plus, in the project I place all images I use in a directory that's existed in the directory which is for example /resources/images and I need to save the images in it.

如前所述,您不能在运行时动态执行此操作。您需要将动态添加的图像放在其他地方(例如在用户本地目录、云服务、共享数据库等下)。你需要从同一个地方读取这些文件。如果将它们放在用户目录下,则可以使用 file URLs 或 file API 而不是 getResource.

来读写文件

示例应用程序

此应用程序使用用户的主目录来存储选定的头像图像。

应用程序将使用应用程序随附的应用程序资源中的默认头像。如果用户选择要使用的新图像,则可以更改默认头像。

当前使用的头像被复制到存储在 <user.home>/.avatar/avatar.png 的文件中。

此答案中提供了用于默认图像的自定义图标图像:

此解决方案可能与您需要的不同,也许您需要为任意图像集(如相册)而不是像头像这样的单个图像上传文件的服务。但希望此处显示的信息为您提供了一个具体示例,说明您可以如何继续解决您的确切问题。

AvatarService 中的 uploadCustomAvatar 方法将 InputStream 作为参数。因此,如果需要,可以采用该方法从本地图像文件以外的其他位置(例如网络 URL)获取自定义图像。

默认头像

已上传自定义头像

AvatarApp.java

import javafx.application.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;

public class AvatarApp extends Application {
    private static final String APP_DIR = ".avatar";

    private AvatarService avatarService;

    @Override
    public void init() {
        LocalStorage localStorage = new LocalStorage(APP_DIR);
        avatarService = new AvatarService(localStorage);
    }

    @Override
    public void start(Stage stage) throws Exception {
        ImageView avatar = new ImageView(avatarService.getAvatarImage());
        avatar.imageProperty().bind(avatarService.avatarImageProperty());
        avatar.setFitWidth(AvatarService.DIMENSIONS);
        avatar.setFitHeight(AvatarService.DIMENSIONS);
        avatar.setPreserveRatio(true);

        Button changeAvatarButton = new Button("Change...");
        changeAvatarButton.setOnAction(e -> uploadCustomAvatar(stage));

        VBox layout = new VBox(10, avatar, changeAvatarButton);
        layout.setStyle("-fx-background-color: linen; -fx-font-size: 16px");
        layout.setAlignment(Pos.CENTER);
        layout.setPadding(new Insets(10));

        stage.setScene(new Scene(layout));
        stage.show();
    }

    private void uploadCustomAvatar(Stage owner) {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Choose your avatar");
        fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter(
                        "PNG Image Files", "*.png"
                )
        );

        File selectedFile = fileChooser.showOpenDialog(owner);
        if (selectedFile != null) {
           try (
                   InputStream inputStream = new BufferedInputStream(
                           Files.newInputStream(selectedFile.toPath())
                   )
           ) {
               avatarService.uploadCustomAvatar(inputStream);
            } catch (IOException ex) {
                System.out.println("Unable to upload avatar");
                ex.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

AvatarService.java

import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.scene.image.Image;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Objects;

class AvatarService {
    public static final double DIMENSIONS = 96;

    private static final String DEFAULT_AVATAR_RESOURCE = "Dragon-icon.png";
    private static final String AVATAR_FILENAME = "avatar.png";

    private final Path avatarPath;
    private final ReadOnlyObjectWrapper<Image> avatarImage = new ReadOnlyObjectWrapper<>();

    public AvatarService(LocalStorage localStorage) {
        avatarPath = localStorage.getLocalStoragePath().resolve(AVATAR_FILENAME);

        if (!Files.exists(avatarPath)) {
            copyDefaultAvatar();
        }

        refreshImage();
    }

    private void copyDefaultAvatar() {
        try {
            Files.copy(
                    Objects.requireNonNull(
                            AvatarApp.class.getResourceAsStream(
                                    DEFAULT_AVATAR_RESOURCE
                            )
                    ),
                    avatarPath
            );
        } catch (IOException e) {
            System.out.println("Unable to initialize default avatar");
            e.printStackTrace();
            Platform.exit();
        }
    }

    public void uploadCustomAvatar(InputStream inputStream) {
        try {
            Files.copy(
                    inputStream,
                    avatarPath,
                    StandardCopyOption.REPLACE_EXISTING
            );

            refreshImage();
        } catch (IOException e) {
            System.out.println("Unable to upload custom avatar");
            e.printStackTrace();
        }
    }

    public Image getAvatarImage() {
        return avatarImage.get();
    }


    public ReadOnlyObjectProperty<Image> avatarImageProperty() {
        return avatarImage.getReadOnlyProperty();
    }

    private void refreshImage() {
        avatarImage.set(
                new Image(
                        "file:" + avatarPath.toAbsolutePath()
                )
        );
    }
}

LocalStorage.java

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

class LocalStorage {
    private Path localStoragePath;

    public LocalStorage(String name) {
        try {
            String userDir = System.getProperty("user.home");

            localStoragePath = Paths.get(userDir, name);
            if (!Files.isDirectory(localStoragePath)) {
                Files.createDirectory(localStoragePath);
            }
        } catch (IOException ex) {
            System.out.println("Unable to initialize local storage");
            ex.printStackTrace();
            Platform.exit();
        }
    }

    public Path getLocalStoragePath() {
        return localStoragePath;
    }
}