我正在使用FXML用Java和JavaFX编写模块化声卡。其中一个关键方面是能够在运行时加载实现插件抽象类的任意类。插件抽象类如下所示:
import javafx.scene.control.Tab;
import java.io.File;
import java.io.FileReader;
import java.net.URLClassLoader;
import java.util.Properties;
public abstract class Plugin {
protected Tab mainTab;
protected Tab settingsTab;
protected Properties propertyFile = new Properties();
protected File fileName;
protected URLClassLoader loader;
public Plugin(){
mainTab = new Tab();
}
public Plugin(String tabName){
mainTab = new Tab(tabName);
settingsTab = new Tab(tabName);
}
public abstract void save();
public Tab getMainTab(){
return mainTab;
}
public Tab getSettingsTab(){
return settingsTab;
}
public void initPropertyFile(String filename){
try{
File file = new File(System.getenv("APPDATA")+"\Testing\"+filename+".properties");
if(file.exists())
propertyFile.load(new FileReader(file));
else{
file.createNewFile();
propertyFile.load(new FileReader(file));
}
}catch(Exception e){
e.printStackTrace();
}
}
public void setClassLoader(URLClassLoader loader){
this.loader = loader;
}
public abstract void initController();
public Properties getPropertyFile(){
return propertyFile;
}
}
我使用URLClassLoader加载在指定JAR文件中找到的任何类,然后实例化Plugin的任何实例并将它们收集在列表中。我遇到的问题是创建一个FXML控制器,用于我在运行时实例化的这些类。因为这些类和控制器都是在运行时使用自定义URLClassLoader加载的,所以我无法在我的FXML文件中指定一个FXML控制器。如果我尝试,我会得到一个错误,说找不到我用于控制器的类。如果我没有在FXML文件中指定控制器,它可以加载UI组件,但我不能在其中指定任何操作。我可以使用FXMLLoader.setController((方法设置控制器,但当我加载这样的控制器时,我使用的按钮都没有任何操作。我尝试过将initialize((方法与FXML注释一起使用,但这个方法不是由FXMLLoader自动调用的。如果我自己调用initialize,那么FXML似乎还没有被注入,但FXML文件指定的UI组件以FXML文件所指定的格式出现在我的屏幕上。
import java.net.URL;
public class Plugin3 extends Plugin {
FXMLLoader mainTabLoader;
URL testURL;
public Plugin3(){
//Calls Plugin Constructor to initialize mainTab and settingsTab, and set the UI names of the tabs
super("Plugin3");
//Try creating FXMLLoader to setup the UI of the plugin
try {
mainTabLoader = new FXMLLoader();
//mainTabLoader.setController(this);
Plugin3 thisObj = mainTabLoader.getController();
testURL = getClass().getResource("testFXML.fxml");
//Load the FXML file
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void save() {
}
//Attempts to set the controller of the FXMLLoader mainTabLoader and call the initialize method of that controller
@Override
public void initController() {
try {
String classPath = "com.example.plugindevelopment.Plugin3Controller";
mainTabLoader.setController(Class.forName(classPath, true, loader).getConstructor().newInstance());
getMainTab().setContent(mainTabLoader.load(testURL));
mainTabLoader.getController().getClass().getDeclaredMethod("initialize").invoke(mainTabLoader.getController());
}catch(Exception e){
e.printStackTrace();
}
}
}
这是控制器:
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
public class Plugin3Controller
{
@FXML
VBox root;
@FXML
Label label;
@FXML
Slider slider;
@FXML
Button button1;
protected void onButtonClick(){
System.out.println("Hello");
}
@FXML
public void initialize(){
System.out.println("Initializing Plugin3Controller");
button1 = new Button("Hello");
button1.setOnAction(event -> onButtonClick());
}
}
这是FXML文件:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<children>
<VBox fx:id="root" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="label" prefHeight="137.2" text="text" />
<Slider fx:id="slider">
<VBox.margin>
<Insets top="100.0" />
</VBox.margin>
<padding>
<Insets top="50.0" />
</padding></Slider>
<Button fx:id="button1" mnemonicParsing="false" text="Click Me" />
</children></VBox>
</children>
</AnchorPane>
我将感谢任何人能提供的任何见解,谢谢。
因为这些类和控制器都是在运行时使用自定义URLClassLoader加载的,所以我无法在我的FXML文件中指定一个FXML控制器。如果我尝试,我会得到一个错误,说找不到我用于控制器的类。
请注意,您可以将类加载器设置为由FXMLLoader
使用。
您尝试手动设置控制器的解决方案中存在许多错误。最重要的是:
String classPath = "com.example.plugindevelopment.Plugin3Controller";
mainTabLoader.setController(Class.forName(classPath, true, loader).getConstructor().newInstance());
getMainTab().setContent(mainTabLoader.load(testURL));
这里mainTabLoader.load(testURL)
调用静态方法FXMLLoader.load(URL)
。由于这是一个静态方法,因此它根本不引用FXMLLoader
实例mainTabLoader
,特别是不会知道控制器实例。
你应该使用
String classPath = "com.example.plugindevelopment.Plugin3Controller";
mainTabLoader.setController(Class.forName(classPath, true, loader).getConstructor().newInstance());
mainTabLoader.setLocation(testURL);
getMainTab().setContent(mainTabLoader.load());
请注意,对load()
的调用没有参数,调用实例方法FXMLLoader.load()
。
一旦FXMLLoader
正确地引用了一个控制器实例,它就会自动调用initialize()
。你应该删除线路
mainTabLoader.getController().getClass().getDeclaredMethod("initialize").invoke(mainTabLoader.getController());
您的控制器实现中也有一个错误(我猜这可能是为了解决上述错误引起的问题而进行的绝望尝试(。
实例化对象并将其分配给@FXML
注释字段(就像使用一样(总是错误的
button1 = new Button("Hello");
拆下此管路。它导致事件处理程序设置在未显示在UI中的按钮上,而不是在FXML文件中声明的按钮上。
总之,你应该有
public class Plugin3Controller
{
@FXML
VBox root;
@FXML
Label label;
@FXML
Slider slider;
@FXML
Button button1;
protected void onButtonClick(){
System.out.println("Hello");
}
@FXML
public void initialize(){
System.out.println("Initializing Plugin3Controller");
button1.setOnAction(event -> onButtonClick());
}
}
和
public class Plugin3 extends Plugin {
FXMLLoader mainTabLoader;
URL testURL;
public Plugin3(){
//Calls Plugin Constructor to initialize mainTab and settingsTab, and set the UI names of the tabs
super("Plugin3");
//Try creating FXMLLoader to setup the UI of the plugin
mainTabLoader = new FXMLLoader();
//mainTabLoader.setController(this);
// This line is patently nonsense: remove it:
// Plugin3 thisObj = mainTabLoader.getController();
testURL = getClass().getResource("testFXML.fxml");
//Load the FXML file
}
@Override
public void save() {
}
//Attempts to set the controller of the FXMLLoader mainTabLoader and call the initialize method of that controller
@Override
public void initController() {
try {
String classPath = "com.example.plugindevelopment.Plugin3Controller";
mainTabLoader.setController(Class.forName(classPath, true, loader).getConstructor().newInstance());
mailTabLoader.setLocation(testURL);
getMainTab().setContent(mainTabLoader.load());
}catch(Exception e){
e.printStackTrace();
}
}
}