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

TornadoFx - Layouts and Menus (3)

by JeongUPark 2019. 12. 10.
반응형

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

MenuBar, Menu, and MenuItem

MenuBar에 menu를 추가하고 각 Menu에 item을 추가해보겠습니다.

import tornadofx.*

class menubar_TEST : View() {
    override val root =  borderpane {
        top {
            menubar {
                menu("File") {
                    menu("Connect") {
                        item("Facebook")
                        item("Twitter")
                    }
                    item("Save")
                    item("Quit")
                }
                menu("Edit") {
                    item("Copy")
                    item("Paste")
                }
            }
        }
    }
}
class menubar_App : App(menubar_TEST::class){
}
fun main(){
    launch<menubar_App>()
}

top 영역에 MenuBar를 추가하고 그 Menubar에 menu들과 그 menu의 item을 추가 하면 위와 같은 결과를 얻을 수 있습니다.

그리고 선택적으로 키보드 단축키, 그래픽 및 각 항목 itme()에 대한 action 함수 매개 변수를 제공하여 동작을 선택할 때 동작을 지정할 수 있습니다

import tornadofx.*

class menubar_TEST : View() {
    override val root =  borderpane {
        top {
            menubar {
                menu("File") {
                    menu("Connect") {
                        item("", graphic = imageview("fb_icon.png")).action { println("Connecting Facebook!") }
                        item("", graphic = imageview("tw_icon.png")).action { println("Connecting Twitter!") }
                    }
                    item("Save","Shortcut+S").action {
                        println("Saving!")
                    }
                    item("Quit","Shortcut+Q").action {
                        println("Quitting!")
                    }
                }
                menu("Edit") {
                    item("Copy","Shortcut+C").action {
                        println("Copying!")
                    }
                    item("Paste","Shortcut+V").action {
                        println("Pasting!")
                    }
                }
            }
        }
    }
}
class menubar_App : App(menubar_TEST::class){
}
fun main(){
    launch<menubar_App>()
}

(이미지를 인터넷에 구해서 딱히 사이즈를 조절안해서 이미지가 큽니다.)

아무튼 facebook icon이나 twitter icon을 누르면 console에 

Connecting Facebook!
Connecting Twitter!

 이런 액션이 발동 합니다.

 

separators

메뉴에서 두 항목 사이에 separator ()를 선언하여 구분선을 만들 수 있습니다. 이는 메뉴에서 명령을 그룹화하고 명확하게 분리하는 데 도움이됩니다.

import tornadofx.*

class menubar_TEST : View() {
    override val root =  borderpane {
        top {
            menubar {
                menu("File") {
                    menu("File") {
                        menu("Connect") {
                            item("Facebook")
                            item("Twitter")
                        }
                        separator()
                        item("Save","Shortcut+S").action {
                            println("Saving!")
                        }
                        item("Quit","Shortcut+Q").action {
                            println("Quitting!")
                        }
                    }
                }
                menu("Edit") {
                    item("Copy","Shortcut+C").action {
                        println("Copying!")
                    }
                    item("Paste","Shortcut+V").action {
                        println("Pasting!")
                    }
                }
            }
        }
    }
}
class menubar_App : App(menubar_TEST::class){
}
fun main(){
    launch<menubar_App>()
}

 

위의 결과 처럼 separator()를 추가하여 Connect와 다음 항목들 사이에 구분 선이 그어진 것을 확인 할 수 있습니다.

ContextMenu

JavaFX의 대부분의 컨트롤에는 ContextMenu 인스턴스를 할당 할 수있는 contextMenu 속성이 있습니다. 컨트롤을 마우스 오른쪽 버튼으로 클릭하면 팝업되는 menu입니다. ContextMenu에는 MenuBar와 마찬가지로 Menu 및 MenuItem 인스턴스를 추가하는 기능이 있습니다. 다음은 예제입니다.

import tornadofx.*
import java.time.LocalDate

private val persons = 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(4, "Nicole Williams", LocalDate.of(1998, 8, 11)) ).asObservable()
class contextmenu_TEST : View() {
    override val root = tableview(persons) {
        column("ID", Person::id)
        column("Name", Person::name)
        column("Birthday", Person::birthday)
        column("Age", Person::age)

        contextmenu {
            item("Send Email").action {
                selectedItem?.apply { println("Sending Email to $name") }
            }
            item("Change Status").action {
                selectedItem?.apply { println("Changing Status for $name") }
            }
        }
    }
}
class contextmenu_App : App(contextmenu_TEST::class){
}
fun main(){
    launch<contextmenu_App>()
}

이렇게 tableview에 contextmenu를 붙이면 항목에서 마우스 우측 키를 누를 경우 셋팅한 menu들이 나타납니다. (사용 가능한 MenuItem의 RadioMenuItem 및 CheckMenuItem 변형도 있습니다.)

