Go言語(golang)でTUIアプリを作ろう ( その6 Listの画面コントロール )
ページング処理を追加しよう。
今回は、前回のプログラムにページコントロールを入れて、全件表示ができるよう変更していきます。新たなWidgetの追加はありません。処理は極めて簡単で、ページング対応ロジックの追加が中心になります。
画面イメージ
まずは、最終画面イメージを示します。
Header部に、ページングボタンを2つ配置しています。このボタンがクリックされることで、ページング表示を行っていくという形となるわけです。
プログラムの分割
その前にプログラムを整理しましょう。
今までは、1つのプログラムに全ての処理を記述していましたが、だいぶ長くなってきたので、下記の2ファイルに分割し、ページングに対応した処理を追加していきます。
(1) Widget関係(mywidget.go)
(2) メイン部(list.go)
それでは、個々のスクリプトを見ていきましょう。
(1) mywidget
tviewの基本Widgetを拡張している部分を抜き出します。
package main
import (
"fmt"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
var cols, rows = GetScreenSize()
func GetScreenSize() (int, int) {
s, _ := tcell.NewScreen()
s.Init()
cols, rows := s.Size()
s.Fini()
return cols, rows
}
var listData = createListData()
func createListData() []string {
var listData []string
var s string
for i := 0; i < 100; i++ {
s = "テストデータ" + fmt.Sprintf("%d", i)
for j := len(s); j < cols; j++ {
s = s + " "
}
listData = append(listData, s)
}
return listData
}
func getListData(from int) []string {
return listData[from-1 : from+rows-2]
}
// -----------------------------------------------
func mySetFocus(app *tview.Application, elements []tview.Primitive, reverse bool) {
for i, el := range elements {
if !el.HasFocus() {
continue
}
if reverse {
i = i - 1
if i < 0 {
i = len(elements) - 1
}
} else {
i = i + 1
i = i % len(elements)
}
app.SetFocus(elements[i])
return
}
app.SetFocus(elements[0])
}
func myButton(label string) *tview.Button {
button := tview.NewButton(label)
button.Box = tview.NewBox().SetBackgroundColor(tcell.ColorBlack)
button.SetLabelColor(tcell.ColorYellow).SetLabelColorActivated(tcell.ColorBlack).SetBackgroundColorActivated(tcell.ColorYellow)
return button
}
(2) list
一覧処理なので、Listという名称に変更しました。
1) ページングボタンの追加
headerにページングボタンを追加します。<N>ボタンで次ページ、<P>ボタンで前ページ画面に移行することとします。
2) ボタンの処理
表示開始レコードをページングボタンの動作に合わせて増減することで画面ページングを行います。今回は、1ページめの<P>ボタンを、最終ページの<N>ボタン表示の抑制には手を抜いて、そのままにしてあります。
下記にコードを示します。
package main
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"strconv"
)
func setList(list *tview.List, from int) *tview.List {
list.Clear()
item := getListData(from)
for i := 0; i < rows-2; i++ {
list.AddItem(item[i], "", 0, nil)
}
return list
}
func nextPage(list *tview.List, from int) (*tview.List, int) {
if from+(rows-2) < len(listData) {
from = from + (rows - 2)
list = setList(list, from)
}
return list, from
}
func priorPage(list *tview.List, from int) (*tview.List, int) {
if from > 1 {
from = from - (rows - 2)
list = setList(list, from)
}
return list, from
}
func doformat(app *tview.Application, from int) tview.Primitive {
list := tview.NewList().ShowSecondaryText(false).SetSelectedTextColor(tcell.ColorWhite).SetSelectedBackgroundColor(tcell.ColorAqua).SetSelectedFocusOnly(true)
list = setList(list, from)
pages := tview.NewPages()
footer := tview.NewTextView().SetText("これはフッター")
header := tview.NewFlex()
btnN := myButton("<N>").SetSelectedFunc(func() {
list, from = nextPage(list, from)
})
btnP := myButton("<P>").SetSelectedFunc(func() {
list, from = priorPage(list, from)
})
btnQ := myButton("<Q>").SetSelectedFunc(func() {
app.Stop()
})
buttons := []tview.Primitive{
btnN,
btnP,
btnQ,
}
header.AddItem(btnN, 0, 1, true)
header.AddItem(btnP, 0, 1, true)
header.AddItem(btnQ, 0, 1, true)
body := tview.NewFlex().SetDirection(tview.FlexRow).AddItem(list, 0, 1, true)
main := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(header, 1, 0, true).
AddItem(body, 0, 1, true).
AddItem(footer, 1, 0, false)
pages.AddPage("main", main, true, true)
header.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyRight:
mySetFocus(app, buttons, false)
case tcell.KeyLeft:
mySetFocus(app, buttons, true)
case tcell.KeyDown:
app.SetFocus(list)
return nil
}
return event
})
body.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyUp:
if list.GetCurrentItem() == 0 {
mySetFocus(app, buttons, true)
return nil
}
}
return event
})
pages.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyRune:
switch event.Rune() {
case 'q':
app.Stop()
case 'n':
list, from = nextPage(list, from)
case 'p':
list, from = priorPage(list, from)
}
}
return event
})
list.SetSelectedFunc(func(index int, s string, secondary string, code rune) {
footer.SetText("pos:" + strconv.Itoa(index) + " data:" + s)
})
return pages
}
func main() {
app := tview.NewApplication()
pages := doformat(app, 1)
if err := app.SetRoot(pages, true).EnableMouse(true).Run(); err != nil {
panic(err)
}
}
動作確認
では、実際に動かしてみましょう。mywidget.go、list.goの2ファイルを同一ディレクトリに入れておきます。
今回以降、モジュール名はlistdbgに統一します。
go mod init listdbg
go mod tidy
環境を設定したら、実行します。
go run list.go mywidget.go
先に示した画面が表示されたでしょうか。
<N>ボタン、<P>ボタンでページング処理が行われます。また、データをEnterキーで選択すると、レコードの内容がfotter部に表示されることも確認してください。
これで、tviewのListを使用した一覧リスト画面の雛形は完成です。
次回以降、この画面から選択されたレコードの詳細(Detail)を表示するプログラムを作成していくことになりますが、その前に「プログラム全体の構造を見直したい」と考えています。