重新访问JavaFX Custom Control

时间:2017-01-10 22:43:08

标签: javafx

我已经运行了Mastering FXML exampleHow 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>

控制器类:

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标签及其所有内容剪切/粘贴到其中。我用<packageName.ControlClassName />替换了FXML文档中的SplitPane标记。然后我创建了控制器类来扩展SplitPane。我已经尝试在FXML标签和/或控制器类中指定控制器,但从来没有做对。 有正确知识的人是否愿意花几分钟才能展示一个有效的例子?我猜更多的人会发现这样的例子非常有用。 因此,SplitPane应该是新的自定义控件,然后您可以默认将其加载到TabPane的第一个选项卡中。然后我将编写代码以在后续选项卡中添加更多实例。

非常感谢你。

更新 我已将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>

fxmlLoader.load()中的CustomSplitPaneController似乎导致了java.lang.StackOverflowError。 也许这里的某些人现在更加明显我缺少什么?

再次感谢。

4 个答案:

答案 0 :(得分:2)

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

在尝试创建您发布的自定义拆分窗格时,创建CustomSplitPaneController实例时会发生以下情况:

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

所以很快就会出现堆栈溢出异常。

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

在您尝试复制的模式中,显示herehere,这稍作修改。在这个版本中,&#34;视图&#34;由FXML文件定义,控制器(行为)直接在控件类本身中实现(没有单独的行为类)。

为了使这项工作,您必须使用FXML与通常略有不同。首先,当您使用自定义控件时,您将直接实例化控件类(不知道定义其布局的FXML)。因此,如果您在java中使用它,那么您将new CustomSplitPane(),如果您在FXML中使用它,您将执行<CustomSplitPane>。无论哪种方式,您都可以调用自定义控件的构造函数(我正在调用CustomSplitPane)。

要在UI层次结构中使用CustomSplitPane,它当然必须是Node子类。如果您希望它是一种SplitPane,您可以将其扩展为SplitPane

public class CustomSplitPane extends SplitPane {

    // ...

}

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

FXMLLoader

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

this

由于您将现有对象指定为控制器,因此FXML文件中不得包含loader.setRoot(this); 属性。

所以你最终得到:

loader.setController(this);

和FXML文件:

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!");
    }
}

答案 1 :(得分:0)

您的自定义控件类应该扩展<suite name="SuiteName" verbose="2">类之一,例如ParentRegion。如果你懒得做布局工作,只需扩展一个高级窗格,如Pane

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

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

答案 2 :(得分:0)

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

模型 Model类是为系统存储任何信息的位置。例如,如果您有textField,则存储文本字段在模型类中保存的值。我一直认为它是我控制的微型数据库。

查看 View类在视觉上是您的控件的样子。定义尺寸,形状,颜色等。有关&#34;颜色&#34;的注释,您可以在此处设置控件的默认颜色。 (这也可以使用FXML完成,但我个人宁愿使用java代码)。该模型通常作为使用bean属性进行绑定的参数传递给View构造函数。 (对于java,不确定你是如何为FXML做的)

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

话虽如此,没有任何其他信息,看起来您所做的一切只是将现有控件组合成预先定义的重用内容。可能值得研究定义一个扩展SplitPane的类,以及一个已经将按钮和标签添加到所需位置的构造函数。然后,您的新课程可以像SplitPane一样对待,也可以对内置的按钮执行操作。

这本书真的很好地分解了,

Apress JavaFX 8示例第6章简介

答案 3 :(得分:0)

好的,所以这里是所有布局的工作解决方案,逐个文件。希望这对其他人也有用。

<强> CustomControlTest.java

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

<强> FXMLDocument.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>
            <customcontroltest.CustomSplitPaneController /> 
         </content>
      </Tab>
   </tabs>
</TabPane>

<强> FXMLDocumentController.java

package customcontroltest;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.Initializable;

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

<强> CustomSplitPane.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.*?>

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

NetBeans IDE将在#handleButtonAction上发出错误,说“控制器未在根组件上定义”,但它实际上不会产生编译错误。 (当我看到突出显示的错误时,我被欺骗甚至没有尝试编译!)

<强> CustomSplitPaneController.java

package customcontroltest;

import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;


public class CustomSplitPaneController 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!");
    }
}