(七)IntelliJ 插件开发—— File and Class Choosers(文件和类选择器)

官方文档

https://www.jetbrains.org/intellij/sdk/docs/user_interface_components/file_and_class_choosers.html

Github

https://github.com/kungyutucheng/my_gradle_plugin

运行环境

macOS 10.14.5
IntelliJ idea 2019.2.4

1、FileChooser#chooseFile

效果

入口

文件对话框

选中后效果

思路

1、创建FileChooserAction并注册到ToolsMenu中,作为触发入口
2、实现FileChooserAction#actionPerformed,在其中创建一个FileChooserDescriptor对象
3、将步骤2当中创建的FileChooserDescriptor对象作为入参,实现FileChooser.chooseFile,并获取到该方法返回到文件对象VirtualFile(VirtualFile传送门)

源码

FileChooserDescriptor

/**
   * Creates new instance. Use methods from {@link FileChooserDescriptorFactory} for most used descriptors.
   *
   * @param chooseFiles       controls whether files can be chosen
   * @param chooseFolders     controls whether folders can be chosen
   * @param chooseJars        controls whether .jar files can be chosen
   * @param chooseJarsAsFiles controls whether .jar files will be returned as files or as folders
   * @param chooseJarContents controls whether .jar file contents can be chosen
   * @param chooseMultiple    controls how many files can be chosen
   */
  public FileChooserDescriptor(boolean chooseFiles,
                               boolean chooseFolders,
                               boolean chooseJars,
                               boolean chooseJarsAsFiles,
                               boolean chooseJarContents,
                               boolean chooseMultiple) {
    myChooseFiles = chooseFiles;
    myChooseFolders = chooseFolders;
    myChooseJars = chooseJars;
    myChooseJarsAsFiles = chooseJarsAsFiles;
    myChooseJarContents = chooseJarContents;
    myChooseMultiple = chooseMultiple;
  }

该构造方法源码到注释已经很清楚了,就不废话多解释了,如果想要支持更多类型的文件,可以重写FileChooserDescriptor#isFileSelectable

FileChooser#chooseFile

@Nullable
  public static VirtualFile chooseFile(@NotNull final FileChooserDescriptor descriptor,
                                       @Nullable final Project project,
                                       @Nullable final VirtualFile toSelect) {
    return chooseFile(descriptor, null, project, toSelect);
  }

  @Nullable
  public static VirtualFile chooseFile(@NotNull final FileChooserDescriptor descriptor,
                                       @Nullable final Component parent,
                                       @Nullable final Project project,
                                       @Nullable final VirtualFile toSelect) {
    Component parentComponent = parent == null ? KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow() : parent;
    LOG.assertTrue(!descriptor.isChooseMultiple());
    return ArrayUtil.getFirstElement(chooseFiles(descriptor, parentComponent, project, toSelect));
  }
  • project:当前打开的工程对象,从源码可以看到,为null的时候project设置为当前激活的窗口
  • toSelect:默认打开的对象或者路径

Demo

FileChooserAction

package com.kungyu.file.chooser;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;

/**
 * @author wengyongcheng
 * @since 2020/3/5 11:23 下午
 */
public class FileChooserAction extends AnAction {

    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        FileChooserDescriptor chooserDescriptor = new FileChooserDescriptor(true,true,true,true,true,true);
        VirtualFile virtualFile = FileChooser.chooseFile(chooserDescriptor, e.getProject(), null);
        if(virtualFile != null) {
            Messages.showMessageDialog(virtualFile.getName(), "获取到的文件名称", Messages.getInformationIcon());
        } else {
            Messages.showMessageDialog("文件名称为空", "文件名称为空", Messages.getInformationIcon());
        }
    }
}

注册action


     

然而,按照上述代码运行,我们会发现报错了,异常栈如下:

java.lang.Throwable: Assertion failed
    at com.intellij.openapi.diagnostic.Logger.assertTrue(Logger.java:180)
    at com.intellij.openapi.diagnostic.Logger.assertTrue(Logger.java:189)
    at com.intellij.openapi.fileChooser.FileChooser.chooseFile(FileChooser.java:72)
    at com.intellij.openapi.fileChooser.FileChooser.chooseFile(FileChooser.java:63)
    at com.kungyu.file.chooser.FileChooserAction.actionPerformed(FileChooserAction.java:20)
    // 其余略

