본문 바로가기
2023년 이전/kotlin-TornadoFx

TornadoFx - Layouts and Menus (2)

by JeongUPark 2019. 12. 9.
반응형

[출처 - 이 글은 tornadofx-guide르 통해 공부한 내용을 정리한 글입니다. 더 정확한 내용은 여기 에서 확인 하실 수 있습니다.]

Form 안에 중첩 layouts

 

복잡한 양식 레이아웃을 만들기 위해 선택한 레이아웃 컨테이너로 fieldset들과 field들을 모두 래핑 할 수 있습니다.

import tornadofx.*

class nesting_layout_TEST : View() {
    override val root = form {
        hbox(20) {
            fieldset("Left FieldSet") {
                hbox(20) {
                    vbox {
                        field("Field l1a") { textfield() }
                        field("Field l2a") { textfield() }
                    }
                    vbox {
                        field("Field l1b") { textfield() }
                        field("Field l2b") { textfield() }
                    }
                }
            }
            fieldset("Right FieldSet") {
                hbox(20) {
                    vbox {
                        field("Field r1a") { textfield() }
                        field("Field r2a") { textfield() }
                    }
                    vbox {
                        field("Field r1b") { textfield() }
                        field("Field r2b") { textfield() }
                    }
                }
            }
        }
    }
}
class nesting_layout_App : App(nesting_layout_TEST::class){
}
fun main(){
    launch<nesting_layout_App>()
}

HBox는 hbox 빌더의 매개 변수를 사용하여 20 픽셀 간격으로 구성됩니다. 명확성을 위해 hbox (spacing = 20)로 지정할 수도 있습니다.

Input Filters 사용

TextInputControl의 일부로 filterInput은 양식 필드에서 사용자 입력을 제한하는 편리한 방법입니다. filterInput은 변경 사항을 양식 필드에 적용하고 필터와 비교합니다. 필터가 true로 평가되면 입력이 승인됩니다. 다음 예제에서 텍스트 필드의 입력은 0에서 10 사이의 정수로 제한됩니다.

import javafx.geometry.Orientation
import javafx.scene.control.TextFormatter
import tornadofx.*

class filter_TEST : View() {
    override val root = form {
        fieldset("filter Test", labelPosition = Orientation.VERTICAL) {
            field("Input", Orientation.VERTICAL) {
                textfield {
                    filterInput(FirstTenFilter)
                }
            }
        }
    }
}
val FirstTenFilter: (TextFormatter.Change) -> Boolean = { change ->
    !change.isAdded || change.controlNewText.let {
        it.isInt() && it.toInt() in 0..10
    }
}


class filter_App : App(filter_TEST::class){
}
fun main(){
    launch<filter_App>()
}

실행 해보면

이런 화면이 나오며, 위의 코드는 TextFormatter.Change 값을 받아서 입력 값이 추가가 아닍, 그리고 최종 입력 값이 Int값이고 그 값이 0과 10 사이의 값인지를 판단하여 boolean 값을 반환하는 code 입니다. 즉 입력창에 입력된 값이 0과 10 사이의 숫자인지를 filter 합니다. 조건을 만족하면 true 아니면 false를 반환하여 입력이 되지 않습니다.

저 중간의 값을을 입력할 때 보면

빈 입력창에 1을 입력하면

input change: TextInputControl.Change [ added "1" at 0; new selection (anchor, caret): [1, 1] ]
input isAdded: true
input controlNewText: 1

2를 입력하면

input change: TextInputControl.Change [ added "2" at 1; new selection (anchor, caret): [2, 2] ]
input isAdded: true
input controlNewText: 12

backspace를 누르면

input change: TextInputControl.Change [ deleted "1" at (0, 1); new selection (anchor, caret): [0, 0] ]
input isAdded: false
input controlNewText: 

이렇게 firstTenFilter의 변화 과정을 확인 할 수 있습니다.

GridPane

컨트롤의 레이아웃을 미세 관리하려면 GridPane을 사용하면 됩니다. 물론 더 많은 구성과 코드 상용구가 필요합니다. GridPane을 사용하기 전에 Form 또는 레이아웃 구성을 추상화하는 다른 레이아웃을 사용하는 것이 좋습니다.

