Go言語(golang)でTUIアプリを作ろう ( その8 画面間データ連携について )
tviewで、画面の遷移をサポートしてみよう。
今回は、新たな画面を追加して画面間の遷移処理を組み込んでみましょう。
これまでの画面がデータを一覧表示(List)するものでしたから、次の画面は、選択された1件分を表示する詳細画面(Detail)となります。
[1] 画面遷移を行うには
Go言語(golang)でTUIアプリを作ろう(その3 tviewの基本構造)で、tviewの基本オブジェクト構造を、概念図にしてみました。
この図にありますように、tviewの中心にあるオブジェクトは、Applicationであり、画面を構成するPrimitiveは、すべてこのApplicationに紐付けられています。したがって、tviewで複数画面を連携するには、このApplicationオブジェクトを共有する必要があります。
アプリケーションの起動形態を、下記の2つに分けて考えてみるとよいでしょう。
(1) 初期起動の場合
Applicationオブジェクトは、まだ存在しない状態(nil)ですので、Applicationオブジェクトの生成から開始します。
(2) 他のクラスから起動された場合
- 例えば、ListからDetailに画面遷移する場合は、Listクラスで生成されたApplicationオブジェクトを、Detailに引き渡します。
- Detailでは、画面作成処理で生成したContainerをApplicationのSetRootメソッドにセットして、新たな画面を表示します。
Applicationオブジェクトを、上記の手順で再利用することになるわけです。このあたりのロジックについては、すでに前回示していますが、下記のようなコードになります。
func (self *MyApplication) run(app *tview.Application, primitive tview.Primitive) {
if app == nil {
self.app = tview.NewApplication()
if err := self.app.SetRoot(primitive, true).EnableMouse(true).Run(); err != nil {
panic(err)
}
} else {
self.app = app
self.display(primitive)
}
}
他の画面を起動するコードは下記となります。この例では、List画面からDetail画面を呼び出しています。
NewDetail().run(self.app, common)
[2] 詳細画面(Detail)の作成
さて、画面遷移を行う手法が明確になったところで、実際の画面を作成していきましょう。
上の画面イメージを作成していきます。新たに使用するPrimitiveは、表示フィールド、入力フィールド、それを格納するコンテナです。
(1) 表示フィールド
tviewにおける表示フィールドは、TextViewで定義します。NewTextView()メソッドでインスタンスを生成し、Setterで適当な属性を定義していきます。
今回は、下記のような共通的な表示フィールドをmyLabelとして設定しています。
func myLabel(label string) *tview.TextView {
return tview.NewTextView().SetTextColor(tcell.ColorAqua).SetText(label).SetTextAlign(tview.AlignLeft)
}
(2) 入力フィールド
入力用のフィールドは、InputFieldで定義します。InputFieldにfocusが移動した場合、文字入力はもちろんですが、矢印キーでの移動、Delキー、Insキーでの文字の削除、追加などが標準でできるようになっています。これも、属性を付加したmyEditを定義しておきます。
func myEdit(text string, rows int) *tview.InputField {
return tview.NewInputField().SetFieldWidth(rows).SetText(text).SetFieldTextColor(tcell.ColorWhite).SetFieldBackgroundColor(tcell.ColorBlack).SetFieldStyle(tcell.StyleDefault.Underline(true))
}
先にも少し触れましたが、tviewでは、複数行入力がサポートされていません。
(3) コンテナ
Primitiveを配置するコンテナには、Flexと、Gridがありますが、今回はFlexをを使用します。
採用理由は、一方向に配置する形式のほうがわかりやすいことと、Flexには、GetItemメソッドによって、内部に格納されているPrimitiveにアクセスできるからです。(なぜ、Gridにはこのメソッドがないのでしょう。どうもこのあたりに、tviewの詰めの甘さを感じます。)
Detail部の入力画面は、下記のようなコードになります。
func (self *Detail) detailBody(pages *tview.Pages, header *tview.Flex, footer *tview.Flex, common *Common) *tview.Flex {
var focusPrimitives []tview.Primitive
body := tview.NewFlex().SetDirection(tview.FlexRow)
btnCategory := myButton(common.category)
editField01 := myEdit("フィールド01", common.cols)
editField02 := myEdit("フィールド02", common.cols)
focusPrimitives = append(focusPrimitives, btnCategory)
focusPrimitives = append(focusPrimitives, editField01)
focusPrimitives = append(focusPrimitives, editField02)
editNote := tview.NewTextView().SetText("FirstLine\nNextLine")
body.AddItem(myLabel("ID"), 1, 0, false)
body.AddItem(myLabel(fmt.Sprintf("%d", common.selectedItem-1)), 1, 0, false)
body.AddItem(myLabel("Category"), 1, 0, false)
body.AddItem(btnCategory, 1, 0, false)
body.AddItem(myLabel("Field01"), 1, 0, false)
body.AddItem(editField01, 1, 0, false)
body.AddItem(myLabel("Field02"), 1, 0, false)
body.AddItem(editField02, 1, 0, false)
body.AddItem(myLabel("Note"), 1, 0, false)
body.AddItem(editNote, 0, 1, true)
body.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyTab:
mySetFocus(self.app, focusPrimitives, false)
return nil
case tcell.KeyBacktab:
if btnCategory.HasFocus() {
myFlexFocus(self.app, header, false)
return nil
} else {
mySetFocus(self.app, focusPrimitives, true)
return nil
}
case tcell.KeyDown:
mySetFocus(self.app, focusPrimitives, false)
return nil
case tcell.KeyUp:
if btnCategory.HasFocus() {
myFlexFocus(self.app, header, false)
return nil
} else {
mySetFocus(self.app, focusPrimitives, true)
return nil
}
}
return event
})
// ------------------------------
// InputCapture on Header
// ------------------------------
header.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyRight, tcell.KeyTab:
myFlexFocus(self.app, header, false)
return nil
case tcell.KeyLeft, tcell.KeyBacktab:
myFlexFocus(self.app, header, true)
return nil
case tcell.KeyDown:
mySetFocus(self.app, focusPrimitives, false)
return nil
case tcell.KeyRune:
switch event.Rune() {
case 'q', 'Q':
self.exit()
case 'r', 'R':
NewMainList().run(self.app, common)
}
}
return event
})
return body
}
Detail部から、List部に戻る部分は、下記のコードになります。
btnR := myButton("<R>").SetSelectedFunc(func() {
NewMainList().run(self.app, common)
})
詳細はソースコードを見てください。tviewでは、Focus移動を自前で書かないといけないので、Python Urwidに比べると冗長さが目立ちますね。
[3] プログラムの実行
下記のコマンドを入力します。
go run cmd/main.go
Listで選択されたデータが、Detailに渡っていることがわかります。
次回は、tviewの標準機能ではサポートされていないDialogを実装してみましょう。