首页 > 代码库 > JavaFX 初学入门(一):FXML嵌套与原始控件继承

JavaFX 初学入门(一):FXML嵌套与原始控件继承

说明

  之前由于做一个小项目需要用swing,结果swing把我折腾的够呛。后来得知有javaFX这个类似于C#中WPF形式的利用XML来写界面的框架之后,马上就转到javaFX上了。找过一些资料但是帮助都不大,最后还是选择直接看官方demo。(我之前是做过android app,有些东西其实都是差不多的。)

  下面选取官方demo中的一个 UnlockCustom 。

  这个demo中涉及到 继承原始控件(自定义),FXML 布局嵌套,以及一些控件动画等。

一、javaFX的入口函数

整个javaFX demo 的入口函数 是 unlock.class  中的main函数。

在这个demo中 main函数中只有一条语句。Application.launch 的作用与swing 中 EventQueue.invokeLater的作用相似,其结果是 启动一个UI线程 并在线程中回调 被加载的类中的start方法。

 1 package unlock; 2  3 import java.util.logging.Level; 4 import java.util.logging.Logger; 5 import javafx.application.Application; 6 import javafx.fxml.FXMLLoader; 7 import javafx.scene.Scene; 8 import javafx.scene.layout.Pane; 9 import javafx.stage.Stage;10 11 /**12  * Main class for the Unlock Custom sample.13  * This is boilerplate code:14  * Loads ‘Unlock.fxml‘, adds the root node to a Scene, and set the scene15  * to the application primary stage.16  * <br>In the Unlock Custom demo the key pad is defined as a custom type named Keypad.17  * From within Unlock.fxml we refer to the key pad by its Java class name, Keypad.18  */19 public class Unlock extends Application {20 21     /**22      * @param args the command line arguments23      */24     public static void main(String[] args) {25         Application.launch(Unlock.class);26     }27 28     @Override29     public void start(Stage primaryStage) {30         try {31             String filename = "Unlock.fxml";32             Pane page = (Pane) FXMLLoader.load(Unlock.class.getResource(filename));33             Scene scene = new Scene(page);34             primaryStage.setScene(scene);35             primaryStage.setTitle("Unlock Custom Sample");36             primaryStage.show();37         } catch (Exception ex) {38             Logger.getLogger(Unlock.class.getName()).log(Level.SEVERE, null, ex);39         }40     }41 }
unlock.class

在上面的代码中,start方法 加载了一个fxml文件并对其进行处理。被加载的fxml文件经过一系列处理 成为了Pane的引用,随后创建了Scene对象并将pane设置到Scene中,接着将Scene设置到Stage对象中。pane、Scene、Stage是3个窗口容器 与 Swing 中的 Jpanel和JFrame的关系相似。

pane、Scene与Stage具体关系如下图

Description of Figure 3-1 follows

Stage 与 Swing 中JFrame 一样是顶层容器,构建了整个程序的主题窗口。

Scene 与Swing 中JPanel 一样是中层容器,用于摆放基本控件或其他中下层容器

Pane 与Swing中 Pane 是一样的,都是控件的载体

二、FXML文件说明

FXML文件其实就是一个布局文件,保存控件相关信息,文件结构基本与XML类似。我们可以通过使用Oralce提供的 JavaFX Scene Builder 工具来进行可视化操作,大大的提高了界面设计的效率。

