3.5.2.1.17. 文件上传控件

FileUploadField 允许用户上传文件到服务器。这个控件包含标题 、 已上传文件的链接 、 还有两个按钮:上传按钮和清除文件选择按钮。当点击上传按钮的时候,会弹出系统标准的文件选择器,用户可以在这里选择需要上传的文件。如果是要上传多个文件,可以用 FileMultiUploadField 控件。

gui upload 7.0

该控件对应的 XML 名称:upload

对于 FileDescriptor 类型的实体属性,可以在 FieldGroup 内用 datasource 属性使用此控件,也可以在 Form 中通过 dataContainer 属性使用,或者也能单独使用。如果此控件绑定到任何数据组件,上传的文件会被立即保存到文件存储,对应的 FileDescriptor 实例会保存到数据库。

<upload fileStoragePutMode="IMMEDIATE"
        dataContainer="personDc"
        property="photo"/>

还可以通过编程的方式控制文件和 FileDescriptor 的保存:

  • 在界面的 XML 描述中声明这个控件:

    <upload id="uploadField"
            fileStoragePutMode="MANUAL"/>
  • 在界面控制器中,需要注入该控件本身,还需要注入 FileUploadingAPIDataManager 这两个接口。订阅 InitEvent 事件并且添加事件监听器,这样可以对文件上传成功或者出错做出相应的操作:

    @Inject
    private FileUploadField uploadField;
    @Inject
    private FileUploadingAPI fileUploadingAPI;
    @Inject
    private DataManager dataManager;
    @Inject
    private Notifications notifications;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        uploadField.addFileUploadSucceedListener(uploadSucceedEvent -> {
    
            File file = fileUploadingAPI.getFile(uploadField.getFileId()); (1)
            if (file != null) {
                notifications.create()
                        .withCaption("File is uploaded to temporary storage at " + file.getAbsolutePath())
                        .show();
            }
    
            FileDescriptor fd = uploadField.getFileDescriptor(); (2)
            try {
                fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); (3)
            } catch (FileStorageException e) {
                throw new RuntimeException("Error saving file to FileStorage", e);
            }
            dataManager.commit(fd); (4)
            notifications.create()
                    .withCaption("Uploaded file: " + uploadField.getFileName())
                    .show();
        });
    
        uploadField.addFileUploadErrorListener(uploadErrorEvent ->
                notifications.create()
                        .withCaption("File upload error")
                        .show());
    }
1 此时如果需要的话,可以取到保存在临时存储的文件。
2 一般来说,文件需要保存到中间件的文件存储中。
3 将文件保存至 FileStorage。
4 将文件描述器保存到数据库。

该控件将所有选择的文件上传到客户端层(client tier) 的临时存储(temporary storage)并且调用通过 addFileUploadSucceedListener() 添加的监听器。在这个监听器里面,从 uploadField 获取了一个 FileDescriptor

com.haulmont.cuba.core.entity.FileDescriptor (别跟 java.io.FileDescriptor 混淆了) 是一个持久化实体,唯一定义一个上传的文件,并且也用这个类从系统下载文件。

FileUploadingAPI.putFileIntoStorage() 方法把文件从客户端层的临时存储移动到文件存储。这个方法的参数是临时存储中文件的标识符和对应的 FileDescriptor 对象,这两个参数都是由 FileUploadField 提供的。

当上传文件到 FileStorage 完成后,通过调用 DataManager.commit()FileDescriptor 实例存到数据库。这个方法会返回保存的实体,可以用来赋值给其它实体里关联这个文件的属性。这里只是简单的把 FileDescriptor 存到了数据库。从 Administration > External Files 界面可以看到这个文件。

通过 addFileUploadErrorListener() 方法添加的监听器会在从客户端上传文件到临时存储出错的时候被调用。

下面列出能跟踪上传进度的监听器:

  • AfterValueClearListener

  • BeforeValueClearListener

  • FileUploadErrorListener

  • FileUploadFinishListener

  • FileUploadStartListener

  • FileUploadSucceedListener

  • ValueChangeListener

