以前、「ToolWindow上に色々なコンポーネントを表示してみる」ことを試してみました。
IntelliJ Platform Pluginの開発にて、Kotlin UI DSL Version 2 や Swing を使って、ToolWindow上にコンポーネントを表示してみた - メモ的な思考的な
そんな中、「TextFieldの入力値に従って絞り込み可能なテーブル」を実現できるか気になったことから、試してみたときのメモを残します。
目次
- 環境
- IntelliJ Platform PluginのToolWindowにテーブルを表示するには
- 絞り込み機能がないテーブルを実装
- データを固定で絞り込んだテーブルを実装
- テーブルのヘッダ行があるテーブルを実装
- TextFieldの入力値で絞り込めるテーブルを実装
- ソースコード
環境
- プラグインの開発環境
IntelliJ Platform PluginのToolWindowにテーブルを表示するには
どのようにすればテーブルを表示できるか分からなかったため、まずはそこから調べてみます。
IntelliJ Platform Pluginの公式ページには、テーブルをどのように表示すればよいかは記載されていませんでした。
一方、IntelliJ Platform PluginのToolWindow上にはJavaのSwingコンポーネントを表示できることは分かっています。
そのため、Swingのテーブル(JTable)と同じような感じで作っていけるのではと考えました。
次に、ToolWindowsで使えるコンポーネントをIDE上でながめていると、 com.intellij.ui.table.JBTable
がありました。
Github上の実装を見たところ、 JTable
を継承していることが分かりました。
https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/ui/table/JBTable.java
そこで今回は、 JBTable
コンポーネントを使ってテーブルを表示してみることにしました。
絞り込み機能がないテーブルを実装
まずは、単純にテーブルを表示してみます。
JTableはどのようにしてデータを表示しているのか調べたところ、以下の記事に詳しく書かれていました。
JTable でデータとビューをつなぐテーブルモデル - Swing の JTable の使い方 - Java の Swing を用いた GUI - Java 入門
そこで、
- データ置き場として、
TableModel
インタフェースを実装したAbstractModel
」を継承したクラスを定義 - Contentに
JBTable
を定義 - ContentFactoryを定義
- plugin.xmlを定義
の順に実装していきます。
AbstractModelを継承したクラスを定義
まず、テーブルに関する値を AbstractModel
を継承したクラスに定義します。
今回は以下を定義しています。
allData
に、テーブルに表示するためのデータを2次元のリストで定義する- 今回はデータを3行用意
colkumns
に、テーブルの列を定義する- テーブルを表示するために必要最低限のメソッドをオーバーライドする
- getRowCount
- getColumnCount
- getValueAt
package com.github.thinkami.hellojetbrainsplugin.ui import javax.swing.table.AbstractTableModel class AppleTableModel: AbstractTableModel() { val allData: List<List<String>> = listOf( listOf("1", "シナノゴールド", "黄"), listOf("2", "シナノホッペ", "赤"), listOf("3", "ジョナゴールド", "赤"), ) val columns: List<String> = listOf("No", "Name", "Color") override fun getRowCount(): Int { return allData.size } override fun getColumnCount(): Int { return columns.size } override fun getValueAt(rowIndex: Int, columnIndex: Int): Any { return allData[rowIndex][columnIndex] } }
Contentを定義
続いて、テーブルを表示するための Contentを定義します。
ここでのポイントは以下でした。
- JBTableオブジェクトの
model
プロパティに、上記で作ったモデルオブジェクトを渡す - Kotlin UI DSL Version 2 に JBTableは無いので、
cell
を使う
package com.github.thinkami.hellojetbrainsplugin.ui import com.intellij.openapi.ui.DialogPanel import com.intellij.ui.dsl.builder.Cell import com.intellij.ui.dsl.builder.panel import com.intellij.ui.table.JBTable class AppleTableContent { var contentPanel : DialogPanel lateinit var myTableModel: Cell<JBTable> init { contentPanel = panel { row { val table = JBTable() val model = AppleTableModel() table.model = model myTableModel = cell(table) } } } }
ToolWindowFactoryを定義
上記で作った Content を ToolWindowFactoryを使って ToolWindow へ表示します。
package com.github.thinkami.hellojetbrainsplugin.toolWindow import com.github.thinkami.hellojetbrainsplugin.ui.AppleTableContent import com.intellij.openapi.project.Project import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowFactory import com.intellij.ui.content.ContentFactory class AppleTableToolWindowFactory: ToolWindowFactory { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { val content = ContentFactory.getInstance().createContent(AppleTableContent().contentPanel, null, false) toolWindow.contentManager.addContent(content) } }
plugin.xmlに、ToolWindowを追加
ToolWindowとして表示できるよう、 plugin.xml に定義を追加します。
<extensions defaultExtensionNs="com.intellij"> <toolWindow factoryClass="com.github.thinkami.hellojetbrainsplugin.toolWindow.AppleTableToolWindowFactory" id="AppleTable"/> <extensions />
動作確認
ToolWindowを表示してみると、想定した通りのデータが表示されました。
ここでの実装
データを固定で絞り込んだテーブルを実装
次に、データを絞り込んだテーブルを表示してみます。
AbstractModelを継承したクラスを修正
まずは絞り込み後のデータを入れておくプロパティ tableData
を用意します。
lateinit var tableData: List<List<String>>
次に
- getRowCount()
- getValueAt()
が参照するプロパティを tableData
へと変更します。
override fun getRowCount(): Int { return tableData.size } override fun getValueAt(rowIndex: Int, columnIndex: Int): Any { return tableData[rowIndex][columnIndex] }
続いて、実際に絞り込みを行うメソッド filterChanged()
を追加し、以下を行います。
シナノ
が含まれるデータに絞り込み、結果をtableData
へ反映AbstractTableModel
の変更をJBTableへ反映するため、fireTableDataChanged()
メソッドを実行
fun filterChanged() { tableData = allData.filter { val name = it[1] // Nameで絞り込むため、列番号を指定 val regex = Regex("シナノ") regex.containsMatchIn(name) }.toList() // allDataとは別オブジェクトにするため toList する // filterが更新されたことを通知する this.fireTableDataChanged() }
最後に、 init
の処理の中で filterChanged()
メソッドを呼び出します。
init { // 初期段階で絞り込みを実行する filterChanged() }
動作確認
再度 ToolWindow を表示してみると、シナノ
が含まれるデータのみテーブルに表示されました。
ここでの実装
以下のコミットになります。
https://github.com/thinkAmi-sandbox/hello_jetbrains_plugin/commit/3dabe5194dde2c265af98ce75ab2ba25246b9d7e
テーブルのヘッダ行があるテーブルを実装
ここまでのスクリーンショットをよく見ると、テーブルにヘッダ行が表示されていません。
そこで、ヘッダ行を表示するよう修正します。
Content で JBScrollPane (scrollCell) を使う
Swingとは異なり、IntelliJ Platform Plugin でテーブルのヘッダ行を表示するには JBScrollPane
(もしくは scrollCell) を使う必要があります。
https://plugins.jetbrains.com/docs/intellij/kotlin-ui-dsl-version-2.html#rowscrollcell-component
ここでは JBScrollPane
を使うように修正します。
lateinit var myTableModel: Cell<JBScrollPane> init { contentPanel = panel { row { // ... myTableModel = cell(JBScrollPane(table)) } } }
AbstractModelを継承したクラスで getColumnName をオーバーライド
ヘッダ行を表示するため、 getColumnName
メソッドをオーバーライドします。
override fun getColumnName(column: Int): String { return columns[column] }
動作確認
再度 ToolWindow を表示すると、ヘッダ行が表示されました。
ここでの実装
以下のコミットになります。
https://github.com/thinkAmi-sandbox/hello_jetbrains_plugin/commit/91df3491d049d07b003f5383eac88924d6e6a486
TextFieldの入力値で絞り込めるテーブルを実装
今回は以下の流れになるよう実装します。
- 画面で絞り込み文字列を入力すると、絞り込み文字列を管理するクラスへ通知
- 絞り込み文字列を管理するクラスが、AbstractModel を継承したクラスへ絞り込み条件が変更になったことを通知
- AbstractModel を継承したクラスが、データを絞り込んだ結果を画面のJBTableへ通知
- 画面が再描画
画面で絞り込み文字列を入力すると、絞り込み文字列を管理するクラスへ通知
AppleTableContent
クラスを修正します。
なお、「絞り込み文字列を入力する」というイベントを捕捉するため、今回は addDocumentListener
を使っています。
- JTable でデータフィルターを実装する方法 - Swing の JTable の使い方 - Java の Swing を用いた GUI - Java 入門
- swing - java documentlistener - Stack Overflow
- java - SwingのDocumentEventの追加について - スタック・オーバーフロー
class AppleTableContent { var contentPanel : DialogPanel lateinit var myTableModel: Cell<JBScrollPane> lateinit var searchText: Cell<JBTextField> // Modelオブジェクトを保持できるようプロパティを追加 val appleTableModel: AppleTableModel init { // 追加したプロパティに初期値を設定 appleTableModel = AppleTableModel() contentPanel = panel { // 絞り込み条件を入力するTextFieldを追加するとともに、イベントリスナーも追加 row { label("Search text") searchText = textField() searchText.component.document.addDocumentListener(object: DocumentListener { override fun insertUpdate(e: DocumentEvent?) { handleChange() } override fun removeUpdate(e: DocumentEvent?) { handleChange() } override fun changedUpdate(e: DocumentEvent?) { handleChange() } }) } row { val table = JBTable() table.model = appleTableModel myTableModel = cell(JBScrollPane(table)) } } } private fun handleChange() { appleTableModel.tableFilter.filterText = searchText.component.text } }
絞り込み文字列を管理するクラスが、AbstractModel を継承したクラスへ絞り込み条件が変更になったことを通知
絞り込み文字列を管理するクラス AppleTableFilter
を追加します。
なお、
AbstractModel を継承したクラスへ絞り込み条件が変更になったことを通知
を実現するため、コンストラクタで AbstractModel を継承したクラスを受け取っています。
package com.github.thinkami.hellojetbrainsplugin.ui // modelをコンストラクタで受け取りつつプロパティとして定義 class AppleTableFilter(val model: AppleTableModel){ var filterText: String = "" set(value) { if (value != filterText) { // 絞り込み文字列に変更があった場合のみ、プロパティを更新してモデルに変更があったことを通知する field = value this.model.filterChanged() } } }
AbstractModel を継承したクラスが、データを絞り込んだ結果を画面のJBTableへ通知
画面の JBTable
へ通知する処理はすでに実装しているため、データを絞り込むところのみ実装します。
クラスのプロパティとして、絞り込み文字列を管理するクラスを追加します。
val tableFilter: AppleTableFilter
続いて、 init
の中で、各種初期値を反映します。
init { // 初期条件による絞り込みを実行するため、初期値を設定しておく tableData = allData.toList() tableFilter = AppleTableFilter(this) filterChanged() }
あとは、絞り込み文字列を tableFilter
から取得するようにします。
fun filterChanged() { tableData = allData.filter { val name = it[1] // Nameで絞り込むため、列番号を指定 name.contains(this.tableFilter.filterText) }.toList() // allDataとは別オブジェクトにするため toList する // ... }
動作確認
絞り込み文字列を何も入力していないときは、全件表示されます。
絞り込み文字列を入力すると、それに応じた内容へと表示が変わります。
もし、絞り込み文字列に該当する行が存在しない場合、テーブルの枠だけが表示されます。
以上でやりたいことが実現できました。
ここでの実装
以下のコミットになります。
https://github.com/thinkAmi-sandbox/hello_jetbrains_plugin/commit/a9d699048c1ad626be487de52ff3615aa401d184
ソースコード
Githubにあげました。
https://github.com/thinkAmi-sandbox/hello_jetbrains_plugin
今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/hello_jetbrains_plugin/pull/14