下面是 unlock.class 中加载的FXML 文件

 1 <?xml version="1.0" encoding="UTF-8"?> 2  3 <?import java.lang.*?> 4 <?import java.net.*?> 5 <?import javafx.scene.control.*?> 6 <?import javafx.scene.image.*?> 7 <?import javafx.scene.layout.*?> 8 <?import javafx.scene.shape.*?> 9 <?import javafx.scene.text.*?>10 <?import unlock.*?>11 <?scenebuilder-classpath-element ../../dist/UnlockCustom.jar?>12 13 14 <AnchorPane id="AnchorPane" fx:id="root" focusTraversable="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="unlock.UnlockController">15   <children>16     <Text layoutX="76.0" layoutY="230.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Hello World">17       <font>18         <Font size="80.0" />19       </font>20     </Text>21     <Rectangle fx:id="okleft" arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="400.0" stroke="BLACK" strokeType="INSIDE" styleClass="unlock-leftright" width="300.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />22     <Rectangle fx:id="okright" arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="400.0" layoutX="300.0" stroke="BLACK" strokeType="INSIDE" styleClass="unlock-leftright" width="300.0" AnchorPane.topAnchor="0.0" />23     <Rectangle fx:id="error" arcHeight="5.0" arcWidth="5.0" fill="#992500" height="400.0" opacity="0.0" stroke="BLACK" strokeType="INSIDE" width="600.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />24     <Keypad fx:id="pad" layoutX="187.0" layoutY="68.0" styleClass="keypad" />25     <Rectangle fx:id="unlocktop" arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" styleClass="unlock-top" visible="true" width="600.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />26     <Rectangle fx:id="unlockbottom" arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" layoutY="200.0" stroke="BLACK" strokeType="INSIDE" styleClass="unlock-bottom" visible="true" width="600.0" AnchorPane.leftAnchor="0.0" />27     <Button fx:id="lock" layoutX="163.0" layoutY="157.0" mnemonicParsing="false" onAction="#unlockPressed" styleClass="unlock-button" text="Click to Unlock" visible="true">28       <graphic>29         <ImageView id="lock" pickOnBounds="true">30           <image>31             <Image preserveRatio="true" smooth="true" url="@lock.png" />32           </image>33         </ImageView>34       </graphic>35     </Button>36   </children>37   <stylesheets>38     <URL value="@Unlock.css" />39   </stylesheets>40 </AnchorPane>
unlock.fxml

除开xml文件声明的语句,文件前几行是导入控件相应的包,这个操作基本与java的格式相匹配。后面的都是在定义控件并设置相应属性。完成的效果如下:

值得注意的是 几乎很多地方都有 fx:xxxxxx的属性标记,这些是javaFX中已经规定的特殊标记属性。具体内容请查看官方文档。

就几个比较重要的 简单的说一下,例如在 控件定义的 第一行中有如下的代码:

<AnchorPane id="AnchorPane" fx:id="root" focusTraversable="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="unlock.UnlockController">

其中 id="AnchorPane"  id 是定义文档中节点的id号,需要与 fx:id区分开。节点id在一个scene中是唯一的,它类似于html中的id属性。

  fx:id="root"  fx:id 是定义控件的id号,与android开发中 布局文件里的控件id 一样。可以在.java文件中声明并引用 这个 id 对应的控件,具体方法后面会讲到。

  fx:controller="unlock.UnlockController"  fx:controller 是定义该FXML所对应的控制器类,在这个控制器类中可以处理在fxml中定义的事件响应方法 ,例如 button 点击事件。
下面是个 button控件的定义,除了 fx:id 和其他基本属性的定义以外 还有一个 
onAction="#keyPressed" 属性的定义。它就是定了一个事件响应方法名称,值得注意的是方法名前面必须加上# 标识符。

<Button fx:id="del" mnemonicParsing="false" onAction="#keyPressed" text="Del" GridPane.columnIndex="0" GridPane.rowIndex="3" />

 三、控制器类

