A lot of my GUI is described in FXML files, and for Buttons and other elements there's an option to set event handler directly from FXML, tied to the method in the controller, set in the same FXML file.
So if there's a class view.Controller and there's a method public void foo(), then it looks something like this:
<StackPane fx:controller="view.Controller" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Button onAction="#foo" />
</children>
</StackPane>
However, after obfuscation with ProGuard it becomes like this:
<StackPane fx:controller="b.D" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Button onAction="#foo" />
</children>
</StackPane>
The foo() in view.Controller gets obfuscated, but the method call in onAction does not. I'm using -adaptresourcefilecontents **.properties,META-INF/MANIFEST.MF,**.xml,**.css,**.fxml parameter for this. There's a similar bug where ProGuard does not obfuscate custom controls, which can be solved by removing the imports in FXML and writing fully qualified paths. This workaround can not be used in this situation, I think. What can be done about it except moving all of onAction from FXML to button.setOnAction() in Java (which is cumbersome and a lot of work)?
I'm using ProGuard 6.0
So in the end I wrote a tiny tool to unpack the jar from ProGuard, rename all onAction calls according to the provided mapping and pack it back to jar again.
This is the tool in case anyone needs it (though it's mostly combined from answers on SO and some regex, nothing fancy).
My solution is to create an annotation named Keep:
#Retention(CLASS)
#Target({PACKAGE, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, METHOD, FIELD})
public #interface Keep {
}
Annotate all the methods to keep with #Keep.
#Keep
public void onStatsTabSelected(MouseEvent actionEvent) {
}
and update the progaurd.conf to keep anything Annotated with #Keep:
-keepclassmembers class * {
#android.widget.Keep *;
}
Related
JDK 11, JavaFX 15.
Despite the wonderful answer How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application? this is still stymieing me. It does not address loading related resources.
I have a simple example loading an FXML file that refers to a CSS file.
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0"
styleClass="mainFxmlClass" xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/11.0.1">
<stylesheets>
<URL value="#/styles/root.css" />
</stylesheets>
</AnchorPane>
And this is the pkg/App.java class:
package pkg;
public class App extends Application {
private static Scene scene;
#Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(App.class.getResource("root.fxml"));
scene = new Scene(loader.load());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
This is the root.css file:
.mainFxmlClass {
}
Here is the jar layout (eliminating maven boiler plate, module info, manifest).
pkg/App.class
pkg/root.fxml
pkg/styles/root.css
styles/root.css
The relevant exception I get is:
Caused by: javafx.fxml.LoadException: Invalid resource: /styles/root.css not found on the classpath
According to Introduction to FXML, under Location Resolution, it says:
As strings, XML attributes cannot natively represent typed location
information such as a URL. However, it is often necessary to specify
such locations in markup; for example, the source of an image
resource. The location resolution operator (represented by an "#"
prefix to the attribute value) is used to specify that an attribute
value should be treated as a location relative to the current file
rather than a simple string.
The exception specifies "/styles/root.css", which is an absolute path. As seen from the JAR layout, I have a /styles/root.css. But the documentation says "relative to the current file".
Assuming the "current file" is /pkg/root.fxml, then /pkg/styles/root.css is (should be) relative to /pkg/root.fxml (or is it /pkg/App.class?), yet it can not locate this one either.
If I comment out the stylesheets element, the file loads just fine.
So, where should I place the root.css file?
To reference a CSS file in a FXML file you can either use:
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0"
styleClass="mainFxmlClass" xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/11.0.1">
<stylesheets>
<String fx:value="/styles/root.css" />
</stylesheets>
</AnchorPane>
Note:
String not URL
fx:value not value
without '#' at the beginning
Seems only to support absolute paths
or
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0"
styleClass="mainFxmlClass" xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/11.0.1"
stylesheets="#/styles/root.css" />
</AnchorPane>
Note:
attribute not nested element
comma separated list of stylesheets
stylesheet URLs must start with '#' here
supports absolute and relative URLs
The next thing is to configure the correct location, which already should be set by the constructor you're using and thus should be fine.
You can find a full sample here.
The sample uses the convention over configuration approach supported by the FXMLLoaders utility class of Drombler Commons.
With your sample this could look something like the following if you rename root.fxml to App.fxml:
scene = new Scene(FXMLLoaders.load(App.class));
It will also look for a App.properties file (and locale specific derivations ; must be in the same package) which could be useful if you have i18n text in your FXML file.
Additionally it will set the correct ClassLoader and location and throws RuntimeExceptions rather than checked exceptions since the user cannot change anything anyway.
You can use this utility class by adding the following dependency:
<dependency>
<groupId>org.drombler.commons</groupId>
<artifactId>drombler-commons-fx-core</artifactId>
<version>1.0</version>
</dependency>
The library is Open Source.
Hello,when I study JavaFX in "Using FXML to Create a User Interface" https://docs.oracle.com/javase/8/javafx/get-started-tutorial/fxml_tutorial.htm
I want to write a demo like a simple calculator.
when I use the GridPane layout, it can not have an error,
but when I use the Pane layout, it showed the error "No Controller specified for top-level element" in the sample.fxml.
I do not understand it
I tried another way that I did not use FXML, wrote code in the Main.java like this
if you use action in fxml you will need to specify what class will be used to find this method, usually by attribute in root element like this
<GridPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
more about https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html#controller_method_event_handlers
or can use the method in FXMLoader when you have to use special instance
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
loader.setController(new Controller());
Parent root = loader.load();
I'm recently writing a javafx application and in one of its parts the client must wait for the server to get a list of people and after getting the list it must be used in a listview that is going to be added to a parent. That parent is and fxml file and after loading it I want to know if it is possible to add the vbox containing the listview to the parent or not. I'd be grateful if anyone could help...
From your question I gather you are not familiar with the idea of a controller or the associated FXML injection performed by a FXMLLoader. This answer by James_D goes over the very basics of the JavaFX lifecycle but it first goes over the basics of the procedure involved when loading a FXML file. If you want to modify the scene-graph that is loaded via FXML then you need to use a controller class with the appropriate FXML annotated fields. For example, let's say your parent is a BorderPane. In your FXML file you'd have:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane fx:id="parent" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/10.0.1"
fx:controller="some.package.YourController">
<top>
<!-- maybe have something like a MenuBar here -->
</top>
<bottom>
<!-- maybe have a some type of status bar here -->
</bottom>
</BorderPane>
Notice the fx:controller attribute; it is the class name of the class to instantiate and use as a controller. Also note the fx:id attribute. In your controller class you'd have:
package some.package;
import javafx.scene.layout.BorderPane;
import javafx.fxml.FXML;
public class YourController {
#FXML
private BorderPane parent; // field name matches the fx:id attribute
#FXML
private void initialize() {
// do any initializing if needed (if not, get rid of this method)
// you can access and modify any FXML injected field from this method
}
}
Then you can use the parent via the controller instance. You could also create and link event handler methods in the controller and do certain behavior based on user actions. It's important to note, however, that how you change the state of the UI in the controllers depends on how you access you model classes. You'll need to have the model available to your controller and possibly have it shared between mutliple controllers. There are a decent number of questions/answers on Stack Overflow about how to do this already.
Here is another resource that may help you: Introduction to FXML.
How to get elements or nodes from FXML file using Java, I know the way by using initialization or by setting controller class in FxmL . But I need to do it without any controller. I want to access the nodes inside the fxml file using.
My FXML COde:
HBox fx:id="hbx" id="hbx" alignment="CENTER_RIGHT" prefHeight="100.0"
prefWidth="200.0" BorderPane.alignment="CENTER"
My java Code
System.out.println(par.lookupAll("hbx"));
See my Code above, could you give me a hint?
After loading the FXML file, you can use Node#lookup():
Node node = fxmlParentPane.lookup("#nodeId");
What are the pros and cons of using FXMLs or not using FXMLs for developing JavaFX applications?
For developing enterprise JavaFX application which approach should one follow?
FXML Cons: It takes slightly longer to load and display.
FXML Pros:
Rapid scene development / mock up using Scene Builder.
FXML is not a compiled language; you do not need to recompile the code to see the changes. Just reload the FXML file.
It provides a clear separation of GUI from logic/controller.
So you can have different versions of a scene/view using the same controller. This is handy for demo's for instance.
The content of an FXML file can be localized as the file is read.
Definitely use FXML in enterprise apps !
I would add two contra to Jurgens list.
If you are working with FXML instantiation of your view is sort of inconvenient. At least from my point of view.
Node explorer = new MyExplorerWidget();
or
Node explorer = cdicontainer.newInstance(MyExplorerWidget.class);
is more pleasant than
FXMLLoader loader = new FXMLLoader(getClass().getResource("com.mycompany.some.very.long.name.MyExplorerWidget.fxml"),explorerwidgetresouces);//Of course we want our app internationalized
Node explorer = loader.load();
Another point is that FXML is static. If you want to generate your UI at run time depending on some model you will write UI code anyway. I ended up with useless fxml files like this PropertyGrid.fxml
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="PropertyGridController">
<children>
<VBox fx:id="vbox" layoutX="63.0" layoutY="-28.0" prefHeight="172.0" prefWidth="163.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
And the PropertyGridController.
public class PropertyGridController{
#FXML
VBox vbox;
....
public void setModel(PropertySheet model){
//.... tons of code to generate the actual property grid and add it to the view
}
}