JavaFX七巧板游戏:布局窗格Panes

JavaFX布局之各种Panes

上次写博客还是上次。

自开始把这两东西一起学Kotlin+JavaFX的顺滑已经过去差不多5个月,联盟……都是废物。我已经编了大概3个小的应用软件,申请了两个软件著作权(一个图标库的实在无聊),最近又把专业方向的一个软件实现出来了。

我整个的学习方式都是捡到什么用什么,在GUI布局方面用得最熟的是三个:

  • BorderPane
  • VBox
  • HBox

差不多我已经觉得自己天下无敌,biu~~~【全文完】

这几天闲下来,感觉还是干点活。于是把JavaFX界面布局的核心类Pane的类图大概拉了一下。

除了我唯一学会的三板斧,还有一堆内容。那就开动吧,把源码和文档整理一下。看看下一步是不是能显得更专业一点。

开局一张图

Pane

从词源上看,Pane是窗户中的一块玻璃。在JavaFX中,Pane是一个Region(的子类),也就是代表窗口的一个区域,这个区域负责其children的定位(layout)。

所以的子类都暴露children属性(Java的getChildren方法,下面只采用Kotlin的,不再提醒对应的Java含义),并实现子节点的定位。当然,children相关的实现实际上在Parent类中。

Pane类可以直接使用,这时候,就需要自行设置其子节点的位置。

class PaneLearningApplication : Application() {
    override fun start(primaryStage: Stage) {
        val canvas = Pane().apply {
        	// background: 展示Pane的区域
            style = "-fx-background-color: black;"
            setPrefSize(200.0, 200.0)
            children.addAll(Circle(50.0, Color.BLUE).apply {
            	// 蓝色的50半径的圆形
            	// 设置layoutX, layoutY,translateX和translateY不变
                relocate(20.0, 20.0)
            }, Rectangle(100.0, 100.0, Color.RED).apply {
            	// 红色的正方形,边长100.0
            	// 设置layoutX, layoutY,translateX和translateY不变
                relocate(70.0, 70.0)
            })
        }
        primaryStage.scene = Scene(canvas, 800.0, 600.0)
        primaryStage.show()
    }
}

fun main() {
    Application.launch(PaneLearningApplication::class.java)
}

这例子的结果是:
Pane布局
两个Node的左上角位置分别为[20,20],[70,70]。此时如果缩放窗口,Pane的内容不会随之缩放,可能超出实际显示范围。

总计以下,Pane和其子类:

  • 是一个区域(Region),负责窗口一个区域的图形展示,上面的例子,专门取名叫canvas
  • 也是一个父节点(Parent),可以为其children增加节点,并且提供管理子节点的功能
  • 主要职责是布局,因此一些叫做Pane但是有自己的可视化元素的实际上是控件,并不是布局(后面专门写一篇)

为了后面的演示方便,按照kotlin的惯例,我们定义一个函数:

fun getPane(color: String) = Pane().apply {
    // background: 展示Pane的区域,参数color为背景色
    style = "-fx-background-color: $color;"
    setPrefSize(200.0, 200.0)
    children.addAll(Circle(50.0, Color.BLUE).apply {
        // 蓝色的50半径的圆形
        // 设置layoutX, layoutY,translateX和translateY不变
        relocate(20.0, 20.0)
    }, Rectangle(100.0, 100.0, Color.RED).apply {
        // 红色的正方形,边长100.0
        // 设置layoutX, layoutY,translateX和translateY不变
        relocate(70.0, 70.0)
    })
}

这个函数返回一个Pane,函数有一个参数设置背景颜色。Pane的建议大小为200×200,绘制一个圆一个正方形。

布局类的用法和展示

BorderPane

先从我最喜欢的开始。

fun borderPane() = BorderPane().apply {
    top = getPane("black")
    bottom = getPane("green")
    left = getPane("yellow")
    right = getPane("pink")
    center = getPane("cyan")
}

class PaneLearningApplication : Application() {
    override fun start(primaryStage: Stage) {
        primaryStage.scene = Scene(
            borderPane(),
            800.0, 600.0
        )
        primaryStage.show()
    }
}

