JavaFX创建自定义控件

By | 3月 13, 2017

使用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。

image

1.2 FXML中指定Controller

在Controller面板里面,可以给FXML指定Controller(包名+类名)。如果FXML没有指定,就需要在code中调用FXMLLoader setController()方法,指定一个Controller。

image

SceneBuilder中可以查看FXML对应Controller大致框架,copy到code中就可以用了。Menu->View->”Show Sample Controller Skeelton”。

image

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了很多教程,关于如何创建自定义控件,大部分都是采用耦合式的。具体哪个好,哪个坏,各有千秋。