GridPane을 사용하는 한 가지 방법은 각 row의 내용을 선언하는 것입니다. 주어진 Node에 대해 gridpaneConstraints를 호출하여 margin및 columnSpan과 같은 Node에 대한 다양한 GridPane동작을 구성 할 수 있습니다.

import tornadofx.*

class grid_TEST : View() {
    override val root =  gridpane {
        row {
            button("North") {
                useMaxWidth = true
                gridpaneConstraints {
                    marginBottom = 10.0
                    columnSpan = 2
                }
            }
        }
        row {
            button("West")
            button("East")
        }
        row {
            button("South") {
                useMaxWidth = true
                gridpaneConstraints {
                    marginTop = 10.0
                    columnSpan = 2
                }
            }
        }
    }
}
class grid_App : App(grid_TEST::class){
}
fun main(){
    launch<grid_App>()
}

 

 

이 code 행은 각각 gridpaneConstraints 안에있는 North및 South버튼의 marginBottom  및 marginTop 에 10.0이 선언되어 행 사이에 10.0의 여백이 있습니다.

또는 각 row컨트롤을 선언하지 않고 각 Node에 대해 column/row인덱스 위치를 명시 적으로 지정할 수 있습니다. 이것은 이전에 구축 한 정확한 레이아웃을 달성하지만 대신 column/row 인덱스 스펙을 사용합니다. 좀 더 장황하지만 컨트롤 위치를보다 명확하게 제어 할 수 있습니다.

import tornadofx.*

class grid_TEST : View() {
    override val root =  gridpane {
        button("North") {
            useMaxWidth = true
            gridpaneConstraints {
                columnRowIndex(0,0)
                marginBottom = 10.0
                columnSpan = 2
            }
        }
        button("West").gridpaneConstraints {
            columnRowIndex(0,2)
        }
        button("East").gridpaneConstraints {
            columnRowIndex(1,2)
        }

        button("South") {
            useMaxWidth = true
            gridpaneConstraints {
                columnRowIndex(2,2)
                marginTop = 10.0
                columnSpan = 2
            }
        }
    }
}
class grid_App : App(grid_TEST::class){
}
fun main(){
    launch<grid_App>()
}

위의 code와 결과처럼 columnRowIndex로 위치를 지정할 수 있습니다. 그리고 gridpaneConstraints에서 수정할 수 있는 다양한 속성은 다음과 같습니다.

참조 - https://edvin.gitbooks.io/tornadofx-guide/part1/7.%20Layouts%20and%20Menus.html

마지막으로  ColumnConstraints를 구성해야하는 경우 모든 자식 Node에서 gridpaneColumnConstraints를 호출하거나 GridPane 자체의 constraintsForColumn (columnIndex)을 호출 할 수 있습니다.

import tornadofx.*

class grid_TEST : View() {
    override val root =  gridpane {
        row {
            button("Left") {
                gridpaneColumnConstraints {
                    percentWidth = 25.0
                }
            }

            button("Middle")
            button("Right")
        }
        constraintsForColumn(1).percentWidth = 50.0
    }
}
class grid_App : App(grid_TEST::class){
}
fun main(){
    launch<grid_App>()
}

stack pane

statck pane은 문자 그대로 layout 들이 쌓이는 pane 입니다.

import javafx.scene.paint.Color
import tornadofx.*

class stack_TEST : View() {
    override val root = stackpane {
        button("BOTTOM") {
            useMaxHeight = true
            useMaxWidth = true
            style {
                backgroundColor += Color.AQUAMARINE
                fontSize = 40.0.px
            }
        }

        button("TOP") {
            style {
                backgroundColor += Color.WHITE
            }
        }
    }
}
class stack_App : App(stack_TEST::class){
}
fun main(){
    launch<stack_App>()
}

예를 들어 위 code 같이 사용을 한다면 bottom위에 top이 쌓이게 됩니다.

Tab Pane

