
IntelliJ Platform 中提供了大量的自定義 Swing 組件,使用這些組件能讓你的 Plugin 保持跟 IDE 其他部分一致的展示效果及運行狀態(tài)。同時使用這些組件相比使用默認的 Swing 組件可以減少大量的代碼編寫工作。
Tool Windows
ToolWindow 是 IDE 的子窗口(面板),用于顯示信息及其他交互操作。這些窗口通常在主窗口的外邊緣有自己的工具欄(如 Project 窗口),其中包含一個或多個工具窗口按鈕,這些按鈕顯示在主 IDE 窗口左側(cè)、底部和右側(cè)的面板。
ToolWindow 使用 com.intellij.toolWindow 擴展點在 plugin.xml 中注冊。下面是 com.intellij.toolWindow 擴展點所支持的屬性:
- id : 對應于工具窗口按鈕上顯示的文本
- icon : 在工具窗口按鈕上顯示的圖標(13x13 像素,灰色且單色;請參閱 IntelliJ Plateform UI 指南中的說明)
- anchor : 表示 ToolWindow 顯示在的屏幕哪一側(cè)(支持 left(默認)、right、bottom)
- secondary : 指定 ToolWindow 是顯示在主要組還是次要組
- factoryClass : 需要實現(xiàn) ToolWindowFactory 接口的類名稱。只有當用戶點擊 ToolWindow 按鈕時,工廠類的 createToolWindowContent() 方法才會調(diào)用并初始化 ToolWindow 的 UI。如果用戶不與 ToolWindow 交互,則不會加載或執(zhí)行任何插件代碼。
示例
下面我們編寫一個最簡單的 ToolWindow 示例,用來實時顯示系統(tǒng)時間,雖然這個功能貌似沒有任何用o(╯□╰)o。
首先我們定義 ToolWindow 的顯示布局,及操作行為。
/**
* 時間展示窗口
*
* @author 魚蠻 Date 2022/6/18
*/
public class TimeDisplayWindow {
/** 容器 */
private JPanel content;
/** 時間顯示 label */
private JLabel timeLabel;
public TimeDisplayWindow(ToolWindow toolWindow) {
new Thread(() -> {
while (true) {
timeLabel.setText(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
// do nothing
}
}
}).start();
}
public JPanel getContent() {
return content;
}
}
代碼很簡單,只是啟動了一個線程,每隔 1 秒鐘修改 timeLabel 字段的 text 屬性為當前時間。
這里 ToolWindow 的顯示布局我使用的是 IDEA 提供的 GUI Designer form,雖然不那么好用,但是總比在代碼中手動去布局要好點,邏輯代碼跟 Swing 布局代碼也可以做到分離,有點 MVC 的意思。
所以這里需要定義一個 GUI Designer form 文件并與 TimeDisplayWindow 進行綁定。
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.roc.toolwindws.TimeDisplayWindow">
<grid id="27dc6" binding="content" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="e0311" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="當前時間 : "/>
</properties>
</component>
<component id="c6bed" class="javax.swing.JLabel" binding="timeLabel">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Label"/>
</properties>
</component>
<hspacer id="b81a1">
<constraints>
<grid row="0" column="2" row-span="3" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<vspacer id="b08fe">
<constraints>
<grid row="1" column="0" row-span="2" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<vspacer id="ab7b8">
<constraints>
<grid row="1" column="1" row-span="2" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
</children>
</grid>
</form>
在 IDEA 的 GUI Designer 面板中可以新增、刪除、拖動組件來調(diào)整布局,不是那么好用,需要習慣。調(diào)整完的布局也很簡單,只是定義了一個 JPanel 當容器,定義了兩個 JLabel 一個固定展示“當前時間 : ”,一個動態(tài)展示“當前時間”。GUI Designer 面板效果如下所示。

接著我們需要定義一個 ToolWindowFactory 對象,來實現(xiàn) ToolWindow 的創(chuàng)建。
/**
* 日期顯示窗口工廠
*
* @author 魚蠻 Date 2022/6/18
*/
public class TimeDisplayWindowFactory implements ToolWindowFactory {
@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
TimeDisplayWindow timeDisplayWindow = new TimeDisplayWindow(toolWindow);
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
Content content = contentFactory.createContent(timeDisplayWindow.getContent(), "", false);
toolWindow.getContentManager().addContent(content);
}
}
最后我們還需要在 plugin.xml 中聲明這個擴展點。
<extensions defaultExtensionNs="com.intellij">
<toolWindow id="時間顯示窗口" anchor="right" factoryClass="com.roc.toolwindws.TimeDisplayWindowFactory" />
</extensions>
至此我們新增一個 ToolWindow 的工作就全部完成了,運行這個插件,發(fā)現(xiàn)在 IDEA 右側(cè)的邊欄多出了“時間顯示窗口”這個按鈕,點擊后將展示我們定義的 ToolWindow ,時間也可以做到不停更新。

