使用FXMLLoader加载FXML文件,调用load()方法,得到FXML根节点对象,然后就可以像使用JavaFX本身的控件一样使用了。
FXMLLoader loader = new FXMLLoader(getClass().getResource("LoginPaneFx.fxml")); // Set root if FXML root node is <fx:root/> loader.setRoot(this); // Set controller if FXML doesn't include a controller. loader.setController(this); GridPane loginPane = loader.<GridPane>load();
1. 定义FXML文件
1.1 定义根节点类型
JavaFX 2之前,根节点类型是以XML tag方式定义的,使用FXMLLoader时,不用也不能调用setRoot()方法。
<GridPane xmlns:fx="http://javafx.com/fxml/1"> ...... </GridPane>
JavaFX 2版本之后,FXML提供<fx:root/>写法,此时要求Controller必须继承自FXML节点对象,使用FXMLLoader加载时,必须调用setRoot()方法。
<fx:root type="GridPane" xmlns:fx="http://javafx.com/fxml/1"> ...... </fx:root>
SceneBuilder左下角Controller面板,可以勾选是否使用<fx:root/>方式定义FXML。
1.2 FXML中指定Controller
在Controller面板里面,可以给FXML指定Controller(包名+类名)。如果FXML没有指定,就需要在code中调用FXMLLoader setController()方法,指定一个Controller。
SceneBuilder中可以查看FXML对应Controller大致框架,copy到code中就可以用了。Menu->View->”Show Sample Controller Skeelton”。
1.3 FXML最佳实践
根节点使用<fx:root/>方式定义,可以将Controller类型和FXML根节点类型保持一致,方便使用。
不要在FXML中指定Controller,通常情况下一个FXML可以对应多个Controller,为了灵活性,我们应当在FXMLLoader中指定Controller。
2. FXML和Controller耦合在一起
2.1 FXML sample
根节点类型是GridPane,使用<fx:root/>定义,FXML里没有指定Controller。
<fx:root type="GridPane" xmlns:fx="http://javafx.com/fxml/1"> ...... </fx:root>
Controller继承GridPane,因为和FXML根节点同类型,其实就是FXML控件本身。
2.2 耦合的Controller
public class LoginPaneFx extends GridPane { @FXML private TextField nameTxt; public LoginPaneFx() { try { FXMLLoader loader = new FXMLLoader( getClass().getResource("LoginPaneFx.fxml")); loader.setRoot(this); loader.setController(this); loader.load(); } catch (Exception e) { e.printStackTrace(); } } }
此时Controller LoginPaneFx就和JavaFX其它控件一样使用。
3. FXML和Controller完全独立
3.1 FXML sample
根节点是GridPane,不使用<fx:root/>,FXML里没有指定Controller。
<GridPane xmlns:fx="http://javafx.com/fxml/1"> ...... </GridPane>
3.2 独立的Controller
实现Initializable接口,initialize()方法会在JavaFX Injection(field injection和method injection)之后,自动调用,方便对注入的JavaFX控件进行初始化。
public class LoginPaneController implements Initializable { @FXML private TextField nameTxt; @FXML public void loginAction() { System.out.println("Do login"); } @Override public void initialize(URL location, ResourceBundle resources) { // This method will be call after all FXML injection completed. // We can initialize JavaFX control here. nameTxt.textProperty().addListener((obversable, oldVal, newVal) -> { System.out.println("Text changed."); }); } }
创建一个JavaFX程序,使用该FXML控件。
public class LoginFX extends Application { @Override public void start(Stage stage) throws Exception { FXMLLoader loader = new FXMLLoader( LoginFX.class.getResource("LoginPaneFx.fxml")); LoginPaneController controller = new LoginPaneController(); loader.setController(controller); GridPane loginPane = loader.<GridPane> load(); stage.setScene(new Scene(loginPane)); stage.setTitle("Login"); stage.show(); } }
4. 总结
耦合式的Controller,里面封装了FXMLLoader的相关行为,方便使用者,和使其它JavaFX控件一样使用。
独立式的Controller,代码简洁,完全使用@FXML注入。使用起来稍微不太方便,需要单独构建一个FXMLLoader.
我Google了很多教程,关于如何创建自定义控件,大部分都是采用耦合式的。具体哪个好,哪个坏,各有千秋。