IDEA 用户界面组件(二)


这篇接着 IDEA 用户界面组件(一) 继续介绍下其他的一些组件。

Popups

Popups 是 IntelliJ 平台中广泛使用弹出窗口 - 没有显式的关闭按钮并在失去焦点时自动消失。个人感觉跟 Action 类似。

IntelliJ 平台提供了 JBPopupFactory 接口,来创建显示不同类型组件的弹出窗口。

createComponentPopupBuilder()

通用的创建方式,允许显示任何 Swing 组件,也就是可以在弹出的窗口中嵌套前面介绍过的 JComponent,而 JComponent 中就可以自定义任何我们想要显示的内容。

public class PopupComponentAction extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        Project project = e.getProject();
        if (Objects.isNull(project)) {
            return;
        }

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(new JLabel("这是一个ComponentPopup"), BorderLayout.CENTER);
        panel.add(new JButton("上一个"), BorderLayout.NORTH);
        panel.add(new JButton("下一个"), BorderLayout.SOUTH);

        JBPopup jbPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, null).createPopup();
        jbPopup.showCenteredInCurrentWindow(project);
    }
}

ComponentPopup 示例

createPopupChooserBuilder()

可以传入一个普通的 java.util.List,根据传入的内容自动生成一个列表项。

public class PopupChooserAction extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        Project project = e.getProject();
        if (Objects.isNull(project)) {
            return;
        }

        JBPopup chooser = JBPopupFactory.getInstance()
                .createPopupChooserBuilder(Lists.newArrayList("abc", "def")).createPopup();
        chooser.showCenteredInCurrentWindow(project);
    }
}
PopupChooser 示例

createConfirmation()

创建一个两个选项的选择弹窗,并可以根据选择的内容执行不同的操作。

public class PopupConfirmationAction extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        Project project = e.getProject();
        if (Objects.isNull(project)) {
            return;
        }

        ListPopup popup = JBPopupFactory.getInstance()
                .createConfirmation("这是个确认创建", ()-> System.out.println("选择了YES"), 1);
        popup.showCenteredInCurrentWindow(project);
    }
}
Confirmation 示例

createActionGroupPopup()

用于展示 Action Group 中的 actions,并执行用户选择的 action。官方文档中给的示例是: Edit / Find Usages / Recent Find Usages 中显示最近的的查询记录,应该就是下面这个。

搜索历史
public class PopupActionGroupAction extends AnAction {

    public static final int NUM_10 = 10;

    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        new PopupGroupAction().actionPerformed(e);
    }

    public static class PopupGroupAction extends DefaultActionGroup {
        @Override
        public void actionPerformed(@NotNull AnActionEvent e) {
            Project project = e.getProject();
            if (Objects.isNull(project)) {
                return;
            }
            ListPopup popup = JBPopupFactory.getInstance()
                    .createActionGroupPopup("CreateActionGroupPopup", this, e.getDataContext(),
                            JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false);
            popup.showCenteredInCurrentWindow(project);
        }

        @Override
        public AnAction @NotNull [] getChildren(@Nullable AnActionEvent e) {
            AnAction[] actions = new AnAction[NUM_10];
            for (int i = 0; i < NUM_10; i++) {
                actions[i] = new AnAction("index:" + i) {
                    @Override
                    public void actionPerformed(@NotNull AnActionEvent e) {
                        System.out.println(e.getPresentation().getText());
                    }
                };
            }
            return actions;
        }
    }
}
PopupActionGroup 示例

Action Group 中除了可以使用箭头键进行选择外,还可以通过传递 JBPopupFactory.ActionSelectionAid 枚举中的常量之一,来选择通过输入序号或者输入部分文本来快速选择 action。

其他说明

创建 Popup 后,您需要通过调用 show() 方法来显示它。您可以通过调用 showInBestPositionFor() 让 IntelliJ 平台根据上下文自动选择位置,或者通过 showUnderneathOf() 和 showInCenterOf() 等方法显式指定位置。

如果您需要在弹出窗口关闭时执行某些操作,您可以使用 addListener() 方法为其附加监听器。

