IntelliJ IDE 插件开发 | (二)UI 界面与数据持久化

系列文章

前言

上一篇文章中介绍了在IDEA下开发、运行和安装插件的基本步骤,因此创建项目等基础步骤不再赘述,本文则开始介绍如何进行 UI 界面的开发以及相关数据的持久化存储,本文涉及到的的完整代码已上传到Github

UI 界面开发

在开发插件的过程中,我们或多或少都需要进行 UI 界面的开发,但是IntelliJ IDE插件需要使用Swing进行 UI 的开发,相信大部分人都不太了解,因此本文及后续文章都选择使用UI Designer这款插件(IDEA 默认安装,可自行检查一下)通过可视化工具拖拽的方式来实现基本的界面设计。

UI Designer 使用步骤

在上一篇文章中提到过,后续 UI 相关的开发使用 Java,其他则使用 Kolin,由于默认创建的工程目录如下,没有 Java 模块:

所有我们首先需要创建 Java 模块:

然后按照引导创建 UI 文件:

创建完成后,会出现类似下图的界面:

可以看到,默认会创建一个 Java 类和一个 form 布局文件,其中 Java 文件主要用于后续控制字段的初始化及获取等操作,form 文件则用于界面布局,点击 form 文件会出现上图所示的三个栏目:组件属性栏、UI 效果栏和组件栏,其中先选中右侧组件然后就可以拖拽到中间的 UI 效果栏展示。

下面我们用一个简单的登录表单来讲解使用方式,首先以一个 GIF 演示拖拽功能开始:

修改 Label 的内容可以通过左侧工具栏或者直接在 UI 效果栏双击标签进行:

输入框对应的字段名则通过左侧组件属性栏进行修改:

这时候我们查看 UIDemo.java 文件会发现以下内容(注释随自己的配置变化):

java 复制代码
import javax.swing.*;

/**
 * UI 界面
 *
 * @author butterfly
 * @date 2023-12-06
 */
public class UIDemo {
    private JTextField username;
    private JTextField password;
}

然后通过预览功能,我们也可以先查看表单效果:

简单的使用步骤就到这里,这里就不挨个讲解组件的效果和使用方式了,后续会在使用的过程和实战应用中再进行讲解,大家也可以先自行探索。

使用平台自带组件

在上一小节中我们讲解了如何通过UI Designer插件来创建我们的 UI 界面,可以发现默认提供的 Swing UI 组件并没有办法满足我们的日常使用,比如文件下拉树选择组件就不存在。而我们对 Swing 的开发又不熟悉,那该怎么办呢?还好,我们还可以使用 IntelliJ 平台自带的组件,下面就来讲解使用方法:

首先在空白处右键创建一个分组:

然后右键分组选择第一项添加组件:

选择类名的方式,然后点击...

这里输入TextFieldWithBrowseButton即可找到带有文件下拉树选择输入框:

然后经过两次确认就可以发现组件已经添加到了左侧组件栏中:

由于 UI Designer 的预览功能不支持原生组件的预览,需要运行插件才可以,这里先知道是类似下图选择文件夹的效果即可:

平台自带的组件大多都可以在com.intellij.openapi.ui包下找到,根据组件名TextFieldWithBrowseButton我们也可以发现相关组件的命名也很规范,因此当需要某个组件时就可以先在该包下或者通过关键字进行搜索,除此之外,我们还可以参考开源插件来找到和学习原生组件的使用方式。

以 Git 插件为例,布局如下:

可以在官方仓库找到源码(这里选择的是 192 版本这个分支,之后的版本布局开始使用 Kotlin 进行了重构):

根据插件界面可以发现第一行就是使用带有文件树选择的输入框,我们在代码中也可以找到对应实现:

由于 192 版本之后使用 Kotlin 进行 UI 的编写,因此初学建议可以下载一个 192 的 IDEA 社区版进行界面的参考,这样通过参考布局及相应源码也是快速学习 UI 界面开发的一种方法。

至于如何快速找到插件的某个组件实现,这里建议在下载源码后,就可以根据界面上的提示文字进行代码的全局搜索即可。

在配置界面和侧边栏中展示 UI 界面

通过上述两步,我们可以了解到如何实现开发一个简单的 UI 界面,下面就开始讲解如何将设计好的界面展示在配置页面或者侧边栏中。

准备工作

在正式开始之前我们需要对上文中创建的界面进行一些修改,布局外层的 Panel 需要先设置一下字段名:

然后增加相应的 Get 方法:

java 复制代码
import javax.swing.*;

/**
 * UI 界面
 *
 * @author butterfly
 * @date 2023-12-06
 */
public class UIDemo {
    
    private JTextField username;
    private JTextField password;
    private JPanel mainPanel;

    public JPanel getMainPanel() {
        return mainPanel;
    }

}

准备工作到此结束。