其他部分与前面相同,StageScene的根节点(类型是Parent)是一个BorderPane。最终5个颜色分开的5个区域展示如下图,注意五个Pane的布局,这样基本上就可以定义一般常见应用程序的窗口布置,最上方(黑色部分)通常布置菜单和工具按钮,左边(黄色)通常布置一个树状或者列表来选择操作对象,右边(粉红色)通常显示一个信息,最下方(绿色)输出状态信息,我喜欢弄一个类似于终端输出的东西,最中间(cyan部分)就是主题操作部分。BorderPane的布局还有很友好的一点,就是默认的空间都给center,缩小放大,上下两个部分的高度,左右两个部分的宽度均不发生变化。

BorderPane示例

HBoxVBox

fun hbox() = HBox(  //HBox和VBox可以互换
    getPane("green"),
    getPane("yellow"),
    getPane("cyan"),
    getPane("black")
)

class PaneLearningApplication : Application() {
    override fun start(primaryStage: Stage) {
        primaryStage.scene = Scene(
            hbox(),
            800.0, 600.0
        )
        primaryStage.show()
    }
}

这两个布局都非常简单,HBox是一行,VBox是一列。从VBox的例子里也可以看到,作为父类的Pane基本啥都不干,children跑到外面,他也不调整人家的尺寸和位置……

HBox
VBox
基本上,会这三个就能完成大部分的布局工作。

GridPane

网格布局,这个照说是非常强大,我们来整个例子看看。

fun gridPane() = GridPane().apply {
    // 子节点, 0基列, 0基行, 宽度(列),高度(行)
    add(getPane("gray"), 0, 0, 3, 1)
    add(getPane("black"), 0, 1, 1, 1)
    add(getPane("yellow"), 1,1,1, 3)
    add(getPane("black"), 2, 1, 1, 1)
    add(getPane("green"),0, 2,1,1)
    add(getPane("green"), 2, 2,1,1)
    add(getPane("black"), 0, 3, 1, 1)
    add(getPane("black"), 2, 3, 1, 1)
    add(getPane("cyan"), 0,4,3,1)
}

GridPane
GridPane不限制子节点的相互覆盖,把后添加的放在前面。

TilePane, FlowPane

这两个布局就是贴砖块,后者有横向和纵向两个贴的顺序。

val panes = arrayOf(
    getPane("black"),
    getPane("green"),
    getPane("cyan"),
    getPane("yellow"),
    getPane("gray"),
    getPane("white"),
    getPane("pink"),
    getPane("purple")
)
fun tilePane() = TilePane(*panes) // 贴砖
fun hflowPane() = FlowPane(*panes).apply { // 横向贴砖
    orientation = Orientation.HORIZONTAL
}
fun vflowPane() = FlowPane(*panes).apply { //纵向贴砖    orientation = Orientation.VERTICAL
}

TilePane:缩放窗口时,与横向贴砖的FlowPane很类似,但是每个子节点保持相同尺寸。
TilePane
FlowPane两种布局的不同之处,把窗口缩放一下就很清楚。
在这里插入图片描述
在这里插入图片描述

AnchorPaneStackPane

这两个Pane没有什么好演示的。前者,设定子节点的锚点,后者把子节点一层一层堆放。

我们把前面的panes映射一下,设置新顶部和左侧的锚点位移。

fun anchorPane() = AnchorPane().apply {
    val mps = panes.mapIndexed{i, p ->
        setTopAnchor(p, i * 50.0)
        setLeftAnchor(p, i * 50.0)
        p
    }.toTypedArray()
    children.addAll(*mps)
}

Anchor

这里,我们为了区分,把每个Pane的最大尺寸做映射。从图中很容易看到堆叠的效果。

fun stackPane() = StackPane().apply {
    val n = panes.size
    val mps = panes.mapIndexed{i, p ->
        (p.apply {
            val k = n + 1 - i
            val d = prefWidth * pow(1.12, k.toDouble())
            setMaxSize(d, d)
        })
    }.toTypedArray()
    children.addAll(*mps)
}

StackPane

结论

  • Pane从含义上看是布局的窗格;
  • Java FX已经定义的布局窗格可以构成复杂的GUI;

还有一个控件Control实际上也有布局的作用,而且过分的是,还有一些名称里包含了Pane这个,比如TitledPane,ScrollPane这些的。实际上这里的语义并不完整。

此外,Pane还有一个子类TextFlow,那个是为了构成文本流,在GUI编程中并不作为布局来使用。

posted @ 2022-07-03 12:09  大福是小强  阅读(52)  评论(0编辑  收藏  举报  来源