IDEA 用戶界面組件(一)

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 面板效果如下所示。

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 表示是/否操作(如果它們存在于對話框中)
  • 可選的不再詢問復選框

使用步驟

  1. 調(diào)用基類構(gòu)造函數(shù)并傳入需要顯示 Dialog 的 Project 對象,或提供 Dialog 的父組件
  2. 調(diào)用 setTitle() 方法來設置 Dialog 的標題
  3. 在構(gòu)造函數(shù)中調(diào)用 init() 方法
  4. 實現(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 了,與預期完全一致。

工作再忙也要休息一下

User Interface Components

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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