配置界面 UI

接下来就是将 UI 展示在配置界面,我们先创建一个UISettingsConfig.kt文件:

kotlin 复制代码
import cn.butterfly.ui.UIDemo
import com.intellij.openapi.options.Configurable
import javax.swing.JComponent

/**
 * UI 配置界面配置类
 *
 * @author butterfly
 * @date 2023-12-06
 */
class UISettingsConfig: Configurable {
    
    private val form = UIDemo()

    private val component: JComponent
    
    init {
        component = form.mainPanel
    }
    
    override fun createComponent() = component
    
    override fun isModified() = true

    override fun apply() {}

    override fun getDisplayName() = "UISettingsConfig"

}

然后在plugin.xml文件中进行如下的配置,这里的applicationConfigurable代表使用应用级别的配置,相应地,还有projectConfigurable代表项目级别的配置,具体区别在下文的数据持久化中进行介绍:

xml 复制代码
<extensions defaultExtensionNs="com.intellij">
    <applicationConfigurable
            instance="cn.butterfly.ui.config.UISettingsConfig"
            id="cn.butterfly.ui.config.UISettingsConfig"
            displayName="UISettingsConfig"/>
</extensions>

这里我们运行插件,打开配置界面就可以发现我们的界面效果了:

如果想要修改其显示位置,比如显示在Tools菜单下:

只需要在配置中增加parentId="tools"即可:

xml 复制代码
<applicationConfigurable
        instance="cn.butterfly.ui.config.UISettingsConfig"
        id="cn.butterfly.ui.config.UISettingsConfig"
        parentId="tools"
        displayName="UISettingsConfig"/>

applicationConfigurable中完整的配置项可查看官网文档

侧边栏界面 UI

最后再来说明如何在侧边栏中显示界面,首先创建一个UISidebarConfig.kt

kotlin 复制代码
import cn.butterfly.ui.UIDemo
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.content.ContentFactory
import javax.swing.JComponent

/**
 * UI 侧边栏界面配置类
 *
 * @author butterfly
 * @date 2023-12-06
 */
class UISidebarConfig: ToolWindowFactory {
    
    private val form = UIDemo()

    private val component: JComponent
    
    init {
        component = form.mainPanel
    }
    
    override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
        toolWindow.contentManager.addContent(
            ContentFactory.getInstance().createContent(component, "", false))
    }

}

然后我们可以准备一个 svg 图标文件用于后续在侧边栏展示:

同时需要创建对应的文件加载接口:

java 复制代码
import com.intellij.openapi.util.IconLoader;
import javax.swing.*;

/**
 * 插件图标
 *
 * @author butterfly
 * @date 2023-12-06
 */
public interface PluginIcons {

    Icon BUTTERFLY = IconLoader.getIcon("/icons/butterfly.svg", PluginIcons.class);

}

然后在plugin.xml配置文件中进行如下设置即可,anchor可设置默认位置,icon用于设置上文的图标:

xml 复制代码
<extensions defaultExtensionNs="com.intellij">
    <toolWindow id="UISettingsConfig" 
                anchor="right"
                factoryClass="cn.butterfly.ui.config.UISidebarConfig"
                icon="cn.butterfly.ui.icons.PluginIcons.BUTTERFLY"/>
</extensions>

运行插件,即可在右侧的侧边栏看到我们设置的界面:

数据持久化

讲完了界面开发的相关内容,下面就开始介绍如何进行配置数据的持久化,首先是创建一个存储数据信息的UIDemoState.kt文件:

kotlin 复制代码
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.util.xmlb.XmlSerializerUtil

/**
 * 数据持久化存储
 *
 * @author butterfly
 * @date 2023-12-06
 */
@Service
@State(name = "UIDemoState", storages = [Storage("ui-demo-state.xml")])
class UIDemoState: PersistentStateComponent<UIDemoState> {
    
    var username = ""
    
    var password = ""
    
    override fun getState(): UIDemoState {
        return this
    }

    override fun loadState(state: UIDemoState) {
        XmlSerializerUtil.copyBean(state, this)
    }

}

其中usernamepassword对应表单中我们设置的两个字段,这里设置了默认值为空字符串,ui-demo-state.xml用于设置数据持久化存储的文件名。

然后我们需要对前文中创建的布局文件进行些许的修改,增加字段的 Get 方法:

java 复制代码
import javax.swing.*;

/**
 * UI 界面
 *
 * @author butterfly
 * @date 2023-12-06
 */
public class UIDemo {

    /**
     * 用户名
     */
    private JTextField username;
    
    /**
     * 密码
     */
    private JTextField password;
    
    private JPanel mainPanel;

    public JTextField getUsername() {
        return username;
    }

    public JTextField getPassword() {
        return password;
    }

    public JPanel getMainPanel() {
        return mainPanel;
    }

}

