如何使用FXML在JavaFX 2.0中创建自定义组件?

问题描述:

我似乎无法找到关于这个问题的任何材料。举一个更具体的例子,假设我想创建一个组合了复选框和标签的简单组件。然后,使用此自定义组件的实例填充ListView。如何使用FXML在JavaFX 2.0中创建自定义组件?

UPDATE: 看到我的回答对完整的代码

更新2: 对于上最新的教程,请咨询the official documentation。在2.2中添加了很多new stuff。最后,Introduction to FXML涵盖了您需要了解的关于FXML的几乎所有内容。

更新3: 亨德里克·埃贝斯作出了极大的帮助blog post有关自定义UI控件。

+0

“官方文档”链接中的自定义控件示例已损坏。有两种方法不属于API的一部分。 –

+0

@danLeon首先,这不是我的“官方文档”。它是由正在从事JavaFX工作的Oracle员工编写的“官方文档”。其次,我链接的代码包含了一个如何在JavaFX 2.2中创建自定义组件的工作示例。最有可能的是你的版本较旧,因此缺少方法。以下是该页面的一个亮点:“在开始之前,请确保您使用的NetBeans IDE的版本支持JavaFX 2.2” – Andrey

+0

You right!我的IDE是在JavaFx 2.1下,感谢评论。现在超过2.2,我在我的电脑中删除了以前的任何Java版本。 –

更新:对于上最新的教程,请咨询the official documentation。在2.2中添加了很多new stuff。此外,Introduction to FXML涵盖了您需要了解的关于FXML的几乎所有内容。最后,Hendrik Ebbers对自定义UI控件做了非常有用的blog post


后的望API周围,并通过一些文档(Intro to FXMLGetting started with FXMLProperty bindingFuture of FXML),我想出了一个相当明智的解决办法读了几天。 我从这个小实验中学到的最不直接的信息是,控制器的实例(在FXML中用fx:controller声明)由载入FXML文件的FXMLLoader保存......最糟糕的是,这个重要的其实在所有的文档中one place只提到我看到:

控制器通常只对创建它

所以请记住FXML装载机可见,以编程方式(从Java代码)获取对在FXML中声明的控制器实例的引用,fx:controller使用FXMLLoader.getController()(参考下面的ChoiceCell类的完整示例的实现)。

另一件需要注意的是,Property.bindBiderctional()将调用属性的值设置为作为参数传入的属性的值。给定两个布尔属性target(最初设置为false)和source(最初设置为true),调用target.bindBidirectional(source)将设置值targettrue。显然,随后要么财产的任何改变都会改变其他财产的价值(target.set(false)会导致source的值设置为false):

BooleanProperty target = new SimpleBooleanProperty();//value is false 
BooleanProperty source = new SimpleBooleanProperty(true);//value is true 
target.bindBidirectional(source);//target.get() will now return true 
target.set(false);//both values are now false 
source.set(true);//both values are now true 

不管怎么说,这是一个演示FXML和Java如何能完整的代码

com.example.javafx.choice 
    ChoiceCell.java 
    ChoiceController.java 
    ChoiceModel.java 
    ChoiceView.fxml 
com.example.javafx.mvc 
    FxmlMvcPatternDemo.java 
    MainController.java 
    MainView.fxml 
    MainView.properties 

FxmlMvcPatternDemo.java

:在一起(以及其他一些有用的东西)

封装结构工作

package com.example.javafx.mvc; 

import java.util.ResourceBundle; 
import javafx.application.Application; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.stage.Stage; 

public class FxmlMvcPatternDemo extends Application 
{ 
    public static void main(String[] args) throws ClassNotFoundException 
    { 
     Application.launch(FxmlMvcPatternDemo.class, args); 
    } 

    @Override 
    public void start(Stage stage) throws Exception 
    { 
     Parent root = FXMLLoader.load 
     (
      FxmlMvcPatternDemo.class.getResource("MainView.fxml"), 
      ResourceBundle.getBundle(FxmlMvcPatternDemo.class.getPackage().getName()+".MainView")/*properties file*/ 
     ); 

     stage.setScene(new Scene(root)); 
     stage.show(); 
    } 
} 

MainView.fxml

<?xml version="1.0" encoding="UTF-8"?> 

<?import java.lang.*?> 
<?import javafx.scene.*?> 
<?import javafx.scene.control.*?> 
<?import javafx.scene.layout.*?> 

<VBox 
    xmlns:fx="http://javafx.com/fxml" 
    fx:controller="com.example.javafx.mvc.MainController" 

    prefWidth="300" 
    prefHeight="400" 
    fillWidth="false" 
> 
    <children> 
     <Label text="%title" /> 
     <ListView fx:id="choicesView" /> 
     <Button text="Force Change" onAction="#handleForceChange" /> 
    </children> 
</VBox> 

MainView.properties

title=JavaFX 2.0 FXML MVC demo 

MainController.java

package com.example.javafx.mvc; 

