重新审视 JavaFX 自定义控件

标签 javafx

我已经运行了 Mastering FXML example , How to create custom components in JavaFX 2.0 using FXML并尝试了该站点的各种其他解决方案,但我仍然没有找到一个足够简单的示例来说明如何设置不是 GUI 的唯一部分的自定义控件。由于问题仍然出现,看来我们需要一个更简单的例子来帮助我们中的一些人......

我正在尝试创建一个简单的控件,该控件由一个垂直的 SplitPane 组成,顶部有一个 Button,下部有一个标签。然后我想将此 SplitPane 控件的实例放置在 TabPane 的多个选项卡中。 要么控件不显示,要么我陷入各种错误,具体取决于我尝试遵循的示例。所以,我会回溯一下,只是简单地问:我如何分离出 SplitPane 成为此处的自定义控件?

这是 FXML 文档:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>

<TabPane prefHeight="256.0" prefWidth="477.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="customcontroltest.FXMLDocumentController">
   <tabs>
      <Tab>
         <content>
            <SplitPane dividerPositions="0.5" orientation="VERTICAL" prefHeight="114.0" prefWidth="160.0">
              <items>
                <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
                     <children>
                          <Button fx:id="button" onAction="#handleButtonAction" text="Click Me!" />
                     </children>
                  </AnchorPane>
                <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
                     <children>
                          <Label fx:id="label" minHeight="16" minWidth="69" />
                     </children>
                  </AnchorPane>
              </items>
            </SplitPane>
         </content>
      </Tab>
   </tabs>
</TabPane>

和 Controller 类:

package customcontroltest;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

public class FXMLDocumentController implements Initializable
{

    @FXML
    private Label label;

    @FXML
    private void handleButtonAction(ActionEvent event)
    {
        label.setText("Hello World!");
    }

    @Override
    public void initialize(URL url, ResourceBundle rb)
    {
        // TODO
    }    
}

和主要测试类:

package customcontroltest;

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


public class CustomControlTest extends Application
{
    @Override
    public void start(Stage stage) throws Exception
    {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

        Scene scene = new Scene(root);

        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args)
    {
        launch(args);
    }
}

我制作了一个新的 FXML 文件,并将整个 SplitPane 标签及其所有内容剪切/粘贴到其中。我将 FXML 文档中的 SplitPane 标记替换为 <packageName.ControlClassName /> .然后我制作了 Controller 类来扩展 SplitPane。我试过在 FXML 标记和/或 Controller 类中指定 Controller ,但从来没有做对。 知识渊博的人愿意花几分钟来展示一个有效的例子吗?我猜想更多的人会发现这样的例子非常有用。 因此,SplitPane 应该是新的自定义控件,然后您可以默认将其加载到 TabPane 的第一个选项卡中。然后我将编写代码以将更多实例添加到后续选项卡中。

非常感谢您。

更新: 我已经打破了SplitPane进入它自己的 FXML 和 Controller 类。 这是 FXML (CustomSplitPane.fxml):

<fx:root type="javafx.scene.control.SplitPane" dividerPositions="0.5" orientation="VERTICAL" prefHeight="114.0" prefWidth="160.0" xmlns:fx="http://javafx.com/fxml/1" fx:controller="customcontroltest.CustomSplitPaneController">
    <items>
        <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
             <children>
                  <Button fx:id="button" onAction="#handleButtonAction" text="Click Me!" />
             </children>
        </AnchorPane>
        <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
             <children>
                  <Label fx:id="label" minHeight="16" minWidth="69" />
             </children>
        </AnchorPane>
    </items>
</fx:root>

和 Controller 类(CustomSplitPaneController.java):

package customcontroltest;

public class CustomSplitPaneController extends AnchorPane
{
    @FXML
    private Label label;
    private SplitPane mySplitPane;

    public CustomSplitPaneController()
    {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("CustomSplitPane.fxml"));

        try 
        {
            fxmlLoader.load();
        } catch (IOException exception) 
        {
            throw new RuntimeException(exception);
        }
    }

    @FXML
    private void handleButtonAction(ActionEvent event)
    {
        label.setText("Hello World!");
    }
}

原来的主 FXML 现在看起来像这样:

<TabPane prefHeight="256.0" prefWidth="477.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="customcontroltest.FXMLDocumentController">
   <tabs>
      <Tab>
         <content>
            <customcontroltest.CustomSplitPaneController /> 
         </content>
      </Tab>
   </tabs>
</TabPane>

fxmlLoader.load()CustomSplitPaneController似乎导致了 java.lang.StackOverflowError。 也许现在这里的人更清楚我所缺少的是什么?

再次感谢。

最佳答案