最后我们就需要对前文中创建的UISettingsConfig.kt文件进行修改,用于处理界面上保存数据的操作:

kotlin 复制代码
import cn.butterfly.ui.UIDemo
import cn.butterfly.ui.state.UIDemoState
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.options.Configurable
import javax.swing.JComponent

/**
 * UI 配置界面配置类
 *
 * @author zjw
 * @date 2023-12-06
 */
class UISettingsConfig: Configurable {
    
    private val form = UIDemo()
    
    private val component: JComponent
    
    private val state = ApplicationManager.getApplication().getService(UIDemoState::class.java)
    
    init {
        component = form.mainPanel
        reset()
    }
    
    override fun createComponent() = component
    
    override fun isModified(): Boolean {
        return state.username != form.username.text || state.password != form.password.text
                
    }
    override fun apply() {
        state.username = form.username.text
        state.password = form.password.text
    }
    
    override fun reset() {
        form.username.text = state.username
        form.password.text = state.password
    }
    
    override fun getDisplayName() = "UISettingsConfig"
    
}

其中reset用于重置表单内容为修改前设置的值,apply用于保存当前修改,isModified用于判断当前数据和上次数据之间是否存在不同,用于Apply按钮的禁用/激活状态的切换,这三个操作对应界面上的效果如下:

那我们的配置文件存储位置是在哪里呢?

由于在前文中我们使用applicationConfigurable选择了应用级别的存储,因此文件就存储在 IDEA 默认的配置文件存储地址:C:\Users\用户名\AppData\Roaming\JetBrains\IntelliJIdea2023.2\options,其中IntelliJIdea2023.2对应自己使用的 IDEA 名称。不过,由于我们当前是通过沙盒环境运行,所以位置并不是全局设置的位置,而是在build/idea-sandbox/config/options下:

当我们将插件安装到我们正式的 IDEA 中,也就可以在上述的位置下发现配置文件了:

如果我们将配置设为projectConfigurable选择项目级别,那么首先需要对配置文件进行如下修改:

xml 复制代码
<extensions defaultExtensionNs="com.intellij">
    <projectConfigurable
            instance="cn.butterfly.ui.config.UISettingsConfig"
            id="cn.butterfly.ui.config.UISettingsConfig"
            parentId="tools"
            displayName="UISettingsConfig"/>
</extensions>

同时修改UIDemoState.kt文件上的注解配置:

kotlin 复制代码
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.util.xmlb.XmlSerializerUtil

/**
 * 数据持久化存储
 *
 * @author butterfly
 * @date 2023-12-06
 */
@Service(Service.Level.PROJECT) // 只修改改行
@State(name = "UIDemoState", storages = [Storage("ui-demo-state.xml")])
class UIDemoState: PersistentStateComponent<UIDemoState> {
    
    var username = ""
    
    var password = ""
    
    override fun getState(): UIDemoState {
        return this
    }

    override fun loadState(state: UIDemoState) {
        XmlSerializerUtil.copyBean(state, this)
    }

}

最后还需要修改UISettingsConfig.kt文件,增加一个 project 的构造参数并修改 state 的初始化:

kotlin 复制代码
import cn.butterfly.ui.UIDemo
import cn.butterfly.ui.state.UIDemoState
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.project.Project
import javax.swing.JComponent

/**
 * UI 配置界面配置类
 *
 * @author butterfly
 * @date 2023-12-06
 */
// 增加 project: Project 构造参数
class UISettingsConfig(project: Project): Configurable {
    
    private val form = UIDemo()

    private val component: JComponent
    
    // 修改改行
    private val state = project.getService(UIDemoState::class.java)
    
    init {
        component = form.mainPanel
        reset()
    }
    
    override fun createComponent() = component
    
    override fun isModified(): Boolean {
        return state.username != form.username.text || state.password != form.password.text
                
    }

    override fun apply() {
        state.username = form.username.text
        state.password = form.password.text
    }
    
    override fun reset() {
        form.username.text = state.username
        form.password.text = state.password
    }

    override fun getDisplayName() = "UISettingsConfig"

}

然后我们就可以在项目下的.idea文件夹中找到我们针对项目级别的配置了:

至于读取配置文件,则通过private val state = project.getService(UIDemoState::class.java)或者private val state = ApplicationManager.getApplication().getService(UIDemoState::class.java)分别换取项目或应用级别的 state 对象,然后读取其中的字段即可。

总结

本文讲解了关于 UI 界面开发和数据持久化相关的内容,如果有错误或不足之处,欢迎一起交流讨论。

相关推荐
追逐时光者5 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_5 小时前
敏捷开发流程-精简版
前端·后端
苏打水com5 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧6 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧6 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧6 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧7 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧7 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng8 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端
烙印6018 小时前
Spring容器的心脏:深度解析refresh()方法(上)
java·后端·spring