Dialogs
DialogWrapper
DialogWrapper 是應用于 IntelliJ Plateform 中顯示的所有模態(tài)對話框(和一些非模態(tài)對話框)的基類。
它提供以下功能:
- 按鈕布局(特定于平臺的確定/取消按鈕順序,及 macOS 中特定的幫助按鈕)
- 上下文幫助
- 記住對話框的大小
- 非模態(tài)驗證(當對話框中輸入的數(shù)據(jù)無效時顯示錯誤消息文本)
- 鍵盤快捷鍵:
- Esc 關(guān)閉對話框
- 左/右按鍵用于在按鈕之間切換
- Y/N 表示是/否操作(如果它們存在于對話框中)
- 可選的不再詢問復選框
使用步驟
- 調(diào)用基類構(gòu)造函數(shù)并傳入需要顯示 Dialog 的 Project 對象,或提供 Dialog 的父組件
- 調(diào)用 setTitle() 方法來設置 Dialog 的標題
- 在構(gòu)造函數(shù)中調(diào)用 init() 方法
- 實現(xiàn) createCenterPanel() 方法,返回這個 Dialog 中的組件(包含要展示的內(nèi)容)
其他說明
- 可以重寫 getPreferredFocusedComponent() 方法來返回一個組件,用與在 Dialog 首次展示時獲取焦點
- 可以重寫 getDimensionServiceKey() 方法來返回一個用于持久化 Dialog 尺寸的標識
- 可以重寫 getHelpId() 方法來返回一個與 Dialog 相關(guān)的的上下文幫助主題
- 使用 show() 方法來展示 Dialog,并且使用 getExitCode() 方法來檢查 Dialog 是如何關(guān)閉的( DialogWrapper#OK_EXIT_CODE|CANCEL_EXIT_CODE|CLOSE_EXIT_CODE),showAndGet() 方法可以看成是上面兩種方法的組合。
- 如果想自定義 Dialog 的展示按鈕(代替標準的 OK/Cancel/Help),可以重寫 createActions() 或者 createLeftActions() 方法。如果是關(guān)閉按鈕,需要使用 DialogWrapperExitAction 作為這個類的基類。
- 使用 action.putValue(DialogWrapper.DEFAULT_ACTION, true) 方法來設置默認的按鈕。
示例
跟 ToolWindow 類似 DialogWrapper 類也經(jīng)常與 GUI Designer form 一起使用,綁定一個GUI Designer form 到一個繼承自 DialogWrapper 的類,并且綁定這個 form 的頂級 panel 到一個字段,在 createCenterPanel() 方法中返回這個字段。
/**
* 提醒休息的 Dialog
*
* @author 魚蠻 Date 2022/6/18
*/
public class RestRemindDialogWrapper extends DialogWrapper {
/** 容器 */
private JPanel content;
/** 顯示內(nèi)容 */
private JLabel textLabel;
/** 圖片內(nèi)容 */
private JLabel imageLabel;
public RestRemindDialogWrapper() {
// 使用當前窗口作為父窗口
super(true);
setTitle("休息一下");
init();
}
@Nullable
@Override
protected JComponent createCenterPanel() {
imageLabel.setIcon(new ImageIcon(getClass().getResource("/dialog/relax.png")));
textLabel.setText("工作再忙,也要記得休息一下。");
return content;
}
}
首先定義一個 RestRemindDialogWrapper 類繼承自 DialogWrapper,這里也不做其他操作,只是設置了兩個 JLabel 的屬性,一個加載一張圖片,一個設置固定的文本內(nèi)容。
下面是新建一個 GUI Designer form ,來控制容器的布局。
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.roc.dialog.RestRemindDialogWrapper">
<grid id="1ec18" binding="content" layout-manager="GridLayoutManager" row-count="4" column-count="5" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="5" y="5" width="650" height="373"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="aa8ee" class="javax.swing.JLabel" binding="textLabel">
<constraints>
<grid row="2" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Label"/>
</properties>
</component>
<vspacer id="991cc">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<vspacer id="b381e">
<constraints>
<grid row="3" column="2" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="69863" class="javax.swing.JLabel" binding="imageLabel">
<constraints>
<grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value=""/>
</properties>
</component>
<hspacer id="a49e4">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<hspacer id="7a146">
<constraints>
<grid row="1" column="4" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
</children>
</grid>
</form>
Dialog 本身是不需要在 plugin.xml 中聲明的,可以在任意地方新建展示,比如對一個休息提醒的插件來說,應該是在后臺計算運行時間,到點自動提醒。這里我們?yōu)榱撕唵危x了一個 Action 作為插件的喚起入口,并在 plugin.xml 中進行聲明。
/**
* 休息提醒 Action
* @author 魚蠻 on 2022/2/18
**/
public class ResetRemindAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
if (new RestRemindDialogWrapper().showAndGet()) {
// 按下了 OK 鍵
System.out.println("休息結(jié)束");
}
}
}
<action id="dialog_show" class="com.roc.dialog.ResetRemindAction" text="休息一下" >
<add-to-group group-id="ShowIntentionsGroup" anchor="first" />
</action>
做完這些之后,運行一下插件。在編輯面板中右鍵,選擇“休息一下”,就可以看到我們要展示的 Dialog 了,與預期完全一致。
