官方文檔
https://www.jetbrains.org/intellij/sdk/docs/user_interface_components/file_and_class_choosers.html
Github
https://github.com/kungyutucheng/my_gradle_plugin
運(yùn)行環(huán)境
macOS 10.14.5
IntelliJ idea 2019.2.4
1、FileChooser#chooseFile
效果



思路
1、創(chuàng)建FileChooserAction并注冊(cè)到ToolsMenu中,作為觸發(fā)入口
2、實(shí)現(xiàn)FileChooserAction#actionPerformed,在其中創(chuàng)建一個(gè)FileChooserDescriptor對(duì)象
3、將步驟2當(dāng)中創(chuàng)建的FileChooserDescriptor對(duì)象作為入?yún)ⅲ瑢?shí)現(xiàn)FileChooser.chooseFile,并獲取到該方法返回到文件對(duì)象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;
}
該構(gòu)造方法源碼到注釋已經(jīng)很清楚了,就不廢話多解釋了,如果想要支持更多類型的文件,可以重寫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:當(dāng)前打開的工程對(duì)象,從源碼可以看到,為null的時(shí)候project設(shè)置為當(dāng)前激活的窗口 -
toSelect:默認(rèn)打開的對(duì)象或者路徑
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());
}
}
}
注冊(cè)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>
然而,按照上述代碼運(yùn)行,我們會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了,異常棧如下:
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());
顯而易見,我們?cè)趧?chuàng)建FileChooserDescriptor對(duì)象的時(shí)候指定的chooseMultiple 屬性為true,觸發(fā)了斷言,改之為false,不再出現(xiàn)異常
問題解決了,但總得知道此處源碼為何要加這個(gè)斷言呢,個(gè)人猜測(cè),
FileChooser#chooseFile可能是之前的版本實(shí)現(xiàn),官網(wǎng)也并沒有介紹該方法,而且在源碼中我們還可以看到FileChooser#chooseFiles方法,而該方法顧名思義就知道它是默認(rèn)支持多選的,所以可能官方已經(jīng)不推薦使用FileChooser#chooseFile,但是為了兼容,依舊保留了該方法,畢竟,使用FileChooser#chooseFile還是可以正常實(shí)現(xiàn)對(duì)應(yīng)的功能的,雖然idea會(huì)報(bào)錯(cuò)
2、Filechooser#chooseFiles
效果

該方法有多個(gè)重載方法,官網(wǎng)推薦使用帶有回調(diào)參數(shù)的方法:
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
實(shí)現(xiàn)一個(gè)右邊帶有瀏覽圖標(biāo)的文件選擇器
效果

源碼
/*
* 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;
}
}
追溯進(jìn)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();
}
}
可以看到實(shí)現(xiàn)了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("文件選擇對(duì)話框");
}
@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());
}