fileUploadField 的属性:

  • fileStoragePutMode - 定义文件和相应的 FileDescriptor 怎么存储。

    • IMMEDIATE 模式,在文件存到客户端层临时存储之后立即存储。

    • MANUAL 模式, 需要手动在 FileUploadSucceedListener 里面编码实现。

      当在 FieldGroup 里面使用 FileUploadField 的时候,默认模式是 IMMEDIATE,其它情况下,默认模式是 MANUAL

  • uploadButtonCaptionuploadButtonIconuploadButtonDescription 这三个 XML 属性可以设置上传按钮的属性。

  • showFileName - 控制上传文件的名称是否要显示在上传按钮旁边,默认是 false 不显示。

  • showClearButton - 控制是否要显示清空按钮,默认 false 不显示。

  • clearButtonCaptionclearButtonIconclearButtonDescription 这三个 XML 属性可以设置清空按钮的属性。

  • accept XML 属性 (或者相应的 setAccept() 方法) 用来设置文件选择对话框里面的文件类型掩码,但是用户还是可以选择“所有文件”来上传任意文件。

    这个属性的值需要是以英文逗号分隔的文件扩展名,比如: *.jpg,*.png

  • 最大可上传的文件大小是由 cuba.maxUploadSizeMb 应用程序属性定义的,默认是 20MB。如果用户选择了更大的文件的话,会有相应的提示信息,并且中断上传过程。

  • fileSizeLimit XML 属性 (或者相应的 setFileSizeLimit() 方法) 用来设置最大允许上传的文件大小,以字节为单位。

    <upload id="uploadField" fileSizeLimit="2000"/>
  • permittedExtensions XML 属性 (或者相应的 setPermittedExtensions() 方法) 设置允许的文件扩展名白名单。

    这个属性的值需要是字符串的集合,其中每个字符串是以 . 开头的允许的文件扩展名,比如:

    uploadField.setPermittedExtensions(Sets.newHashSet(".png", ".jpg"));
  • dropZone - 允许设置一个特殊的 BoxLayout 用来作为从浏览器外部拖拽文件可以放置的目标容器区域。这个目标区域可以覆盖整个对话框的窗口。当文件被拖拽到这块区域的时候,这个容器会被高亮显示,否则目标区域不会显示。

    <layout spacing="true"
            width="100%">
        <vbox id="dropZone"
              height="AUTO"
              spacing="true">
            <textField id="textField"
                       caption="Title"
                       width="100%"/>
            <textArea id="textArea"
                      caption="Description"
                      width="100%"
                      rows="5"/>
            <checkBox caption="Is reference document"
                      width="100%"/>
            <upload id="upload"
                    dropZone="dropZone"
                    showClearButton="true"
                    showFileName="true"/>
        </vbox>
        <hbox spacing="true">
            <button caption="mainMsg://actions.Apply"/>
            <button caption="mainMsg://actions.Cancel"/>
        </hbox>
    </layout>
    gui dropZone

    如果想要 dropZone 不变并且一直显示,需要给这个容器设置预定义的样式名称 dropzone-container。此时这个容器应该是空的,只包含一个 label 组件:

    <layout spacing="true"
            width="100%">
        <textField id="textField"
                   caption="Title"
                   width="100%"/>
        <checkBox caption="Is reference document"
                  width="100%"/>
        <upload id="upload"
                dropZone="dropZone"
                showClearButton="true"
                showFileName="true"/>
        <vbox id="dropZone"
              height="150px"
              spacing="true"
              stylename="dropzone-container">
            <label stylename="dropzone-description"
                   value="Drop file here"
                   align="MIDDLE_CENTER"/>
        </vbox>
        <hbox spacing="true">
            <button caption="mainMsg://actions.Apply"/>
            <button caption="mainMsg://actions.Cancel"/>
        </hbox>
    </layout>
    gui dropZone static
  • pasteZone 允许设置一个特殊的容器用来处理粘贴(paste)的快捷键。此时需要这个容器内部的一个文字输入控件获得焦点(focused)。这个功能只支持基于 Chromium 的浏览器。

    <upload id="uploadField"
            pasteZone="vboxId"
            showClearButton="true"
            showFileName="true"/>

参考 加载和显示图片 有更多复杂的使用上传文件的例子。