官方文档
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
<action id="com.kungyu.file.chooser.FileChooserAction" class="com.kungyu.file.chooser.FileChooserAction" text="FileChooserAction" description="FileChooserAction">
<add-to-group group-id="ToolsMenu" anchor="after" relative-to-action="com.kungyu.notification.HintNotificationAction"/>
</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
效果
该方法有多个重载方法,官网推荐使用带有回调参数的方法:
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<? super List<VirtualFile>> 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
实现一个右边带有浏览图标的文件选择器
效果
源码
/*
* 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<JTextField> {
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<T extends JComponent> extends BrowseFolderRunnable <T> implements ActionListener {
public BrowseFolderActionListener(@Nullable @Nls(capitalization = Nls.Capitalization.Title) String title,
@Nullable @Nls(capitalization = Nls.Capitalization.Sentence) String description,
@Nullable ComponentWithBrowseButton<T> textField,
@Nullable Project project,
FileChooserDescriptor fileChooserDescriptor,
TextComponentAccessor<? super T> 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
可以打开树形文件选择框,且附带搜索功能
效果
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());
}