前面介紹了FXML的基本格式及其控制器的用法,算是打通了FXML方式的編碼流程。程序界面通常保持固定尺寸,不過有時也允許用戶拖曳窗口大小,不拖不打緊,一拖就可能壞事。像之前的登錄窗口,沒拖的時候界面如下圖所示。
現在開始慢慢把窗口拖長,拖到一半停下來,此時登錄界面如下圖所示。
乍看過去,界面上的各控件大小保持不變,且始終居中顯示,沒發現什么問題。可是繼續拖長窗口,突然之間這些控件乾坤大挪移,用戶名區域頂到了第一行,登錄按鈕跟着頂到了第二行,變化后的界面效果如下圖所示。
之所以出現控件排版錯亂的問題,是因為該界面的根節點采用了流式窗格FlowPane。所謂流式,指的是從左到右排列,倘若沒排滿一行,就跟在當前行后面;只有排滿了一行,才會另起一行繼續排。剛剛拖拉窗口的時候,拖得太長了,導致窗口的寬度能夠容納登錄類型與用戶名兩個區域,結果兩塊區域便擠到同一行了。顯然這不是期望的界面布局,至少控件要老老實實呆在自己的位置,不可越雷池一步。
若想避免流式窗格排版飄忽的問題,可以使用垂直箱子VBox替換流式窗格,垂直箱子規定它的每個直接下級都占用一行,絕對不會產生兩個直接下級擠在同一行的現象。於是修改原來的fxml文件,把根節點FlowPane換成VBox,對應的xml標簽變為以下格式:
<VBox fx:controller="com.javafx.fxml.LoginController" xmlns:fx="http://javafx.com/fxml" alignment="center"> <!-- 這是xml注釋標記。中間省略登錄窗口的各控件標簽 --> </VBox>
fxml文件修改完畢,重新運行測試程序,彈出的登錄窗口如下圖所示。
現在不管怎樣拉長窗口,各區域都留在當前行,再也不會亂跑了。然而采用VBox的界面很不協調,緣由在於VBox不支持hgap與vgap屬性,因此各控件之間沒能自動分隔開,幾乎都粘在一起了,例如:
1、登錄類型、用戶名、密碼三塊區域的左側直接頂到了窗口邊緣;
2、用戶名輸入框、密碼輸入框、登錄按鈕三個自上往下緊緊貼着,不留一絲空隙;
似此過於緊湊的界面,令人感覺頗為拘謹,還是留個適當的間隔比較好。雖然VBox不支持hgap與vgap屬性,但它另外提供了padding屬性組,允許分別指定上、下、左、右四個方向的間距。padding節點掛在哪個VBox或HBox之下,就表示哪個箱子會在內部自動留白,padding對應的xml標簽具體寫法如下所示:
<padding> <Insets top="10.0" bottom="10.0" left="10.0" right="10.0"/> </padding>
上述的padding節點例子,定義了在上、下、左、右四個方向各留出10個像素的空白間距。考慮到VBox和HBox下面可能掛着好幾個子控件,為了更好地將這些子控件跟padding區分開,fxml又給VBox和HBox引入了children子節點,凡是下級控件統統放到children節點之下,而padding節點專門放置四個方向的間隔距離。如此一來,形態完整的VBox節點結構變成了以下這般:
<VBox fx:controller="com.javafx.fxml.LoginController" xmlns:fx="http://javafx.com/fxml" alignment="center"> <children> <!-- 這是xml注釋標記。中間省略VBox的下級控件列表 --> </children> <padding> <Insets top="10.0" bottom="10.0" left="10.0" right="10.0"/> </padding> </VBox>
由上面的xml樣例可以看到,改進之后的VBox標簽變得層次分明、結構清晰,大大增強了它的可讀性。
除此之外,fxml還為VBox和HBox提供了自動伸展功能,也就是說,隨着窗口尺寸的增大,VBox和HBox的寬高也會隨之增大。其中水平方向的寬度自適應,由屬性HBox.hgrow控制,其值為ALWAYS時表示當前箱子的寬度跟隨上級變化;垂直方向的寬度自適應則由屬性VBox.vgrow控制,其值為ALWAYS時表示當前箱子的高度跟隨上級變化。尤其需要注意的是,除了VBox和HBox這兩個箱子支持自動伸展以外,只有幾個輸入框控件支持自動伸展,其中TextField與PasswordField只支持水平方向上的自動伸展,而TextArea同時支持水平與垂直兩個方向的自動伸展。
利用fxml的幾個新節點和新屬性改造原先的登錄界面,一方面,整個登錄界面在窗口四周邊緣均留白,各行之間也留出一條縫隙;另一方面,令用戶名輸入框和密碼輸入框支持水平伸展,令用戶名區域和密碼區域支持垂直伸展。這么改造一番之后的fxml文件示例如下:
<VBox fx:controller="com.javafx.fxml.LoginController" xmlns:fx="http://javafx.com/fxml" alignment="center"> <children> <HBox fx:id="hbType" prefWidth="400" prefHeight="40"> <children> <Label fx:id="labelType" prefWidth="120" prefHeight="40" text="登錄類型:" /> <fx:define> <ToggleGroup fx:id="tgType" /> </fx:define> <RadioButton fx:id="rbPassword" prefWidth="140" prefHeight="40" toggleGroup="$tgType" text="密碼登錄" selected="true" /> <RadioButton fx:id="rbVerifycode" prefWidth="140" prefHeight="40" toggleGroup="$tgType" text="驗證碼登錄" /> </children> <padding> <Insets top="0.0" bottom="10.0" left="0.0" right="0.0"/> </padding> </HBox> <HBox fx:id="hbUser" prefWidth="400" prefHeight="40" VBox.vgrow="ALWAYS"> <children> <Label fx:id="labelUser" prefWidth="120" prefHeight="40" text="用戶名:" /> <TextField fx:id="fieldUser" prefWidth="280" prefHeight="40" HBox.hgrow="ALWAYS" /> </children> <padding> <Insets top="0.0" bottom="10.0" left="0.0" right="0.0"/> </padding> </HBox> <HBox fx:id="hbPassword" prefWidth="400" prefHeight="40" VBox.vgrow="ALWAYS"> <children> <Label fx:id="labelPassword" prefWidth="120" prefHeight="40" text="密 碼:" /> <PasswordField fx:id="fieldPassword" prefWidth="280" prefHeight="40" HBox.hgrow="ALWAYS" /> </children> <padding> <Insets top="0.0" bottom="10.0" left="0.0" right="0.0"/> </padding> </HBox> <Button fx:id="btnLogin" prefWidth="400" prefHeight="40" text="登 錄" /> <Label fx:id="labelLoginResult" prefWidth="400" prefHeight="40" text="這里顯示登錄結果" /> </children> <padding> <Insets top="10.0" bottom="10.0" left="10.0" right="10.0"/> </padding> </VBox>
再次運行測試程序,彈出的登錄窗口如下圖所示,果然各級控件與周邊都隔了一小段距離。
接着在水平方向拉長窗口,拉長之后的窗口界面如下面左圖所示。回到初始尺寸,在垂直方向拉高窗口,拉高之后的如下面右圖所示。
從上面兩張效果圖可見,幾個箱子和輸入框的寬高確實跟隨窗口尺寸的變化而變化。
更多Java技術文章參見《Java開發筆記(序)章節目錄》