menuitme 빌더는 메뉴가 op 블록 파라메터로 선택된 경우 수행 할 액션을 수행합니다. 하지만, 이것은 다른 빌더와 충돌하며 op 블록은 빌더가 작성한 요소에서 작동합니다. 따라서 item 빌더가 대안으로 도입되어 항목 자체를 조작 할 수 있으므로 setOnAction을 호출하여 조치를 지정해야합니다.메뉴 항목 빌더는 항목 빌더보다 더 간결한 방식으로 일반적인 경우를 해결하므로 더 이상 사용되지 않습니다. (사실 이부분은 해석을 하고 이해해보려 했지만 잘 이해되지 않아 제가 이해한 부분을 따로 적어보겠습니다.  menuitem으로 항목을 만들어서 사용할 때 액션이 발생할 경우 충돌이 날 수 있으므로, item을 사용하여 setOnAction을 통하여 액션을 수행하도록 만들어야한다고 이해했습니다.)

 

ListMenu

TornadoFx의 ListMenu는 HTML5의 menu와 유사하게 동작합니다.

import javafx.scene.layout.Priority
import tornadofx.*

class list_TEST : View() {
    override val root = listmenu(theme = "blue") {
        item(text = "Contacts") {
            // Marks this item as active.
            activeItem = this
            whenSelected { /* Do some action */ }
        }
        item(text = "Projects")
        item(text = "Settings")
    }
}


class list_App : App(list_TEST::class){
}

fun main(){
    launch<list_App>()
}

ListMenu에는 많은 속성이 있습니다. 그 속성은 여기에서 ListMenu를 검색해서 확인하시면 될 것 같습니다.

Item

아이템 빌더는 매우 편리한 방법으로 ListMenu에 대한 아이템을 생성 할 수 있습니다. 다음과 같은 구문이 지원됩니다.

item("SomeText", graphic = SomeNode, tag = SomeObject) {
    // Marks this item as active.
    activeItem = this

    // Do some action when selected
    whenSelected { /* Action */ }
}

Filling the parent container

useMaxWidth 속성을 사용하여 부모 컨테이너를 가로로 채울 수 있습니다. useMaxHeight 속성은 부모 컨테이너를 세로로 채 웁니다. 이러한 속성은 실제로 모든 노드에 적용되지만 특히 ListMenu에 유용합니다.

SqueezeBox

JavaFX에는 일련의 TilePanes를 그룹화하여 Accordion컨트롤을 만들 수있는 Accordion 컨트롤이 있습니다.JavaFX Accordion에서는 한 번에 하나의 접혀있는 Accordion만열 수 있습니다. 이 문제를 해결하기 위해 TornadoFX는 SqueezeBox 구성 요소를 제공하며, 이는 Accordion과 매우 유사하지만 향상된 동작을 제공합니다. (Accordion menu는 접혔다 펼쳐지는 메뉴를 말합니다.)

import tornadofx.*

class squeeze_TEST : View() {
    override val root = squeezebox {
        fold("Customer Editor", expanded = true) {
            form {
                fieldset("Customer Details") {
                    field("Name") { textfield() }
                    field("Password") { textfield() }
                }
            }
        }
        fold("Some other editor", expanded = true) {
            stackpane {
                label("Nothing here")
            }
        }
    }
}


class squeeze_App : App(squeeze_TEST::class){
}

fun main(){
    launch<squeeze_App>()
}

위 코드를 실행 시키면 

Caused by: java.lang.NoClassDefFoundError: com/sun/javafx/scene/control/skin/BehaviorSkinBase

라는 error가 발생합니다. 그 이유는 tornadoFx가 아직 Java 9 이상을 완벽 호완하지 못해서 발생하는 문제로 보입니다. 그래서 java 8으로 project setting을 변경 하고 실행했습니다. (변경 방법은 여기서 확인하시면 됩니다.)

multiselect = false를 빌더 생성자에 전달하면 하나의 항목만  접기/확장 하도록 SqueezeBox에 지시 할 수 있습니다. 선택적으로 접기에 대한 제목 창의 오른쪽 모서리에있는 십자 표시를 클릭하여 접기를 닫을 수 있습니다. 접기 도구에 closeable = true를 전달하여 접기마다 닫기 버튼을 활성화 할 수 있습니다.

import tornadofx.*

class squeeze_TEST : View() {
    override val root = squeezebox {
        multiselect = false
        fold("Customer Editor", expanded = true, closeable = true) {
            form {
                fieldset("Customer Details") {
                    field("Name") { textfield() }
                    field("Password") { textfield() }
                }
            }
        }
        fold("Some other editor", closeable = true) {
            stackpane {
                label("Nothing here")
            }
        }
    }
}


class squeeze_App : App(squeeze_TEST::class){
}

