
這篇接著 IDEA 用戶界面組件(一) 繼續(xù)介紹下其他的一些組件。
Popups
Popups 是 IntelliJ 平臺(tái)中廣泛使用彈出窗口 - 沒有顯式的關(guān)閉按鈕并在失去焦點(diǎn)時(shí)自動(dòng)消失。個(gè)人感覺跟 Action 類似。
IntelliJ 平臺(tái)提供了 JBPopupFactory 接口,來創(chuàng)建顯示不同類型組件的彈出窗口。
createComponentPopupBuilder()
通用的創(chuàng)建方式,允許顯示任何 Swing 組件,也就是可以在彈出的窗口中嵌套前面介紹過的 JComponent,而 JComponent 中就可以自定義任何我們想要顯示的內(nèi)容。
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("這是一個(gè)ComponentPopup"), BorderLayout.CENTER);
panel.add(new JButton("上一個(gè)"), BorderLayout.NORTH);
panel.add(new JButton("下一個(gè)"), BorderLayout.SOUTH);
JBPopup jbPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, null).createPopup();
jbPopup.showCenteredInCurrentWindow(project);
}
}

createPopupChooserBuilder()
可以傳入一個(gè)普通的 java.util.List,根據(jù)傳入的內(nèi)容自動(dòng)生成一個(gè)列表項(xiàng)。
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);
}
}

createConfirmation()
創(chuàng)建一個(gè)兩個(gè)選項(xiàng)的選擇彈窗,并可以根據(jù)選擇的內(nèi)容執(zhí)行不同的操作。
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("這是個(gè)確認(rèn)創(chuàng)建", ()-> System.out.println("選擇了YES"), 1);
popup.showCenteredInCurrentWindow(project);
}
}

createActionGroupPopup()
用于展示 Action Group 中的 actions,并執(zhí)行用戶選擇的 action。官方文檔中給的示例是: Edit / Find Usages / Recent Find Usages 中顯示最近的的查詢記錄,應(yīng)該就是下面這個(gè)。

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

Action Group 中除了可以使用箭頭鍵進(jìn)行選擇外,還可以通過傳遞 JBPopupFactory.ActionSelectionAid 枚舉中的常量之一,來選擇通過輸入序號(hào)或者輸入部分文本來快速選擇 action。
其他說明
創(chuàng)建 Popup 后,您需要通過調(diào)用 show() 方法來顯示它。您可以通過調(diào)用 showInBestPositionFor() 讓 IntelliJ 平臺(tái)根據(jù)上下文自動(dòng)選擇位置,或者通過 showUnderneathOf() 和 showInCenterOf() 等方法顯式指定位置。
如果您需要在彈出窗口關(guān)閉時(shí)執(zhí)行某些操作,您可以使用 addListener() 方法為其附加監(jiān)聽器。
如果只是想簡(jiǎn)單的創(chuàng)建一個(gè)通知內(nèi)容,可以通過 JBPopupFactory.getInstance().createMessage("這是一個(gè)消息").showInFocusCenter(); 來創(chuàng)建一個(gè)很簡(jiǎn)單的消息。
Notifications
要顯示一些通知信息,可以使用 Dialogs、Popup,但通常來說顯示非模態(tài)通知的方法是使用 Notifications 類。
它有兩個(gè)主要優(yōu)點(diǎn):
- 用戶可以控制每種通知類型的顯示方式,通過 Settings/Preferences | Appearance & Behavior | Notifications
- 所有顯示的通知都收集在事件日志工具窗口中,供以后查看
通知的文本支持 HTML 標(biāo)記。
使用 Notification.addAction(AnAction) 可以在內(nèi)容下方添加鏈接,通過 NotificationAction 可以更方便的使用。
可以通過 Notification 構(gòu)造函數(shù)的 groupId 參數(shù)指定通知類型。用戶可以在 Settings/Preferences | Appearance & Behavior | Notifications 中選擇每種通知類型對(duì)應(yīng)的顯示類型。
普通 Notification
要通過首選項(xiàng)指定顯示類型,需要使用 NotificationGroup 創(chuàng)建通知,下面是使用 NotificationGroup 方式來創(chuàng)建 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("這是一個(gè) Notification INFORMATION", NotificationType.INFORMATION)
.notify(project);
NotificationGroupManager.getInstance().getNotificationGroup("Custom Notification Group WARNING")
.createNotification("這是一個(gè) Notification WARNING", NotificationType.WARNING)
.notify(project);
NotificationGroupManager.getInstance().getNotificationGroup("Custom Notification Group ERROR")
.createNotification("這是一個(gè) Notification ERROR", NotificationType.ERROR)
.notify(project);
}
}
執(zhí)行后在 IDEA 的右下角就可以看到通知出現(xiàn),應(yīng)該是同時(shí)最多能展示 2 個(gè) Notification,創(chuàng)建的 INFORMATION 并沒有同時(shí)展示出來。同時(shí)在 Event Log 里面可以看到通知記錄。
![]() Notification
|
![]() Event Log
|
|---|
帶 HTML 標(biāo)記的 Notification
下面創(chuàng)建了一個(gè)帶有 HTML 標(biāo)簽的通知消息,不過貌似對(duì) HTML 標(biāo)簽的支持不是特別好。
public class BalloonHtmlText extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getProject();
if (Objects.isNull(project)) {
return;
}
// 創(chuàng)建一個(gè)消息
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());
}
}