可以看到,这是我们的代码:

com.kungyu.file.chooser.FileChooserAction.actionPerformed(FileChooserAction.java:20)

于是,往上追溯:

at com.intellij.openapi.fileChooser.FileChooser.chooseFile(FileChooser.java:63)

FileChooser.java:72处可以看到:

LOG.assertTrue(!descriptor.isChooseMultiple());

显而易见,我们在创建FileChooserDescriptor对象的时候指定的chooseMultiple 属性为true,触发了断言,改之为false,不再出现异常

问题解决了,但总得知道此处源码为何要加这个断言呢,个人猜测,FileChooser#chooseFile可能是之前的版本实现,官网也并没有介绍该方法,而且在源码中我们还可以看到FileChooser#chooseFiles方法,而该方法顾名思义就知道它是默认支持多选的,所以可能官方已经不推荐使用FileChooser#chooseFile,但是为了兼容,依旧保留了该方法,毕竟,使用FileChooser#chooseFile还是可以正常实现对应的功能的,虽然idea会报错


2、Filechooser#chooseFiles

效果

chooseFiles

该方法有多个重载方法,官网推荐使用带有回调参数的方法:

The best method to use is the one which returns void and takes a callback receiving the list of selected files as a parameter. This is the only overload which will display a native file open dialog on macOS.

因此,本文以以下方法为例:

源码

/**
   * Shows file/folder open dialog, allows user to choose files/folders and then passes result to callback in EDT.
   * On MacOS Open Dialog will be shown with slide effect if Macish UI is turned on.
   *
   * @param descriptor file chooser descriptor
   * @param project    project
   * @param toSelect   file to preselect
   * @param callback   callback will be invoked after user have closed dialog and only if there are files selected
   * @see FileChooserConsumer
   */
  public static void chooseFiles(@NotNull final FileChooserDescriptor descriptor,
                                 @Nullable final Project project,
                                 @Nullable final VirtualFile toSelect,
                                 @NotNull final Consumer> callback) {
    chooseFiles(descriptor, project, null, toSelect, callback);
  }

Demo

FileChooserAction

FileChooserDescriptor chooserDescriptor = new FileChooserDescriptor(true,true,true,true,true,true);
VirtualFile toSelect = LocalFileSystem.getInstance().findFileByPath(File.separator + "Users" + File.separator + "wengyongcheng" + File.separator + "swagger-html" + File.separator);
FileChooser.chooseFiles(chooserDescriptor, null, toSelect, virtualFiles -> {
    if (CollectionUtils.isNotEmpty(virtualFiles)) {
        for (VirtualFile file : virtualFiles) {
            Messages.showMessageDialog(file.getPath(), file.getName(),Messages.getInformationIcon());
        }
   }
});

3、 TextFieldWithBrowseButton

实现一个右边带有浏览图标的文件选择器

效果

image.png

源码

