【问题标题】:JavaFX Custom Control revisited重新审视 JavaFX 自定义控件
【发布时间】:2017-01-10 22:43:08
【问题描述】:

我已经运行了Mastering FXML exampleHow to create custom components in JavaFX 2.0 using FXML 并尝试了该站点的各种其他解决方案,但我仍然没有找到一个足够简单的示例来展示如何设置一个不是唯一部分的自定义控件图形用户界面。由于问题仍在弹出,我们似乎需要为我们中的一些人提供一个更简单的示例..

我正在尝试创建一个简单的控件,该控件由一个垂直的 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 标记及其所有内容剪切/粘贴到其中。我用&lt;packageName.ControlClassName /&gt; 替换了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>

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

再次感谢。

【问题讨论】:

  • 我不明白您发布的代码的哪一部分被您认为是此处的“自定义控件”。您是否尝试过按照documentation 中描述的方式执行此操作?
  • 我有,但似乎缺少一些东西。我希望“自定义控件”成为 SplitPane 标记内的所有内容。
  • 那么你为什么不在你的问题中发布呢?如果他们看不到您的代码,没有人可以帮助您弄清楚您缺少什么。
  • 公平点。我希望有人能够在一个工作示例中为我破解 SplitPane,而不是调查我的代码中的错误。我可能是错的。我将再写一次尝试并发布代码。谢谢。
  • 不过,您已经引用了一个非常好的工作示例……目前尚不清楚(至少对我而言)您想要的内容不在您链接的示例中。

标签: javafx


【解决方案1】:

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

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

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

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


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

在您尝试复制的模式中,显示为herehere,对此进行了轻微修改。在这个版本中,“视图”由 FXML 文件定义,控制器(行为)直接在控制类本身中实现(没有单独的行为类)。

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

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

public class CustomSplitPane extends SplitPane {

    // ...

}

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

loader.setRoot(this);

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

loader.setController(this);

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

所以你最终得到:

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>

【讨论】:

  • 优秀。感谢您花时间写这篇文章。
  • 谢谢詹姆斯,我在这里也学到了一些新东西,可以指定根节点:)
【解决方案2】:

您的自定义控件类应扩展Parent 类之一,例如RegionPane。如果您懒得做布局,只需扩展一个高级窗格,如 GridPane

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

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

【讨论】:

  • 啊,那可能是我出错的地方。我将我读到的所有内容解释为控制类可以扩展SplitPane,这是我迄今为止所做的。我会尝试扩展AnchorPane。非常感谢!
  • @Andreas 好吧,您绝对可以从SplitPane 扩展,但是您将如何编写您的 FXML?您丢失了一个容器来托管两个不同的东西,并且在 FXML 中不能有两个根节点。当然,您总是可以选择将其拆分为两个 FXML 并将这两个加载到您的自定义控件中,但是这样会不会更麻烦。
  • 我已经用我从最初的帖子中提取的代码更新了我的原始帖子,并扩展了“AnchorPane”。仍然缺少一些重要的部分..
  • @Andreas 您必须设置控制器。 fxmlLoader.setController(this);
  • @Andreas 但是你发布的控制类没有扩展SplitPane
【解决方案3】:

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

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

查看 View 类在视觉上是您的控件的外观。定义大小、形状、颜色等。关于“颜色”的注释,这是您设置控件默认颜色的地方。 (这也可以使用 FXML 来完成,但我个人更愿意使用 java 代码)。模型通常作为使用 bean 属性绑定的参数传递给 View 构造函数。 (对于 java,不知道你会如何为 FXML 做)

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

话虽如此,在没有任何额外信息的情况下,看起来您所做的一切只是将现有控件组合成预定义的控件以供重复使用。可能值得考虑定义一个扩展 SplitPane 的类,以及一个已经将您的按钮和标签添加到您想要它们的位置的构造函数。然后可以将您的新类视为 SplitPane,并内置您对按钮的操作。

这本书很好地解释了这一点,

Apress JavaFX 8 示例介绍第 6 章

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 有一点需要注意,CustomSplitPane 在技术上并不是一个控制器;它本身在实例化时就像任何控件一样工作(如GridPane 等)。以你现在的命名方式,半年后再看这个,你会感到困惑。
    • 啊,是的,我想我明白你的意思了。 “控制器”实际上应该是一个包含 CustomSplitPane 实例的项目/对象,对吗?但是由于在这种情况下 CustomSplitPaneController.java 将自己设置为控制器,所以可以这么说,我可以侥幸逃脱。 (?) =)
    猜你喜欢
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-12
    • 2012-02-12
    相关资源
    最近更新 更多