提问者:小点点

JavaFX自定义控件再探


我已经运行了掌握FXML示例,即如何使用FXML在JavaFX 2.0中创建自定义组件,并尝试了该站点的各种其他解决方案,但是我仍然没有找到足够简单的示例来显示如何设置自定义控件,该控件不是GUI的唯一部分。既然这个问题仍然存在,看来我们需要一个更简单的例子..

我试图创建一个简单的控件,由一个垂直的SplitPane组成,顶部有一个按钮,下部有一个标签。然后我想把这个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>

和控制器类:

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标签及其所有内容剪切/粘贴到其中

事先非常感谢。

更新:我已经将< code>SplitPane分解成它自己的FXML和控制器类。下面是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>

以及控制器类(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>

CustomSplitPaneController中的fxmlLoader. load()似乎导致了java.lang.StackOverflow Error。也许现在对这里的人来说,我错过了什么更明显?

再次感谢。


共3个答案

匿名用户

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

在尝试创建您发布的自定义拆分窗格时,当您创建自定义拆分窗格控制器的实例时,发生的情况是:

    < li >在< code > CustomSplitPaneController 的构造函数中创建一个< code>FXMLLoader,它加载< code > customsplitpane . fxml < Li > < code > customsplitpane . fxml 具有< code>fx:controller属性,该属性将< code > CustomSplitPaneController 指定为控制器类,因此它创建< code > CustomSplitPaneController 的新实例(当然是通过调用其构造函数) < Li > < code > CustomSplitPaneController 的构造函数创建加载< code > customsplitpane . fxml 的< code>FXMLLoader <李>等等。

所以很快,你会得到一个堆栈溢出异常。

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

在您试图复制的模式中,如这里和这里所示,这是稍微修改的。在这个版本中,“视图”由一个FXML文件定义,控制器(行为)直接在控制类本身中实现(没有单独的行为类)。

为了实现这一点,您必须使用与平时略有不同的FXML。首先,当您使用自定义控件时,您将直接实例化控件类(不知道定义其布局的FXML)。因此,如果你在java中使用它,你会做新的CustomSplitPane(),如果你正在FXML中使用它你会做

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

public class CustomSplitPane extends SplitPane {

    // ...

}

现在,在CustomSplitPane的构造函数中,您需要加载定义布局的FXML文件,但您需要它来布局当前对象。(在FXML文件的常用用法中,FXMLLoader为指定类型的层次结构的根创建一个新节点,load()方法将其返回。您希望FXMLLoader使用现有对象作为层次结构的根部。)要执行此操作,请使用

loader.setRoot(this);

此外,由于处理程序方法是在当前对象中定义的,所以您也希望控制器成为当前对象:

loader.setController(this);

由于您将现有对象指定为控制器,因此您不能在FXML文件中具有fx:控制器属性。

所以你最终会:

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>

匿名用户

自定义控件类应扩展父类之一,例如“区域”或“窗格”。如果您懒得做布局工作,只需扩展像 GridPane 这样的高级窗格即可。

然后,您的自定义控件类应该加载一个包含SplitPane及其子级的FXML。FXML的控制器可以简单地是这个自定义控制类,也可以将其分离为其个人控制器类。SplitPane节点应作为自定义控件类的子节点添加;这意味着您的自定义控件(它是Parent类型)必须处理一些布局逻辑。此时,您的自定义控件已完成。

该控件已准备好在FXML中使用。但是,如果您想在Scene Builder中使用它,您需要将其打包到JAR文件中,并将其添加到Scene Builder。请注意,为了使Scene Builder工作,您的自定义控件类必须定义一个无参数构造函数。

匿名用户

我不完全确定这是否是您要查找的,但是JavaFX控件基于模型,视图,控制器(MVC)模式。

模型,其中模型类是存储系统信息的地方。例如,如果您有一个文本字段,您将在模型类中存储该文本字段包含的值。我总是认为它是我控制的微型数据库。

View View类是控件的外观。定义大小、形状、颜色等。注意“颜色”,这是您设置控件默认颜色的地方。(这也可以使用FXML来完成,但我个人更愿意使用java代码)。模型通常作为使用bean财产绑定的参数传递给View构造函数。(对于java,不确定如何对FXML进行操作)

控制器控制器类是可以进行操作的地方。如果我单击一个按钮,或更改textField中的某些内容,控制器会做什么或如何操作模型。Model和View都作为参数传递给控制器。这为控制器提供了对模型和视图的参考,从而允许控制器按照设计操作每个模型和视图。其他外部类可以与控制器类交互,并且控制器类对模型和视图进行操作。

话虽如此,在没有任何额外信息的情况下,看起来你所做的一切都只是将现有的控件组合成预定义的控件以供重用。定义一个扩展SplitPane的类,以及一个已经将按钮和标签添加到所需位置的构造函数,可能值得一试。然后,你的新类可以像SplitPane一样被处理,也可以内置你对按钮的操作。

书中对此进行了很好的分析,

Apress JavaFX 8示例介绍第6章