import com.example.javafx.choice.ChoiceCell; 
import com.example.javafx.choice.ChoiceModel; 
import java.net.URL; 
import java.util.ResourceBundle; 
import javafx.collections.FXCollections; 
import javafx.event.ActionEvent; 
import javafx.fxml.FXML; 
import javafx.fxml.Initializable; 
import javafx.scene.control.ListCell; 
import javafx.scene.control.ListView; 
import javafx.util.Callback; 

public class MainController implements Initializable 
{ 
    @FXML 
    private ListView<ChoiceModel> choicesView; 

    @Override 
    public void initialize(URL url, ResourceBundle rb) 
    { 
     choicesView.setCellFactory(new Callback<ListView<ChoiceModel>, ListCell<ChoiceModel>>() 
     { 
      public ListCell<ChoiceModel> call(ListView<ChoiceModel> p) 
      { 
       return new ChoiceCell(); 
      } 
     }); 
     choicesView.setItems(FXCollections.observableArrayList 
     (
      new ChoiceModel("Tiger", true), 
      new ChoiceModel("Shark", false), 
      new ChoiceModel("Bear", false), 
      new ChoiceModel("Wolf", true) 
     )); 
    } 

    @FXML 
    private void handleForceChange(ActionEvent event) 
    { 
     if(choicesView != null && choicesView.getItems().size() > 0) 
     { 
      boolean isSelected = choicesView.getItems().get(0).isSelected(); 
      choicesView.getItems().get(0).setSelected(!isSelected); 
     } 
    } 
} 

ChoiceView.fxml

<?xml version="1.0" encoding="UTF-8"?> 

<?import java.lang.*?> 
<?import javafx.scene.*?> 
<?import javafx.scene.control.*?> 
<?import javafx.scene.layout.*?> 

<HBox 
    xmlns:fx="http://javafx.com/fxml" 

    fx:controller="com.example.javafx.choice.ChoiceController" 
> 
    <children> 
     <CheckBox fx:id="isSelectedView" /> 
     <Label fx:id="labelView" /> 
    </children> 
</HBox> 

ChoiceController.java

package com.example.javafx.choice; 

import javafx.beans.value.ChangeListener; 
import javafx.beans.value.ObservableValue; 
import javafx.fxml.FXML; 
import javafx.fxml.Initializable; 
import javafx.scene.control.CheckBox; 
import javafx.scene.control.Label; 

public class ChoiceController 
{ 
    private final ChangeListener<String> LABEL_CHANGE_LISTENER = new ChangeListener<String>() 
    { 
     public void changed(ObservableValue<? extends String> property, String oldValue, String newValue) 
     { 
      updateLabelView(newValue); 
     } 
    }; 

    private final ChangeListener<Boolean> IS_SELECTED_CHANGE_LISTENER = new ChangeListener<Boolean>() 
    { 
     public void changed(ObservableValue<? extends Boolean> property, Boolean oldValue, Boolean newValue) 
     { 
      updateIsSelectedView(newValue); 
     } 
    }; 

    @FXML 
    private Label labelView; 

    @FXML 
    private CheckBox isSelectedView; 

    private ChoiceModel model; 

    public ChoiceModel getModel() 
    { 
     return model; 
    } 

    public void setModel(ChoiceModel model) 
    { 
     if(this.model != null) 
      removeModelListeners(); 
     this.model = model; 
     setupModelListeners(); 
     updateView(); 
    } 

    private void removeModelListeners() 
    { 
     model.labelProperty().removeListener(LABEL_CHANGE_LISTENER); 
     model.isSelectedProperty().removeListener(IS_SELECTED_CHANGE_LISTENER); 
     isSelectedView.selectedProperty().unbindBidirectional(model.isSelectedProperty()) 
    } 

    private void setupModelListeners() 
    { 
     model.labelProperty().addListener(LABEL_CHANGE_LISTENER); 
     model.isSelectedProperty().addListener(IS_SELECTED_CHANGE_LISTENER); 
     isSelectedView.selectedProperty().bindBidirectional(model.isSelectedProperty()); 
    } 

    private void updateView() 
    { 
     updateLabelView(); 
     updateIsSelectedView(); 
    } 

    private void updateLabelView(){ updateLabelView(model.getLabel()); } 
    private void updateLabelView(String newValue) 
    { 
     labelView.setText(newValue); 
    } 

    private void updateIsSelectedView(){ updateIsSelectedView(model.isSelected()); } 
    private void updateIsSelectedView(boolean newValue) 
    { 
     isSelectedView.setSelected(newValue); 
    } 
} 

ChoiceModel.java

package com.example.javafx.choice; 

import javafx.beans.property.BooleanProperty; 
import javafx.beans.property.SimpleBooleanProperty; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 

public class ChoiceModel 
{ 
    private final StringProperty label; 
    private final BooleanProperty isSelected; 

    public ChoiceModel() 
    { 
     this(null, false); 
    } 

    public ChoiceModel(String label) 
    { 
     this(label, false); 
    } 

    public ChoiceModel(String label, boolean isSelected) 
    { 
     this.label = new SimpleStringProperty(label); 
     this.isSelected = new SimpleBooleanProperty(isSelected); 
    } 