File and Class Choosers
通過 Dialog
要讓用戶選擇一個(gè)文件、目錄或多個(gè)文件,可以使用 FileChooser.chooseFiles() 方法。這個(gè)有多個(gè)重載方法。最好用的方式選擇返回 void 的方法,并傳入一個(gè)接收所選文件列表作為參數(shù)的回調(diào)。類似下面的方法:
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 類控制可以選擇哪些文件。構(gòu)造函數(shù)參數(shù)指定是否可以選擇文件和(或)目錄,以及是否允許多選(詳細(xì)說明請(qǐng)參見 FileChooserDescriptorFactory)。
要對(duì)允許的選擇進(jìn)行更細(xì)粒度的控制,可以覆寫 isFileSelectable() 方法。還可以通過覆寫 getIcon()、getName() 和 getComment() 方法來自定義文件的呈現(xiàn)方式。需要注意的是,macOS 系統(tǒng)對(duì)大多數(shù)的自定義都不支持。如果確實(shí)想要修改,則需要使用重載的 chooseFiles() 來顯示標(biāo)準(zhǔn)的 IntelliJ 平臺(tái)對(duì)話框。
通過 Textfield
使用文件選擇器的一種非常常見的方法是使用文本字段輸入路徑,并使用省略號(hào)按鈕 (...) 來顯示文件選擇器。要?jiǎng)?chuàng)建這樣的控件,請(qǐng)使用 TextFieldWithBrowseButton 組件,并對(duì)其調(diào)用 addBrowseFolderListener() 方法來設(shè)置文件選擇器。
通過 Tree
通過 TreeFileChooserFactory 類可以使用另一種選擇文件的 UI。當(dāng)使用輸入文件名來搜索選擇文件時(shí),這種 UI 的效果是最好的。
這個(gè) API 顯示的對(duì)話框有兩個(gè)選項(xiàng)卡:
- 一個(gè)是顯示項(xiàng)目結(jié)構(gòu)
- 另一個(gè)是顯示類似于 Navigate | File 的文件列表。
要顯示對(duì)話框,請(qǐng)?jiān)?createFileChooser() 返回的選擇器上調(diào)用 showDialog() 方法。通過調(diào)用 getSelectedFile() 來獲得用戶的選擇。
Class 文件選擇
如果想提供選擇 Java 類的功能,可以使用 TreeClassChooserFactory 類。其不同的方法允許指定獲取類的范圍,可以將選擇限制為特定類的子類或接口的實(shí)現(xiàn),以及包含或排除內(nèi)部類等。
UI 的效果與 TreeFileChooserFactory 非常類似。
Package 選擇
如果要選擇 Java 包,可以使用 PackageChooserDialog 類。這個(gè)類繼承自 DialogWrapper,使用起來與前面介紹的 DialogWrapper 一致。
下面是一個(gè)簡(jiǎn)單的示例,集合了上面介紹的 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;
}
}
}
這是整個(gè) Dialog 的顯示樣式。

下面是分別使用 5 種文件選擇器的文件選擇 UI 效果。
普通文件選擇

Textfield 文件選擇

Tree 文件選擇
![]() Tree 文件選擇
|
![]() Tree 文件選擇
|
|---|
Class 文件選擇
![]() Class 文件選擇
|
![]() Class 文件選擇
|
|---|
Package 選擇