fun main(){
    launch<squeeze_App>()
}

SqueezeBox와 Accordion의 또 다른 중요한 차이점은 오버플로 공간을 분산시키는 방식입니다. Accordion은 세로로 확장되어 부모 컨테이너를 채우고 현재 열려있는 것 아래로 접힌 부분을 맨 아래까지 밀어 넣습니다. 부모 컨테이너가 매우 큰 경우 부 자연스럽게 보입니다. 'squeezebox'는 아코디언과 비슷한 모양을 얻기 위해 fillHeight = true를 추가하여야합니다.

TitlePane의 스타일을 지정하는 것처럼 SqueezeBox의 스타일을 지정할 수 있습니다. 닫기 버튼에는 닫기 버튼이라는 css 클래스가 있고 컨테이너에는 squeeze-box라는 CSS 클래스가 있습니다.

Drawer navigation

Drawer는 탭 창과 매우 유사한 탐색 구성 요소이지만 각 드로어 항목을 부모 컨테이너의 양쪽에 세로 또는 가로로 배치 된 단추 모음으로 구성합니다.

item을 선택하면, 그 아이템에 대한 콘텐츠가 부모의 세로 또는 가로에 도킹되어 있는지 여부에 따라 컨트롤의 높이 또는 너비와 콘텐츠의 기본 너비 또는 높이에 걸쳐있는 콘텐츠 영역의 버튼의 위나 아래 또는 옆에 나타납니다. multiselect  모드에서는 여러 드로어 항목을 동시에 열고 이들 사이의 공간을 공유 할 수도 있습니다. 해당 버튼 순서대로 항상 열립니다. 다음의 예제 입니다.

import tornadofx.*
import java.time.LocalDate
class drawer_TEST : View() {
    override val root = drawer {
        item("Screencasts", expanded = true) {
            webview {
                prefWidth = 470.0
                engine.load("https://edvin.gitbooks.io/tornadofx-guide/part1/7.%20Layouts%20and%20Menus.html#")
            }
        }
        item("Links") {
            listview(links) {
                cellFormat { link ->
                    graphic = hyperlink(link.name) {
                        setOnAction {
                            hostServices.showDocument(link.uri)
                        }
                    }
                }
            }
        }
        item("People") {
            tableview(people) {
                column("Name", Person_::name)
                column("Nick", Person_::nick)
            }
        }
    }
}


class drawer_App : App(drawer_TEST::class){
}
class Link(var name: String, var uri: String)
data class Person_(var name: String, var nick: String)
private val links = listOf(
    Link("naver","https://www.naver.com/"),
    Link("daum","https://www.daum.net/"),
    Link("youbute","https://www.youtube.com/"),
    Link("twitch","https://www.twitch.tv/")).asObservable()
private val people = listOf(
    Person_("jeongupark","jupark"),
    Person_("AAA","aaa"),
    Person_("BBB","bbb"),
    Person_("CCC","ccc")).asObservable()
fun main(){
    launch<drawer_App>()
}

위의 코드는 3개의 탭을 가지고 1 번째 탭은 Webview로 TornadoFx layout doc 부분입니다(현재 제가 공부하고 있는 부분입니다.) 두 번째 탭은 링크가 걸려있고 각 text를 클릭하면 브라우저에서 그 주소를 오픈 합니다. 3번째는 tableView 입니다.

그리고 drawer에 다음과 같은 옵션을 주면

import javafx.geometry.Side
// ...
drawer(side = Side.RIGHT, multiselect = true)
// ...

이 처럼 오른쪽에 탭을 둘구 있고 여러개를 열수도 있습니다. 그리고 showHeader를 통하여 각 탭의 헤더에 그 탭의 이름을 보여줄 수 있습니다. 하지만 따로 설정 없이 나타나는것을 보면 showHeader=true가 default 값인 것 같습니다.

Drawer가 무언가 옆에 추가되면, Drawer의 내용 영역이 옆에 노드를 대체해야하는지(기본값) 또는 그 위에 떠야하는지 선택할 수 있습니다. floatingContent 속성은 기본적으로 false이므로 드로어가 옆에있는 내용을 대체합니다.

Drawer의 maxContentSize 및 fixedContentSize 속성을 사용하여 내용 영역의 크기를 추가로 제어 할 수 있습니다. dockingSide에 따라 이러한 속성은 내용 영역의 너비 또는 높이를 제한합니다.

Workspace 기능은 Drawer 컨트롤을 기본적으로 지원합니다. Workspace 의 leftDrawer, rightDrawer 및 bottomDrawer 속성을 사용하면 서랍 항목을 항목에 고정 할 수 있습니다. 이에 대한 자세한 내용은 Workspace 장을 참조하시면 될 것 같습니다..

반응형

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

TornadoFx - Layouts and Menus (2)  (0) 2019.12.09
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