    public String getLabel(){ return label.get(); } 
    public void setLabel(String label){ this.label.set(label); } 
    public StringProperty labelProperty(){ return label; } 

    public boolean isSelected(){ return isSelected.get(); } 
    public void setSelected(boolean isSelected){ this.isSelected.set(isSelected); } 
    public BooleanProperty isSelectedProperty(){ return isSelected; } 
} 

ChoiceCell.java

package com.example.javafx.choice; 

import java.io.IOException; 
import java.net.URL; 
import javafx.fxml.FXMLLoader; 
import javafx.fxml.JavaFXBuilderFactory; 
import javafx.scene.Node; 
import javafx.scene.control.ListCell; 

public class ChoiceCell extends ListCell<ChoiceModel> 
{ 
    @Override 
    protected void updateItem(ChoiceModel model, boolean bln) 
    { 
     super.updateItem(model, bln); 

     if(model != null) 
     { 
      URL location = ChoiceController.class.getResource("ChoiceView.fxml"); 

      FXMLLoader fxmlLoader = new FXMLLoader(); 
      fxmlLoader.setLocation(location); 
      fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory()); 

      try 
      { 
       Node root = (Node)fxmlLoader.load(location.openStream()); 
       ChoiceController controller = (ChoiceController)fxmlLoader.getController(); 
       controller.setModel(model); 
       setGraphic(root); 
      } 
      catch(IOException ioe) 
      { 
       throw new IllegalStateException(ioe); 
      } 
     } 
    } 
} 
+0

oooooh现在我明白了。非常感谢。 – glasspill

+0

很乐意帮忙。不过,请确保您查看我在更新中提供的链接。 2.2 – Andrey

快速回答是< fx:include >标记,但是,您需要在Controller类中设置ChoiceModel。

<VBox 
    xmlns:fx="http://javafx.com/fxml" 

    fx:controller="fxmltestinclude.ChoiceDemo" 
> 
    <children> 
    **<fx:include source="Choice.fxml" />** 
    <ListView fx:id="choices" /> 
    </children> 
</VBox> 
+0

另外,看看这个文档http://fxexperience.com/wp-content/uploads/2011/08/Introducing-FXML.pdf – JimClarke

+0

好吧,但我将如何使用Choice.fxml渲染'选择'中的每个项目,清单? – Andrey

为JavaFX 2.1,您可以创建这样一个自定义FXML控制组件:

<?xml version="1.0" encoding="UTF-8"?> 

<?import java.lang.*?> 
<?import java.util.*?> 
<?import javafx.scene.*?> 
<?import javafx.scene.control.*?> 
<?import javafx.scene.layout.*?> 
<?import customcontrolexample.myCommponent.*?> 

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="customcontrolexample.FXML1Controller"> 
    <children> 
     <MyComponent welcome="1234"/> 
    </children> 
</VBox> 

组件代码:

MyComponent.java

package customcontrolexample.myCommponent; 

import java.io.IOException; 
import javafx.beans.property.StringProperty; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Node; 
import javafx.scene.layout.Pane; 
import javafx.util.Callback; 

public class MyComponent extends Pane { 

    private Node view; 
    private MyComponentController controller; 

    public MyComponent() { 
     FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("myComponent.fxml")); 
     fxmlLoader.setControllerFactory(new Callback<Class<?>, Object>() { 
      @Override 
      public Object call(Class<?> param) { 
       return controller = new MyComponentController(); 
      } 
     }); 
     try { 
      view = (Node) fxmlLoader.load(); 

     } catch (IOException ex) { 
     } 
     getChildren().add(view); 
    } 

    public void setWelcome(String str) { 
     controller.textField.setText(str); 
    } 

    public String getWelcome() { 
     return controller.textField.getText(); 
    } 

    public StringProperty welcomeProperty() { 
     return controller.textField.textProperty(); 
    } 
} 

MyComponentController.java

package customcontrolexample.myCommponent; 

import java.net.URL; 
import java.util.ResourceBundle; 
import javafx.fxml.FXML; 
import javafx.fxml.Initializable; 
import javafx.scene.control.TextField; 

public class MyComponentController implements Initializable { 

    int i = 0; 
    @FXML 
    TextField textField; 

    @FXML 
    protected void doSomething() { 
     textField.setText("The button was clicked #" + ++i); 
    } 

    @Override 
    public void initialize(URL location, ResourceBundle resources) { 
     textField.setText("Just click the button!"); 
    } 
} 

myComponent.fxml

<?xml version="1.0" encoding="UTF-8"?> 

<?import java.lang.*?> 
<?import java.util.*?> 
<?import javafx.scene.*?> 
<?import javafx.scene.control.*?> 
<?import javafx.scene.layout.*?> 

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="customcontrolexample.myCommponent.MyComponentController"> 
    <children> 
    <TextField fx:id="textField" prefWidth="200.0" /> 
    <Button mnemonicParsing="false" onAction="#doSomething" text="B" /> 
    </children> 
</VBox> 

此代码需要检查,如果没有记忆泄漏。

+1

添加了很多新东西,非常感谢,这有所帮助。 – ShaggyInjun