41

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

更新:查看我的完整代码答案

更新 2:有关最新教程,请查阅官方文档。2.2 中添加了很多新内容。最后,FXML 简介几乎涵盖了您需要了解的有关 FXML 的所有内容。

更新 3:Hendrik Ebbers 发表了一篇关于自定义 UI 控件的非常有用的博客文章。

4

4 回答 4

40

更新:有关最新教程,请查阅官方文档。2.2 中添加了很多新内容。此外,FXML 简介几乎涵盖了您需要了解的有关 FXML 的所有内容。最后,Hendrik Ebbers 发表了一篇关于自定义 UI 控件的非常有用的博客文章。


在查看API并阅读一些文档(FXML 简介、 FXML属性绑定入门、FXML未来)几天后,我想出了一个相当明智的解决方案。我从这个小实验中学到的最不直接的信息是控制器的实例(在 FXML 中用 fx:controller 声明)由加载 FXML 文件的FXMLLoader持有......最糟糕的是,这个重要的事实在我看到的所有文档中只在一处提到:

控制器通常只对创建它的 FXML 加载器可见

因此,请记住,为了以编程方式(从 Java 代码)获取对fx:controller使用FXMLLoader.getController()在 FXML 中声明的控制器实例的引用(有关完整示例,请参阅下面的 ChoiceCell 类的实现)。

另外需要注意的是,Property.bindBiderctional()会将调用属性的值设置为作为参数传入的属性的值。给定两个布尔属性target(最初设置为false)和source(最初设置为true)调用将设置为target.bindBidirectional(source)的值。显然,对任一属性的任何后续更改都会更改另一个属性的值(将导致 的值设置为):targettruetarget.set(false)sourcefalse

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();
    }
}

主视图.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

主控制器.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);
        }
    }
}

选择视图.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>

选择控制器.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);
    }
}

选择模型.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);
            }
        }
    }
}
于 2011-12-13T14:22:05.010 回答
8

对于 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>

组件代码:

我的组件.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();
    }
}

我的组件控制器.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!");
    }
}

我的组件.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>

这段代码需要检查是否没有内存泄漏。

于 2012-10-01T21:05:11.233 回答
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>
于 2011-12-08T21:55:05.977 回答
2

自定义组件介绍fxml章节

给了我正确的提示。我的意图是将标签、滑块和文本字段组合到一个自定义 LabeledValueSlider 组件中。

使用示例:见rc-dukes Self-Dukes Self-Dukes Self-Driving RC Car Java FX App 的 resources/fx

    <LabeledValueSlider fx:id='cannyThreshold1' text="Canny threshold 1" blockIncrement="1" max="2000" min="0" value="20" format="\%.0f"/>
    <LabeledValueSlider fx:id="cannyThreshold2" text="Canny threshold 2"  blockIncrement="1" max="2000" min="0" value="50" format="\%.0f"/>
    <LabeledValueSlider fx:id="lineDetectRho" text="LineDetect rho" blockIncrement="0.01" max="20" min="0" value="0.5" />
    <LabeledValueSlider fx:id="lineDetectTheta" text="LineDetect theta" blockIncrement="0.01" max="5" min="-5" value="0.5" />
    <LabeledValueSlider fx:id="lineDetectThreshold" text="LineDetect threshold" blockIncrement="1" max="200" min="0" value="20" format="\%.0f" />
    <LabeledValueSlider fx:id="lineDetectMinLineLength" text="LineDetect minLineLength"  blockIncrement="1" max="200" min="0" value="50" format="\%.0f"/>
    <LabeledValueSlider fx:id="lineDetectMaxLineGap" text="LineDetect maxLineGap" blockIncrement="1" max="500" min="0" value="50" format="\%.0f"/>

7个标签值滑块

FXML 文件

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>

<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml">

    <padding>
        <Insets left="10" right="10" />
    </padding>
    <Label fx:id='label' text="Label for Slider" minWidth="180"/>
    <Slider fx:id='slider' blockIncrement="1" max="100" min="0" value="50" />
    <TextField fx:id="textField" maxWidth="75"/>
</fx:root>

组件源码LabeledValueSlider.java

package org.rcdukes.app;

import java.io.IOException;
import java.net.URL;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;

/**
 * a Slider with a Label and a value
 * 
 * @author wf
 *
 */
public class LabeledValueSlider extends HBox {
  public static boolean debug=true;
  protected static final Logger LOG = LoggerFactory
      .getLogger(LabeledValueSlider.class);
  @FXML
  private Label label;
  @FXML
  private Slider slider;
  @FXML
  private TextField textField;

  String format;


  public String getFormat() {
    return format;
  }

  public void setFormat(String format) {
    textField.textProperty().bind(slider.valueProperty().asString(format));
    this.format = format;
  }

  public double getBlockIncrement() {
    return slider.getBlockIncrement();
  }

  public void setBlockIncrement(double value) {
    slider.setBlockIncrement(value);
  }

  public double getMax() {
    return slider.getMax();
  }

  public void setMax(double value) {
    slider.setMax(value);
  }

  public double getMin() {
    return slider.getMin();
  }

  public void setMin(double value) {
    slider.setMin(value);
  }

  public double getValue() {
    return slider.getValue();
  }

  public void setValue(double value) {
    slider.setValue(value);
  }

  public String getText() {
    return label.getText();
  }

  public void setText(String pLabelText) {
    label.setText(pLabelText);
  }

  public URL  getResource(String path) {
    return getClass().getClassLoader().getResource(path);
  }

  /**
   * construct me
   * see https://docs.oracle.com/javase/9/docs/api/javafx/fxml/doc-files/introduction_to_fxml.html#custom_components
   */
  public LabeledValueSlider() {
    FXMLLoader fxmlLoader = new FXMLLoader(
        getResource("fx/labeledvalueslider.fxml"));
    try {
      // let's load the HBox - fxmlLoader doesn't know anything about us yet
      fxmlLoader.setController(this); 
      fxmlLoader.setRoot(this);
      Object loaded = fxmlLoader.load();
      Object root=fxmlLoader.getRoot();

      if (debug) {
        String msg=String.format("%s loaded for root %s", loaded.getClass().getName(),root.getClass().getName());
        LOG.info(msg);
      }

      textField.setAlignment(Pos.CENTER_RIGHT);
      if (format == null)
        setFormat("%.2f");
    } catch (IOException exception) {
      throw new RuntimeException(exception);
    }
  }
}
于 2020-02-23T09:26:24.520 回答