fx:controller FXML 文件中的属性是 FXML 加载器创建指定类的实例并将其用作 FXML 定义的 UI 层次结构的 Controller 的指令。

在尝试创建您发布的自定义拆分 Pane 时,当您创建 CustomSplitPaneController 的实例时会发生什么?是:

  • 您创建了一个 FXMLLoaderCustomSplitPaneController 的构造函数中,加载 CustomSplitPane.fxml
  • CustomSplitPane.fxml有一个 fx:controller属性指定 CustomSplitPaneController作为 Controller 类,因此它创建了 CustomSplitPaneController 的新实例(当然是通过调用它的构造函数)
  • CustomSplitPaneController 的构造函数创建一个 FXMLLoader加载 CustomSplitPane.fxml
  • 等等。

很快,您会遇到堆栈溢出异常。


JavaFX 中的控制类封装了 View 和 Controller 。在标准的 JavaFX 控件类中, View 由 Skin 表示。类和 Controller 由 Behavior类(class)。控件类本身扩展了 Node (或子类:Region),当您实例化它时,它会同时实例化皮肤和行为。皮肤定义控件的布局和外观,行为将各种输入操作映射到修改控件本身属性的实际代码。

在您尝试复制的模式中,显示为 herehere ,这里稍作修改。在此版本中,“ View ”由 FXML 文件定义, Controller (行为)直接在控件类本身中实现(没有单独的行为类)。

要完成这项工作,您必须使用与平常略有不同的 FXML。首先,当您使用自定义控件时,您将直接实例化控件类(无需了解定义其布局的 FXML)。所以如果你在 java 中使用它,你会做 new CustomSplitPane() ,如果你在 FXML 中使用它,你会做 <CustomSplitPane> .无论哪种方式,您都会调用自定义控件的构造函数(我称之为 CustomSplitPane )。

使用CustomSplitPane在 UI 层次结构中,它当然必须是 Node子类。如果你想让它成为一种 SplitPane , 你会让它扩展 SplitPane :

public class CustomSplitPane extends SplitPane {

    // ...

}

现在,在 CustomSplitPane 的构造函数中,您需要加载定义布局的 FXML 文件,但您需要它来布置当前对象。 (在 FXML 文件的通常用法中,FXMLLoader 为层次结构的根创建一个指定类型的新节点,load() 方法返回它。您希望 FXMLLoader 使用现有对象作为层次结构的根。)为此,您使用 <fx:root>元素作为 FXML 文件的根元素,你告诉 FXMLLoader使用 this作为根:

loader.setRoot(this);

此外,由于处理程序方法是在当前对象中定义的,因此您还希望 Controller 成为当前对象:

loader.setController(this);

由于您将现有对象指定为 Controller ,因此您不能有 fx:controller FXML 文件中的属性。

所以你最终得到:

package customcontroltest;

public class CustomSplitPane extends SplitPane {
    @FXML
    private Label label;

    public CustomSplitPaneController() {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("CustomSplitPane.fxml"));

        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try  {
            fxmlLoader.load();
        } catch (IOException exception)  {
            throw new RuntimeException(exception);
        }
    }

    @FXML
    private void handleButtonAction(ActionEvent event) 
        label.setText("Hello World!");
    }
}

和 FXML 文件:

<fx:root type="javafx.scene.control.SplitPane" dividerPositions="0.5" orientation="VERTICAL" prefHeight="114.0" prefWidth="160.0" xmlns:fx="http://javafx.com/fxml/1" >
    <items>
        <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
             <children>
                  <Button fx:id="button" onAction="#handleButtonAction" text="Click Me!" />
             </children>
        </AnchorPane>
        <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
             <children>
                  <Label fx:id="label" minHeight="16" minWidth="69" />
             </children>
        </AnchorPane>
    </items>
</fx:root>

现在您可以根据需要在另一个 FXML 文件中使用它:

<TabPane prefHeight="256.0" prefWidth="477.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="customcontroltest.FXMLDocumentController">
   <tabs>
      <Tab>
         <content>
            <customcontroltest.CustomSplitPane /> 
         </content>
      </Tab>
   </tabs>
</TabPane>

关于重新审视 JavaFX 自定义控件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41579752/

相关文章:

Java任务什么都不做?

JavaFX:TableView:获取选定的行单元格值

java - 使用逻辑条件验证文本字段的完整性

WebView 中 JavaFX 滚动条交叉点的颜色

java - JavaFX FXMLLoader 上的 NullPointer 位置

java - Java JavaFX 应用程序中的多线程问题

java - 使用 JavaFX 创建 webapp - 可能吗?

java - 如何强制重新渲染 JavaFX ComboBox 下拉列表单元格?

java - 您可以使用 java.swing 和 javafx 进行 android 开发吗?

javafx - JavaFX中 slider 拇指的值