TabPane은 "Tab"으로 구분 된 다른 화면으로 UI를 만듭니다. 이를 통해 해당 탭을 클릭하여 여러 화면을 빠르고 쉽게 전환 할 수 있습니다. tabpane ()을 선언 한 다음 필요한만큼 tab () 인스턴스를 선언 할 수 있습니다. 각 tab () 함수마다 컨테이너 node부터 시작하여 node의 계층 구조를 빌드합니다.

import tornadofx.*

class tab_TEST : View() {
    override val root =  tabpane {
        tab("Screen 1") {
            vbox {
                button("Button 1")
                button("Button 2")
            }
        }
        tab("Screen 2") {
            hbox {
                button("Button 3")
                button("Button 4")
            }
        }
    }
}
class tab_App : App(tab_TEST::class){
}
fun main(){
    launch<tab_App>()
}

TabPane은 화면을 분리하고 많은 수의 컨트롤을 구성하는 효과적인 도구입니다. 아래 예제는 tab () 블록 내에서 TableView와 같은 복잡한 컨트롤을 선언하기에 다소 간결하다는 것을 보여줍니다.

import tornadofx.*
import java.time.LocalDate

class tab_TEST : View() {
    override val root =  tabpane {
        tab("Screen 1") {
            vbox {
                button("Button 1")
                button("Button 2")
            }
        }
        tab("Screen 2") {
            tableview<Person> {
                items = listOf(
                    Person(1,"Samantha Stuart", LocalDate.of(1981,12,4)),
                    Person(2,"Tom Marks",LocalDate.of(2001,1,23)),
                    Person(3,"Stuart Gills",LocalDate.of(1989,5,23)),
                    Person(3,"Nicole Williams",LocalDate.of(1998,8,11))
                ).observable()

                column("ID",Person::id)
                column("Name", Person::name)
                column("Birthday", Person::birthday)
                column("Age",Person::age)
            }
        }
    }
}
class tab_App : App(tab_TEST::class){
}
fun main(){
    launch<tab_App>()
}
import java.time.LocalDate
import java.time.Period
data class Person(var id: Int, var name: String, var birthday: LocalDate, var age:Int  = Period.between(birthday,
    LocalDate.now()).years)

많은 빌더와 마찬가지로 TabPane에는 탭의 동작을 조정할 수있는 몇 가지 속성이 있습니다. 예를 들어 tabClosingPolicy를 호출하여 탭에서 "X"단추를 제거하여 닫을 수 없도록 할 수 있습니다.

import javafx.scene.control.TabPane
import tornadofx.*

class tab_TEST : View() {
    override val root =  tabpane {
        tabClosingPolicy = TabPane.TabClosingPolicy.UNAVAILABLE

        tab("Screen 1") {
            vbox {
                button("Button 1")
                button("Button 2")
            }
        }
        tab("Screen 2") {
            hbox {
                button("Button 3")
                button("Button 4")
            }
        }
    }
}
class tab_App : App(tab_TEST::class){
}
fun main(){
    launch<tab_App>()
}

마지막으로 일반 add 함수 또는 특수한 tab 함수를 사용하여 Fragments 및 View와 같은 다른 UIComponent를 탭에 포함시킬 수도 있습니다

import tornadofx.*

class tab_TEST : View() {
    override val root =  tabpane {
        tab<Screen1>()
        tab<Screen2>()
    }
}
class Screen1 : Fragment("Screen 1") {
    override val root = vbox {
        button("Button 1")
        button("Button 2")
    }
}

class Screen2 : Fragment("Screen 2") {
    override val root = vbox {
        button("Button 3")
        button("Button 4")
    }
}
class tab_App : App(tab_TEST::class){
}
fun main(){
    launch<tab_App>()
}

반응형

'2023년 이전 > kotlin-TornadoFx' 카테고리의 다른 글

TornadoFx - Layouts and Menus (3)  (0) 2019.12.10
TornadoFx - Layouts and Menus (1)  (0) 2019.12.09
TornadoFx - Type-Safe CSS(1)  (0) 2019.10.31
TornadoFX - Exception 문제  (0) 2019.10.11
TornadoFx - Data Controls(2)  (0) 2019.10.11