JavaFX-FXML:initialize()似乎无法识别实例变量



请求解释
如果这个解释在网上其他地方,我就找不到了。这个问题似乎适用于所有类型的FXML自定义小部件。

这是对工作程序模块的相当大的简化。最初的想法是初始化并使用一些实例变量在自定义小部件控制器中。

当在构造函数中完成变量初始化时,所有操作都很好。将初始化从构造函数中移出的想法对于"initialize()"方法来说,这在当时似乎很好。主要是为了防止未来可能会有更多的变量,直到构造函数运行之后。

使用"initialize()"方法出现了一些我不完全理解的问题如这里的代码所例示的。

"initialize()"方法似乎无法识别实例变量。所提供的代码是工作形式的,即小部件出现并工作。这是不好的东西被评论出来只是为了让人们看到它的工作原理。

但是,如果取消注释"initialize()"方法并尝试运行在程序中,它会在一个简单的实例变量上出现NullPointerException。实际程序无法识别HashMap,但此处的PrintStream可以减少发布代码中的混乱。

无论有没有@FXML注释,问题都会出现在各种地方以及组合。

失败的原因似乎多种多样,可能包括以下几点
1."initialize()"的工作方式与我在阅读其描述后认为的不同
2."initialize()"和进程线程之间没有通信
3.派生自超类的自定义小部件控制器会把事情搞砸吗
4.测试是在使用Java 8的NetBeans 8.0.2中运行的,这会把它搞砸吗?但问题是为什么
5.注释不能很好地用于子类
6.以上的组合还是完全不同的东西?

自定义控制器Java代码,DirectorySelectionWidgets.Java:

package blogpost ;
// Java imports
import java.io  .PrintStream ;
// JavaFX imports
import javafx.event           .ActionEvent ;
import javafx.scene.control   .Button      ;
import javafx.scene.control   .Control     ;
import javafx.fxml            .FXML        ;
public class DirectorySelectionWidgets extends UserControl
  {
    @FXML
    private Button fromRootSelectionButton ;
    /**
     *  Does not work with or without @FXML annotation.
     */
//    @FXML
    protected PrintStream out = System.out ;
    /**
     *  UNCOMMENT method to see the NullPointerException on instance variable.
     *    The fuller version failed on important variables.
     *  <P>
     *  Does not work with or without @FXML annotation.
     */
//    @FXML
//    public void initialize()
//      { out.println( "HERE just entered initialize()" ) ; }
    public DirectorySelectionWidgets()
     { super() ; }
    @FXML
    private void handleRootSelectionRequest( ActionEvent actionEvent )
      {
        Control control = (Control) actionEvent.getSource() ;
        out.println( 
            "HERE control inside handleRootSelectionRequest control =>n   "
            + control
                   ) ;
      }
  }

自定义小部件fxml文件,DirectorySelectionWidgets.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.net.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.text.*?>
<GridPane id="rootSelectorPane" fx:id="rootSelectorPane" alignment="CENTER" gridLinesVisible="false" layoutY="42.0" maxWidth="1.7976931348623157E308" prefWidth="828.0" styleClass="root-selector-pane" xmlns:fx="http://javafx.com/fxml" >
  <children>
    <Button id="fromRootSelectionButton" fx:id="fromRootSelectionButton" alignment="CENTER" mnemonicParsing="false" onAction="#handleRootSelectionRequest" prefWidth="168.0" styleClass="root-selector-buttons" text="Set 'From' Root Directory" textAlignment="CENTER" GridPane.columnIndex="0" GridPane.halignment="CENTER" GridPane.hgrow="NEVER" GridPane.rowIndex="0" GridPane.valignment="CENTER" GridPane.vgrow="NEVER" />
  </children>
  <columnConstraints>
    <ColumnConstraints fillWidth="false" halignment="LEFT" hgrow="NEVER" minWidth="-Infinity" prefWidth="166.0" />
  </columnConstraints>
  <rowConstraints>
    <RowConstraints maxHeight="-1.0" minHeight="-1.0" prefHeight="-1.0" vgrow="NEVER" />
  </rowConstraints>
<stylesheets>
  <URL value="@PuzzleCss.css" />
</stylesheets>
</GridPane>

自定义小部件超类UserControl.java:

package blogpost ;
/*
 *   Information link as of April 2016 is
 *   <A HREF="https://programmingwithpassion.wordpress.com/2013/07/07/creating-a-reusable-javafx-custom-control/">
 *     <I>Benjamin's programming Blog</I>.
 *   </A>
 * <BR>
 * Orginal copyright 2014 Benjamin Gale.
 * License document is also there inside the Java file on his blog.
 * <P>
 * Modified in accordance with license.
 */
// Java imports
import java.io            .IOException ;
import java.util.logging  .Level       ;
import java.util.logging  .Logger      ;
import java.net           .URL         ;
// JavaFX imports
import javafx.fxml          .FXMLLoader  ;
import javafx.geometry      .HPos        ;
import javafx.scene         .Node        ;
import javafx.scene.layout  .Region      ;
import javafx.geometry      .VPos        ;
/**
 *  This is a convenience class for creating custom controls that use the
 *    {@code FXMLLoader loader = new FXMLLoader() ;}
 *    approach. Mainly used for custom widgets.
 *  <P>
 *  Just subclass this class and all the FXMLLoader work is already done.
 *  <P>
 *  The only file restrictions are the following.
 *    <UL>
 *      <LI>
 *        The controller file and the fxml file must be in the same package.
 *      </LI>
 *      <LI>
 *        The fxml file must have the same (case sensitive) name (sans suffix)
 *          as the controller class.
 *        <BR>
 *        That is, 
 *          if the controller file is named {@code MyController.java} then
 *          the fxml file must be named {@code MyController.fxml}.
 *          <BR>
 *          This also works with other JavaFX controller files; for example,
 *          {@code MyController.groovy} would work for Groovy developers.
 *      </LI>
 *    </UL>
 */
