#131 closed defect (worksforme)
Preview problems with nested classes
| Reported by: | Bill van Melle <billvm@…> | Owned by: | tom |
|---|---|---|---|
| Priority: | major | Milestone: | 0.0.14 |
| Component: | tooling-fxgraph | Version: | 0.0.13 |
| Keywords: | Cc: |
Description
Background: My standard practice when building a complex user control Foo is to define a class Foo, in such a way that Foo is both an ordinary control to the rest of the world and also the controller object of Foo.fxml, which is loaded by Foo's constructor. This was impossible in JavaFX 2.0, but doable in 2.1. (This is basically the WPF way of doing things.)
Bug: when one of these fxml-defined controls is in turn used by another fxgraph/fxml file, and the subcontrol in turn contains another user-defined control (which need not use fxml) the preview of the parent control messes up in various ways. Here's the structure of my test case:
Widget -> SubWidget -> UserControl
Widget is defined by an fxgraph file, and it is the preview that fails. Widget contains SubWidget as a subcomponent. SubWidget loads SubWidget.fxml in its constructor. SubWidget.fxml contains UserControl as a subcomponent, which gets assigned to a local @FXML field in SubWidget.
The main failure modes seem to be:
(1) The place in the preview where UserControl should appear is blank; or
(2) The preview fails completely with an IllegalArgumentException, apparently in the place where the fxml loader tries to assign UserControl to a field in SubWidget.
My experience with the attached test case is that the very first time I preview it in an Eclipse session, it previews correctly. The next time I try to preview it, even if I have changed no code, it fails (stack trace below). Restarting Eclipse then lets me preview it exactly once before it fails again.
In coming up with the test case, I originally had failure mode (1) more often. As a further clue in that case, it appeared that the SubWidget constructor never execute the code that followed the loading of its fxml, so maybe the loading failed quietly?
Stack trace:
java.lang.IllegalArgumentException: Can not set efxtest.UserControl field efxtest.SubWidget.subControl to efxtest.UserControl at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source) at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source) at sun.reflect.UnsafeObjectFieldAccessorImpl.set(Unknown Source) at java.lang.reflect.Field.set(Unknown Source) at javafx.fxml.FXMLLoader$ValueElement.processValue(FXMLLoader.java:675) at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:575) at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2137) at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2027) at javafx.fxml.FXMLLoader.load(FXMLLoader.java:1900) at efxtest.FXMLControl.loadFXML(FXMLControl.java:89) at efxtest.FXMLControl.loadMyFXML(FXMLControl.java:50) at efxtest.FXMLControl.loadMyFXML(FXMLControl.java:41) at efxtest.SubWidget.<init>(SubWidget.java:14) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) at java.lang.reflect.Constructor.newInstance(Unknown Source) at java.lang.Class.newInstance0(Unknown Source) at java.lang.Class.newInstance(Unknown Source) at javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:810) at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:570) at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2137) at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2027) at at.bestsolution.efxclipse.tooling.fxgraph.ui.preview.LivePreviewPart$8.run(LivePreviewPart.java:419) at com.sun.javafx.application.PlatformImpl$3.run(PlatformImpl.java:141) at org.eclipse.swt.internal.win32.OS.DispatchMessageW(Native Method) at org.eclipse.swt.internal.win32.OS.DispatchMessage(OS.java:2545) at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3752) at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$9.run(PartRenderingEngine.java:1015) at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332) at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:909) at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:85) at org.eclipse.ui.internal.Workbench$4.run(Workbench.java:580) at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332) at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:535) at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:149) at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:124) at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196) at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110) at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79) at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:353) at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:180) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:629) at org.eclipse.equinox.launcher.Main.basicRun(Main.java:584) at org.eclipse.equinox.launcher.Main.run(Main.java:1438) at org.eclipse.equinox.launcher.Main.main(Main.java:1414)
Attachments (1)
Change History (4)
Changed 12 months ago by Bill van Melle <billvm@…>
comment:1 Changed 12 months ago by tom
- Milestone set to 0.0.14
- Resolution set to worksforme
- Status changed from new to closed
This is not a problem of e(fx)clipse preview but that you are creating your custom FXMLLoader but to make things work you need to pass on the correct classloader!
Change your code like this:
public static Pane loadFXML(final String resourceName, final ResourceBundle? resources, final Object controller) {
URL resource = controller.getClass().getResource(resourceName);
if (resource == null)
return null;
FXMLLoader loader = new FXMLLoader(resource);
loader.setClassLoader(controller.getClass().getClassLoader());
if (resources != null)
loader.setResources(resources);
loader.setControllerFactory(new Callback<Class<?>, Object>() {
@Override
public Object call(Class<?> param) {
return controller;
}
});
try {
return (Pane) loader.load();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
and things start to work magically :-) I'm closing this as works for me if you think there's something e(fx)clipse could do to make this work better reopen with suggestions
comment:2 Changed 12 months ago by Bill van Melle <billvm@…>
Thanks! I've been using this base class for a couple months with no trouble at run time. I had no idea the class loader thing was important. I take it the default class loader inside the Eclipse environment gets something wrong? I guess I should read up on class loaders.
comment:3 Changed 12 months ago by tom
The problem is that the classloader FXML uses by default is the classloader of the Eclipse-IDE itself which has no idea about the class-files in your project.
The preview constructs a classloader by inspecting your project and sets this special one so that you can reference classes which are in your project (and its classpath).
The problem you see here is a very common one when it comes to IDE vs Project-Runtime-Classpath - the Preview runs in the classpath of the IDE (which has the whole IDE stuff and JavaFX), the project you are currently developing in with its ever changing classpath.

test case