/*
 * Copyright 2000-2013 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.openapi.ui;

import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;

public class TextBrowseFolderListener extends ComponentWithBrowseButton.BrowseFolderActionListener {
  public TextBrowseFolderListener(@NotNull FileChooserDescriptor fileChooserDescriptor) {
    this(fileChooserDescriptor, null);
  }

  public TextBrowseFolderListener(@NotNull FileChooserDescriptor fileChooserDescriptor, @Nullable Project project) {
    super(null, null, null, project, fileChooserDescriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
  }

  void setOwnerComponent(@NotNull TextFieldWithBrowseButton component) {
    myTextComponent = component.getChildComponent();
  }

  FileChooserDescriptor getFileChooserDescriptor() {
    return myFileChooserDescriptor;
  }
}

追溯进super方法:

public static class BrowseFolderActionListener extends BrowseFolderRunnable  implements ActionListener {
    public BrowseFolderActionListener(@Nullable @Nls(capitalization = Nls.Capitalization.Title) String title,
                                      @Nullable @Nls(capitalization = Nls.Capitalization.Sentence) String description,
                                      @Nullable ComponentWithBrowseButton textField,
                                      @Nullable Project project,
                                      FileChooserDescriptor fileChooserDescriptor,
                                      TextComponentAccessor accessor) {
    super(title, description, project, fileChooserDescriptor, textField != null ? textField.getChildComponent() : null, accessor);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      run();
    }
}

可以看到实现了BrowseFolderRunnable接口,而在BrowseFolderRunnable#run方法中,可以看到底层依旧是使用了FileChooser#chooseFile:

@Override
public void run() {
    FileChooserDescriptor fileChooserDescriptor = myFileChooserDescriptor;
    if (myTitle != null || myDescription != null) {
        fileChooserDescriptor = (FileChooserDescriptor)myFileChooserDescriptor.clone();
        if (myTitle != null) {
            fileChooserDescriptor.setTitle(myTitle);
        }
        if (myDescription != null) {
            fileChooserDescriptor.setDescription(myDescription);
        }
    }

    FileChooser.chooseFile(fileChooserDescriptor, getProject(), myTextComponent, getInitialFile(), this::onFileChosen);
}

Demo

FileChooserDialogWrapper

package com.kungyu.file.chooser;

import com.intellij.ide.util.AbstractTreeClassChooserDialog;
import com.intellij.ide.util.BrowseFilesListener;
import com.intellij.ide.util.TreeFileChooserFactory;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.ui.*;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.apache.commons.collections.CollectionUtils;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.io.File;

/**
 * @author wengyongcheng
 * @since 2020/3/6 11:55 下午
 */
public class FileChooserDialogWrapper extends DialogWrapper {

    private TextFieldWithBrowseButton textFieldWithBrowseButton;

    private JTextField fileTextFiled;

    public FileChooserDialogWrapper(){
        super(true);
        init();
        setTitle("文件选择对话框");
    }

    @Nullable
    @Override
    protected JComponent createCenterPanel() {

        JPanel panel = new JPanel();
        textFieldWithBrowseButton = new TextFieldWithBrowseButton();
        fileTextFiled = new JTextField();
        FileChooserDescriptor chooserDescriptor = new FileChooserDescriptor(true,true,true,true,true,true);
        TextBrowseFolderListener listener = new TextBrowseFolderListener(chooserDescriptor);
        textFieldWithBrowseButton.addBrowseFolderListener(listener);
        textFieldWithBrowseButton.setText(File.separator + "Users" + File.separator + "wengyongcheng" + File.separator + "swagger-html" + File.separator);
        panel.setLayout(new BorderLayout());
        panel.setPreferredSize(new Dimension(400,40));
        panel.add(textFieldWithBrowseButton, BorderLayout.CENTER);


        return panel;
    }

    @Nullable
    @Override
    protected ValidationInfo doValidate() {
        String filePath = textFieldWithBrowseButton.getText();
        VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(filePath);
        if (virtualFile != null) {
            Messages.showMessageDialog(virtualFile.getPath(), virtualFile.getName(), Messages.getInformationIcon());
        }
        return null;
    }
}

FileChooserAction

new FileChooserDialogWrapper().showAndGet();

4、TreeFileChooserFactory

可以打开树形文件选择框,且附带搜索功能

效果

未限制文件类型
限制Java文件类型

Demo

FileChooserAction

TreeFileChooserFactory instance = TreeFileChooserFactory.getInstance(e.getProject());
TreeFileChooser.PsiFileFilter fileFilter = file -> file.getName().endsWith(".java");
TreeFileChooser javaFileChooser = instance.createFileChooser("java文件选择器", null, null, fileFilter);
javaFileChooser.showDialog();
PsiFile selectedFile = javaFileChooser.getSelectedFile();
if (selectedFile != null) {
     Messages.showMessageDialog(selectedFile.getVirtualFile().getPath(),selectedFile.getVirtualFile().getName(), Messages.getInformationIcon());
}

你可能感兴趣的:((七)IntelliJ 插件开发—— File and Class Choosers(文件和类选择器))