问题 我想在运行时将通过javafx场景构建器构建的自定义面板添加到网格窗格中。我自定义的面板存在按钮,标签等。
我的尝试 我试图从窗格扩展......
public class Celli extends Pane{
public Celli() throws IOException{
Parent root = FXMLLoader.load(getClass().getResource("Cell.fxml"));
this.getChildren().add(root);
}
}
...然后在控制器的添加方法中使用此面板
@FXML
private void textChange(KeyEvent event) {
GridPane g = new GridPane();
for (int i=0 : i<100; i++){
g.getChildren().add(new Celli());
}
}
}
它有效,但表现非常差。
我在寻找什么 有没有办法通过javafx场景构建器设计面板(因此在fxml中有这个面板)然后在运行时将它添加到gridpane而不为每个实例使用这个fxmlloader。我认为它因fxml加载器而表现不佳。当我添加标准按钮时,例如whitout fxml它非常快。
答案 0 :(得分:21)
简答:不,它不是(截至JavaFX 2.x和8.0)。它可能是未来版本(JFX> 8)
答案很长: FXMLLoader目前不是作为模板提供程序执行的,它一次又一次地实例化同一个项目。相反,它意味着是一个大型GUI(或序列化它们)的一次性加载器。
性能很差,因为根据FXML文件,每次调用load()
时,FXMLLoader都必须通过反射查找类及其属性。这意味着:
对代码中完成的同一FXML文件的load()
的后续调用目前也没有任何改进。这意味着:不缓存已找到的类,不缓存BeanAdapter等等。
但是,通过将自定义类加载器设置为FXMLLoader实例,可以解决步骤1的性能问题:
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class MyClassLoader extends ClassLoader{
private final Map<String, Class> classes = new HashMap<String, Class>();
private final ClassLoader parent;
public MyClassLoader(ClassLoader parent) {
this.parent = parent;
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> c = findClass(name);
if ( c == null ) {
throw new ClassNotFoundException( name );
}
return c;
}
@Override
protected Class<?> findClass( String className ) throws ClassNotFoundException {
// System.out.print("try to load " + className);
if (classes.containsKey(className)) {
Class<?> result = classes.get(className);
return result;
} else {
try {
Class<?> result = parent.loadClass(className);
// System.out.println(" -> success!");
classes.put(className, result);
return result;
} catch (ClassNotFoundException ignore) {
// System.out.println();
classes.put(className, null);
return null;
}
}
}
// ========= delegating methods =============
@Override
public URL getResource( String name ) {
return parent.getResource(name);
}
@Override
public Enumeration<URL> getResources( String name ) throws IOException {
return parent.getResources(name);
}
@Override
public String toString() {
return parent.toString();
}
@Override
public void setDefaultAssertionStatus(boolean enabled) {
parent.setDefaultAssertionStatus(enabled);
}
@Override
public void setPackageAssertionStatus(String packageName, boolean enabled) {
parent.setPackageAssertionStatus(packageName, enabled);
}
@Override
public void setClassAssertionStatus(String className, boolean enabled) {
parent.setClassAssertionStatus(className, enabled);
}
@Override
public void clearAssertionStatus() {
parent.clearAssertionStatus();
}
}
用法:
public static ClassLoader cachingClassLoader = new MyClassLoader(FXMLLoader.getDefaultClassLoader());
FXMLLoader loader = new FXMLLoader(resource);
loader.setClassLoader(cachingClassLoader);
这显着加快了性能。但是,步骤2没有解决方法,因此这可能仍然是一个问题。
但是,官方JavaFX jira中已有功能请求。你很高兴支持这些要求。
链接:
FXMLLoader应该能够在load()调用之间缓存导入和属性:
https://bugs.openjdk.java.net/browse/JDK-8090848
将setAdapterFactory()添加到FXMLLoader:
https://bugs.openjdk.java.net/browse/JDK-8102624
答案 1 :(得分:2)
我遇到过类似的问题。我还必须动态地多次加载一个基于fxml的自定义组件,这花费的时间太长了。在我的例子中,FXMLLoader.load方法调用很昂贵。
我的方法是并行化组件实例化,它解决了问题。
考虑到问题上发布的示例,使用多线程方法的控制器方法将是:
private void textChange(KeyEvent event) {
GridPane g = new GridPane();
// creates a thread pool with 10 threads
ExecutorService threadPool = Executors.newFixedThreadPool(10);
final List<Celli> listOfComponents = Collections.synchronizedList(new ArrayList<Celli>(100));
for (int i = 0; i < 100; i++) {
// parallelizes component loading
threadPool.execute(new Runnable() {
@Override
public void run() {
listOfComponents.add(new Celli());
}
});
}
// waits until all threads completion
try {
threadPool.shutdown();
threadPool.awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// seems to be a improbable exception, but we have to deal with it
e.printStackTrace();
}
g.getChildren().addAll(listOfComponents);
}
答案 2 :(得分:0)
在@Sebastian先生的代码中添加“已经加载的类的缓存”代码。它对我有用。请建议更改以获得更好的性能。
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("In Class loader");
Class result;
System.out.println(" >>>>>> Load class : "+name);
result = (Class)classes.get(name);
if(result != null){
System.out.println(" >>>>>> returning cached class.");
return result;
}else{
Class<?> c = findClass(name);
if ( c == null ) {
throw new ClassNotFoundException( name );
}
System.out.println(" >>>>>> loading new class for first time only");
return c;
}
}