2011-12-08 248 views
30

我似乎無法找到關於這個問題的任何材料。舉一個更具體的例子,假設我想創建一個組合了複選框和標籤的簡單組件。然後,使用此自定義組件的實例填充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版本。 –

回答

28

更新:對於上最新的教程,請諮詢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

2

快速回答是< 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

4

爲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

相關問題