下面再让我们看到前面说的控制器类 UnlockController.class

 1 package unlock; 2  3 import java.net.URL; 4 import java.util.ResourceBundle; 5 import javafx.event.ActionEvent; 6 import javafx.fxml.FXML; 7 import javafx.fxml.Initializable; 8 import javafx.scene.control.Button; 9 import javafx.scene.control.PasswordField;10 import javafx.util.Callback;11 12 /**13  * The controller for the custom Keypad component - see ‘Keypad.fxml‘ 14  * and ‘Keypad.java‘.15  */16 public final class KeypadController implements Initializable {17             18     @FXML //  fx:id="del"19     private Button del; // Value injected by FXMLLoader20 21     @FXML //  fx:id="ok"22     private Button ok; // Value injected by FXMLLoader23 24     @FXML //  fx:id="display"25     private PasswordField display; // Value injected by FXMLLoader26 27     private Callback<String, Boolean> validateCallback = null;28     29     // Handler for Button[Button[id=null, styleClass=button]] onAction30     // Handler for Button[fx:id="del"] onAction31     // Handler for Button[fx:id="ok"] onAction32     public void keyPressed(ActionEvent event) {33         // handle the event here34         if (event.getTarget() instanceof Button) {35             if (event.getTarget() == del && !display.getText().isEmpty()) {                36                 delete();37             } else if (event.getTarget() == ok) {38                 validateCallback.call(display.getText());39                 display.setText("");40             } else if (event.getTarget() != del) {41                 append(((Button)event.getTarget()).getText());42             }43             event.consume();44         }45     }46     47     private void delete() {48         display.setText(display.getText().substring(0, display.getText().length() -1));49     }50     51     private void append(String s) {52         String text = display.getText();53         if (text == null) text = "";54         display.setText(text+s);55     }56 57     @Override // This method is called by the FXMLLoader when initialization is complete58     public void initialize(URL fxmlFileLocation, ResourceBundle resources) {59         assert del == null : "fx:id=\"del\" was not injected: check your FXML file ‘Keypad.fxml‘.";60         assert ok != null : "fx:id=\"ok\" was not injected: check your FXML file ‘Keypad.fxml‘.";61         assert display != null : "fx:id=\"password\" was not injected: check your FXML file ‘Keypad.fxml‘.";62     }63     64     void setValidateCallback(Callback<String,Boolean> validateCB) {65         validateCallback = validateCB;66     }67     68 }
KeypadController

在这个类中,我们可知找到  keyPressed 方法,它就是前面 button控件中定义的事件方法。他的处理与android 中 布局文件里 android:onclick 属性类似。剩下的与swing中对事件处理的描述相似。

让我们向前看几行,找到

1     @FXML //  fx:id="del"2     private Button del; // Value injected by FXMLLoader3 4     @FXML //  fx:id="ok"5     private Button ok; // Value injected by FXMLLoader6 7     @FXML //  fx:id="display"8     private PasswordField display; // Value injected by FXMLLoader

这里很明确的备注了 几行代码的意图。就是前面我们提到的 fx:id属性 在.java文件中的定义与引用。 想要在java文件中直接引用FXML里定义的控件必须使用@FXML标记来描述待定义的变量,并且变量名要与fxml文件中的fx:id 的值相同。如此一来就可以直接对控件进行操作了。

四、继承原始控件与FXML嵌套处理

让我们在回到 unlock.fxml文件里找到 下面这一行代码

<Keypad fx:id="pad" layoutX="187.0" layoutY="68.0" styleClass="keypad" />

这个是一个自定义控件,keypad 是 Vbox的子类。

 1 public class Keypad extends VBox { 2      3     private final KeypadController controller; 4     public Keypad() { 5         controller = load(); 6     } 7      8     private KeypadController load() { 9         10         final FXMLLoader loader = new FXMLLoader();11         12         // fx:root is this node.13         loader.setRoot(this);14         15         // The FXMLLoader should use the class loader that loaded16         // this class (Keypad).17         loader.setClassLoader(this.getClass().getClassLoader());18         19         // Keypad.fxml contains the configuration for ‘this‘20         loader.setLocation(this.getClass().getResource("Keypad.fxml"));21         22         try {23             final Object root = loader.load();24             assert root == this;25         } catch (IOException ex) {26             throw new IllegalStateException(ex);27         }28         29         final KeypadController keypadController = loader.getController();30         assert keypadController != null;31         return keypadController;32     }33     34     void setValidateCallback(Callback<String,Boolean> callback) {35         controller.setValidateCallback(callback);36     }37 }
keypad.class

而这个子类在代码中并未声明控件而是加载了一个FXML文件,通过FXML来加载控件。也是这样让我们实现了一个类似于FXML嵌套的处理,所谓FXML的嵌套 实际上就是通过加载自定义控件,在代码中load FXML来完成的。在官方教程中并没有直接说明FXML可以嵌套处理也没有办法,不知道该如何处理。

 

剩下代码的可以通过官方DEMO自行查看与学习。