public abstract class UserControl extends Region
  {
    private final String resourcePath = "%s.fxml" ;
    public UserControl()
      { this.loadView() ; }
    /**
     *  A primary purpose for this class,
     *   make creating custom controls easier.
     */
    private void loadView()
      {
        FXMLLoader fxmlLoader = new FXMLLoader() ;
        fxmlLoader.setController( this ) ;
        fxmlLoader.setLocation( this.getViewURL() ) ;
        try
          {
            Node root = (Node) fxmlLoader.load() ;
            setMaxSize( root ) ;
            this.getChildren().add( root ) ;
          }
        catch ( IOException ioException )
          { 
            Logger.getLogger( UserControl.class.getName() )
              .log( Level.SEVERE, null, ioException ) ;
          }
      }
    private String getViewPath()
      { return String.format( resourcePath, this.getClass().getSimpleName() ) ; }
    private URL getViewURL()
      { return this.getClass().getResource( this.getViewPath() ) ; }
    @Override
    protected void layoutChildren()
      {
        getChildren().stream().forEach(
            (node) ->
              { layoutInArea( node, 0, 0,
                              getWidth(), getHeight(),
                              0,
                              HPos.LEFT, VPos.TOP
                             ) ;
              }
          ) ;
      }
    private void setMaxSize(Node node)
      {
        if ( node != null && node instanceof Region )
          {
            Region region = (Region) node ;
            region.setMaxWidth(  Double.MAX_VALUE ) ;
            region.setMaxHeight( Double.MAX_VALUE ) ;
          }
      }
  }

测试Java代码,DirectorySelectionWidgetsTest.Java:

package blogpost ;
// JavaFX imports
import javafx.application  .Application ;
import javafx.fxml         .FXMLLoader  ;
import javafx.scene        .Parent      ;
import javafx.scene        .Scene       ;
import javafx.stage        .Stage       ;
public class DirectorySelectionWidgetsTest extends Application
  {
    // Written this way while reducing code to smaller size and new locations.
    protected String fxmlFullFileName = ""
                   + "blogpost"
                   + "/"
                   + "DirectorySelectionWidgetsTest"
                   + "."
                   + "fxml" ;
    protected String mainTitle = "Test directory selection widgets" ;
    @Override
    public void start( Stage stage )
      throws Exception
      {
        FXMLLoader fxmlLoader = new FXMLLoader() ;
          fxmlLoader.setController( this )       ;
        Parent root = fxmlLoader.load( 
            getClass().getClassLoader().getResource( fxmlFullFileName )
                                     ) ;
        Scene scene = new Scene( root ) ;
          stage.setTitle( mainTitle )   ;
          stage.setScene( scene )       ;
        stage.show() ;
      }
    public static void main( String[] args )
      { launch( args ) ; }
  }

测试fxml文件,DirectorySelectionWidgetsTest.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import blogpost.*?>
<AnchorPane id="anchorPane" fx:id="anchorPane" styleClass="header-title-pane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
  <children>
    <DirectorySelectionWidgets id="selectionWidgets" fx:id="selectionWidgets" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
  </children>
  <stylesheets>
    <URL value="@PuzzleCss.css" />
  </stylesheets>
</AnchorPane>

css文件,PuzzleCss.css:

.root-selector-buttons
  {
    -fx-background-color  :
      green, linear-gradient( to bottom right, #FFFF00 40%, #99FF33 100% ) ;
    -fx-text-fill : black ;
  }
.root-selector-pane
  {
    -fx-background-color  : #DDFFBB    ;
    -fx-border-color      : #DDFFBB    ;
  }
.rootSelectorTextFields
  {
    -fx-border-color : #00BB00 ;
    -fx-text-fill    : black   ;
  }

我认为答案是一个意外的启动序列
我期待着通常的构造函数流;超类(UserControl)构造函数->子类(DirectorySelectionWidgets)构造函数(定义、子类变量,然后运行构造函数代码)->等等

我得到的是子类方法,initialize()是在运行子类构造函数之前在超类构造函数操作中调用的
注意:initialize()是子类DirectorySelectionWidgets中的一个方法。

这是小路。

DirectorySelectionWidgetsTest=>start(),输入
UserControl()=>构造函数,输入
DirectorySelectionWidgets.initialize()=>initializeUserControl()=>构造函数,结束
DirectorySelectionWidgets=>构造函数,输入预期初始化()在此之后调用
DirectorySelectionWidgetsTest=>开始(),结束

因此,在运行子类方法initialize()时,没有定义DirectorySelectionWidgets局部变量。

一种猜测是,这是FXML基础结构在子类层次结构中的顶级构造函数完成后立即跳到子类initialize()方法的结果吗?DirectorySelectionWidgetsTeststart()方法在这方面的作用我不清楚;下一次的研究。

任何进一步的见解都是非常受欢迎的,但意外的创业序列肯定在我的困惑中起到了一定作用。

最新更新