如果只是想简单的创建一个通知内容,可以通过 JBPopupFactory.getInstance().createMessage("这是一个消息").showInFocusCenter(); 来创建一个很简单的消息。

Notifications

要显示一些通知信息,可以使用 Dialogs、Popup,但通常来说显示非模态通知的方法是使用 Notifications 类。

它有两个主要优点:

  • 用户可以控制每种通知类型的显示方式,通过 Settings/Preferences | Appearance & Behavior | Notifications
  • 所有显示的通知都收集在事件日志工具窗口中,供以后查看

通知的文本支持 HTML 标记。

使用 Notification.addAction(AnAction) 可以在内容下方添加链接,通过 NotificationAction 可以更方便的使用。

可以通过 Notification 构造函数的 groupId 参数指定通知类型。用户可以在 Settings/Preferences | Appearance & Behavior | Notifications 中选择每种通知类型对应的显示类型。

普通 Notification

要通过首选项指定显示类型,需要使用 NotificationGroup 创建通知,下面是使用 NotificationGroup 方式来创建 Notification。

 <extensions defaultExtensionNs="com.intellij">
    <notificationGroup id="Custom Notification Group INFORMATION" displayType="BALLOON" />
    <notificationGroup id="Custom Notification Group WARNING" displayType="BALLOON" />
    <notificationGroup id="Custom Notification Group ERROR" displayType="BALLOON" />
  </extensions>
public class Notification extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        Project project = e.getProject();
        if (Objects.isNull(project)) {
            return;
        }

        NotificationGroupManager.getInstance().getNotificationGroup("Custom Notification Group INFORMATION")
                .createNotification("这是一个 Notification INFORMATION", NotificationType.INFORMATION)
                .notify(project);

        NotificationGroupManager.getInstance().getNotificationGroup("Custom Notification Group WARNING")
                .createNotification("这是一个 Notification WARNING", NotificationType.WARNING)
                .notify(project);

        NotificationGroupManager.getInstance().getNotificationGroup("Custom Notification Group ERROR")
                .createNotification("这是一个 Notification ERROR", NotificationType.ERROR)
                .notify(project);
    }
}

执行后在 IDEA 的右下角就可以看到通知出现,应该是同时最多能展示 2 个 Notification,创建的 INFORMATION 并没有同时展示出来。同时在 Event Log 里面可以看到通知记录。

Notification
Event Log

带 HTML 标记的 Notification

下面创建了一个带有 HTML 标签的通知消息,不过貌似对 HTML 标签的支持不是特别好。

public class BalloonHtmlText extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        Project project = e.getProject();
        if (Objects.isNull(project)) {
            return;
        }

        // 创建一个消息
        final JFrame jFrame = WindowManager.getInstance().getFrame(project);
        Balloon balloon = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("<form>姓名:<input type=\"text\" name=\"name\"/>住址:<input type=\"text\" name=\"address\"/><button type=\"submit\">提交</button></form>", MessageType.INFO, e1 -> {
        }).createBalloon();
        balloon.showInCenterOf(Objects.requireNonNull(jFrame).getRootPane());
    }
}
HTML Balloon

File and Class Choosers

通过 Dialog

要让用户选择一个文件、目录或多个文件,可以使用 FileChooser.chooseFiles() 方法。这个有多个重载方法。最好用的方式选择返回 void 的方法,并传入一个接收所选文件列表作为参数的回调。类似下面的方法:

  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);
  }

FileChooserDescriptor 类控制可以选择哪些文件。构造函数参数指定是否可以选择文件和(或)目录,以及是否允许多选(详细说明请参见 FileChooserDescriptorFactory)。

要对允许的选择进行更细粒度的控制,可以覆写 isFileSelectable() 方法。还可以通过覆写 getIcon()、getName() 和 getComment() 方法来自定义文件的呈现方式。需要注意的是,macOS 系统对大多数的自定义都不支持。如果确实想要修改,则需要使用重载的 chooseFiles() 来显示标准的 IntelliJ 平台对话框。

通过 Textfield

使用文件选择器的一种非常常见的方法是使用文本字段输入路径,并使用省略号按钮 (...) 来显示文件选择器。要创建这样的控件,请使用 TextFieldWithBrowseButton 组件,并对其调用 addBrowseFolderListener() 方法来设置文件选择器。

