(七)IntelliJ 插件開發(fā)—— 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

運(yùn)行環(huán)境

macOS 10.14.5
IntelliJ idea 2019.2.4

1、FileChooser#chooseFile

效果

入口

文件對(duì)話框

選中后效果

思路

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

效果

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)的文件選擇器

效果

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<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

可以打開樹形文件選擇框,且附帶搜索功能

效果

未限制文件類型
限制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());
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容