跳转至

UI、通知与用户体验

插件 UI 的目标不是“能显示控件”,而是让用户感觉它就是 IDE 的自然组成部分。官方文档建议优先使用 IntelliJ Platform 提供的 UI 组件、Kotlin UI DSL、DialogWrapper、非模态通知、Tool Window 和状态栏 Widget,而不是直接堆普通 Swing 控件。

UI 选择原则

用户意图 推荐入口 不推荐
执行一个明确命令 Action、Popup、Toolbar 自定义按钮散落在多个面板
长期查看插件状态 Tool Window 反复弹窗
配置插件行为 Settings / Configurable 自定义配置文件编辑器
阻止用户继续前需要输入 DialogWrapper 原生 JDialog
告知不阻塞的结果或警告 Notification、Editor Hint、Editor Banner 模态 Message Box
展示总是相关的小状态 Status Bar Widget 常驻 Tool Window 或通知

如果一个功能可以通过已有 IDE 模式表达,就不要发明新的交互模式。

使用平台 UI 组件

IntelliJ Platform 提供大量定制 Swing 组件。它们会自动适配主题、缩放、字体、间距和 IDE 行为。常见建议:

  • Settings 和 Dialog 中使用 Kotlin UI DSL。
  • 对旧 Swing 组件启用 Plugin DevKit 的 Undesirable class usage 检查,寻找平台替代组件。
  • 使用 UI Inspector 查看 IDE 现有 UI 的组件实现。
  • UI 文案遵循 JetBrains UI Guidelines:短、明确、面向用户动作。
  • Kotlin 项目不要使用 UI Designer 插件生成表单。

Kotlin UI DSL 适合设置页和对话框:

panel {
    row("Endpoint:") {
        textField()
            .bindText(settings::endpoint)
            .comment("Base URL used by the plugin.")
    }
    row {
        checkBox("Enable analysis")
            .bindSelected(settings::enabled)
    }
}

DialogWrapper

平台模态对话框应继承 DialogWrapper。它提供:

  • 平台一致的 OK/Cancel/Help 按钮顺序。
  • Esc 关闭、按钮焦点切换等快捷键。
  • 对话框尺寸记忆。
  • 非模态输入校验。
  • “Do not ask again” 等标准行为。

最小结构:

class ExampleDialog(project: Project) : DialogWrapper(project) {
    private val nameField = JBTextField()

    init {
        title = "Create Example"
        init()
        initValidation()
    }

    override fun createCenterPanel(): JComponent {
        return panel {
            row("Name:") {
                cell(nameField)
                    .align(AlignX.FILL)
            }
        }
    }

    override fun getPreferredFocusedComponent(): JComponent = nameField

    override fun doValidate(): ValidationInfo? {
        return if (nameField.text.isBlank()) {
            ValidationInfo("Name is required", nameField)
        } else {
            null
        }
    }
}

显示并判断结果:

if (ExampleDialog(project).showAndGet()) {
    // OK pressed
}

不要在用户按 OK 后再弹一个错误框提示输入无效。应在 doValidate() 中返回 ValidationInfo,让错误直接贴近输入控件。

通知用户

官方文档明确建议避免用模态消息框通知错误或普通事件。根据场景选择非模态 UI:

场景 推荐 API
编辑器中当前动作无法完成 HintManager.showErrorHint()
文件顶部需要用户配置 SDK、环境或依赖 EditorNotificationProvider + EditorNotificationPanel
重要新功能或变更提示 GotItTooltip
普通后台任务结果、警告、需要用户稍后处理 Notification Balloons
对话框输入错误 DialogWrapper.doValidate()

Editor Banner 示例思路:

<extensions defaultExtensionNs="com.intellij">
  <editorNotificationProvider implementation="com.example.ExampleEditorNotificationProvider"/>
</extensions>

如果不访问索引,Provider 应实现 DumbAware,保证索引构建期间也能显示关键配置提示。

Notification Balloon

通知适合非阻塞消息。典型内容包括同步完成、配置缺失、后台任务失败、插件需要用户打开设置页等。

实践建议:

  • 使用明确的 notification group。
  • 提供可执行 action,例如 “Open Settings”。
  • 错误消息要包含用户下一步,而不是只展示异常文本。
  • 不要把大量日志塞进通知;详细信息应进入 Event Log、Run Console 或 Tool Window。
  • 高频事件不要逐条发通知,应该合并或只显示状态。

Status Bar Widget

状态栏 Widget 适合展示总是相关、短小、当前上下文相关的信息,例如编码、分支、当前文件状态、插件开关。

注册入口是 StatusBarWidgetFactory

<extensions defaultExtensionNs="com.intellij">
  <statusBarWidgetFactory
      id="ExampleStatus"
      implementationClass="com.example.ExampleStatusBarWidgetFactory"/>
</extensions>

注意:

  • plugin.xmlid 必须与 StatusBarWidgetFactory.getId() 匹配。
  • 只展示真正值得常驻的信息,状态栏空间非常有限。
  • 文件相关状态可继承 StatusBarEditorBasedWidgetFactory
  • Widget 需要实现 StatusBarWidget,由 factory 创建和释放。
  • LightEdit 模式默认不显示 Widget;需要支持时实现 LightEditCompatible

UI 可访问性与可维护性

最低检查:

  • 支持亮色、暗色和高对比主题。
  • 文本不写死颜色,图标使用主题适配资源。
  • 所有后台任务有进度和取消路径。
  • 错误提示贴近用户上下文。
  • 设置页支持搜索。
  • 控件文案能被非插件作者理解。
  • UI 创建和模型访问遵守 EDT/BGT 与读写动作规则。

参考来源