通过 Tree

通过 TreeFileChooserFactory 类可以使用另一种选择文件的 UI。当使用输入文件名来搜索选择文件时,这种 UI 的效果是最好的。

这个 API 显示的对话框有两个选项卡:

  • 一个是显示项目结构
  • 另一个是显示类似于 Navigate | File 的文件列表。

要显示对话框,请在 createFileChooser() 返回的选择器上调用 showDialog() 方法。通过调用 getSelectedFile() 来获得用户的选择。

Class 文件选择

如果想提供选择 Java 类的功能,可以使用 TreeClassChooserFactory 类。其不同的方法允许指定获取类的范围,可以将选择限制为特定类的子类或接口的实现,以及包含或排除内部类等。

UI 的效果与 TreeFileChooserFactory 非常类似。

Package 选择

如果要选择 Java 包,可以使用 PackageChooserDialog 类。这个类继承自 DialogWrapper,使用起来与前面介绍的 DialogWrapper 一致。

下面是一个简单的示例,集合了上面介绍的 5 种文件选择器。

public class FileChooseAction extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
       new CustomDialog(e).show();
    }

    public static class CustomDialog extends DialogWrapper{

        AnActionEvent anActionEvent;

        public CustomDialog(AnActionEvent anActionEvent) {
            super(true);
            this.anActionEvent = anActionEvent;
            init();
        }

        @Override
        protected @Nullable JComponent createCenterPanel() {

            Project project = anActionEvent.getProject();
            if (Objects.isNull(project)) {
                return null;
            }
            PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
            if (psiFile == null) {
                return null;
            }

            JPanel panel = new JPanel(new FlowLayout());
            panel.setVisible(true);

            // 添加普通文件选择
            JButton fileChooseBtn = new JButton("普通文件选择");
            fileChooseBtn.addActionListener(event ->
                    FileChooser.chooseFiles(FileChooserDescriptorFactory.createSingleFileDescriptor(),
                            project, null, (s) -> s.forEach(f -> System.out.println(f.getName()))));
            panel.add(fileChooseBtn);

            // 添加带浏览按钮的文本框控件
            TextFieldWithBrowseButton browseButton = new TextFieldWithBrowseButton();
            browseButton.addBrowseFolderListener(new TextBrowseFolderListener(FileChooserDescriptorFactory.createSingleFileDescriptor()));
            panel.add(browseButton);

            // 添加 Tree 文件选择
            JButton treeFileChooseBtn = new JButton("Tree 文件选择");
            treeFileChooseBtn.addActionListener(event -> {
                TreeFileChooser chooser = TreeFileChooserFactory.getInstance(project)
                        .createFileChooser("Tree 文件选择", psiFile, FileTypes.PLAIN_TEXT, null);
                chooser.showDialog();
                System.out.println(chooser.getSelectedFile());
            });
            panel.add(treeFileChooseBtn);

            // 添加 Class 文件选择
            JButton treeClassChooseBtn = new JButton("Class 文件选择");
            treeClassChooseBtn.addActionListener(event -> {
                TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project).createProjectScopeChooser("Class 文件选择");
                chooser.showDialog();
                System.out.println(chooser.getSelected());
            });
            panel.add(treeClassChooseBtn);

            // 添加 Java 包选择
            JButton packageChooseBtn = new JButton("Java 包选择");
            packageChooseBtn.addActionListener(event -> {
                PackageChooserDialog chooser = new PackageChooserDialog("Java 包选择", project);
                if (chooser.showAndGet()) {
                    PsiPackage aPackage = chooser.getSelectedPackage();
                    System.out.println(aPackage.getName());
                }
            });
            panel.add(packageChooseBtn);

            return panel;
        }
    }
}

这是整个 Dialog 的显示样式。

5 种文件选择器

下面是分别使用 5 种文件选择器的文件选择 UI 效果。

普通文件选择

普通文件选择

Textfield 文件选择

Textfield 文件选择

Tree 文件选择

Tree 文件选择
Tree 文件选择

Class 文件选择

Class 文件选择
Class 文件选择

Package 选择

Package 选择
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容