Custom Create in form - Fields don't get initialized when "Generate GUI into" is set to "Binary class files"
I want to create a form using the GUI Designer that contains an EditorTextField. To initialize this text field, I need to manually instantiate it and pass it the current project. According to the docs, this should be possible via the "Custom Create" checkbox and the createUIComponents() method, which is done like this in multiple places in IDEA itself. Since I need the current project for instantiation, I set it as a field when instantiating the class.
My code when using "Generate GUI into: Binary class files":
public class MyComponent {
private final Project project;
private JPanel panel;
private EditorTextField packageSelectField;
[other bound fields in the form]
public MyComponent(@NonNull Project project) {
this.project = project;
[code for setting up other form components]
}
private void createUIComponents() {
packageSelectField = new EditorTextField(JavaReferenceEditorUtil.createDocument(
"com.default.package", project, false), project, StdFileTypes.JAVA);
}
Unfortunately, the project field is null in createUIComponents() and thus the EditorTextField can't be instantiated. It seems like the method is somehow called before the constructor is called and thus the fields don't get initialized. Although I don't see how that is possible, given that I manually instantiate the MyComponent class. Now, if I set the "Generate GUI into" setting to "Java source code" and thus have IntelliJ generate the hidden setup methods, it works fine.
My code when using "Generate GUI into: Java source code":
public class MyComponent {
private final Project project;
private JPanel panel;
private EditorTextField packageSelectField;
[other bound fields in the form]
public MyComponent(@NonNull Project project) {
this.project = project;
$$$setupUI$$$();
[code for setting up other form components]
}
private void createUIComponents() {
packageSelectField = new EditorTextField(JavaReferenceEditorUtil.createDocument(
"com.default.package", project, false), project, StdFileTypes.JAVA);
}
/**
* Method generated by IntelliJ IDEA GUI Designer
* >>> IMPORTANT!! <<<
* DO NOT edit this method OR call it in your code!
*
* @noinspection ALL
*/
private void $$$setupUI$$$() {
createUIComponents();
panel = new JPanel();
...
}
...
}
As you can see, in the generated code the constructor calls $$$setupUI$$$() after assigning the field (as expected), which then calls createUIComponents(). I.e. if I let IntelliJ generate the GUI into Java sources, the field is assigned correctly, but if I have it generate to a Binary class file, the field is not assigned and remains null, thus being unusable in createUIComponents(). Am I doing something wrong or is this unintended behavior?
Please sign in to leave a comment.
Please sets "Binary class files" and:
-show full java code and .form file
-insert to `createUIComponents()`
and show full stack trace
-for .class file run `javap -c MyComponent` and show full listing
Here's the full code of the components involved (set to "Binary class files").
MyComponent.java:
MyComponent.form:
MyComponent is used as a page in my plugin's settings, which are tabbed. It is added here as follows:
SettingsTab interface:
Following is the output you requested:
Throwable stack trace: See on Hastebin
Output of javap: See on Hastebin
Hello,
In your bytecode I don't see $$$setupUI$$$() and other special methods.
What type of projects do you use? Idea plugin project?
How do you compile a project?
After turn to "Binary class files" try clear all classes, IDE caches and rebuild project.
After its, if steel not working please send idea.log and build.log
Hi,
the project is a standard Gradle project using the gradle-intellij-plugin. Here's my gradle.build:
I compile the project using the Gradle build task, which gets executed automatically as part of the runIde task.
build task:
runIde task:
Before executing the above tasks, I
Unfortunately, this does not make a difference. The hidden $$$ methods are not compiled into the class file.
The host IDE's idea.log and build.log
The sandbox IDE's idea.log (does not have a build.log)
Hi, please run gradle `clean instrumentCode --info` and attach the output.
Btw, how are you checking that $$$setupUI$$$ is not there? What's the class file you check?
> Btw, how are you checking that $$$setupUI$$$ is not there? What's the class file you check?
I'm checking the class file of MyComponent using both IntelliJ's decompiler and `javap` as suggested before.
> please run gradle `clean instrumentCode --info` and attach the output.
Here you go.
> I'm checking the class file of MyComponent
Which one? There are at least four of them in case of building the plugin.
According to logs the instrumentation succeed. Where is the form sources located?
> Which one? There are at least four of them in case of building the plugin.
Good to know, I just checked in build/classes/java/main/com/mycompany/plugin/modules/mymodule/settings.
I can only find one other class file for MyComponent in main-instrumented/... and it actually calls `$$$setupUI$$$()` as the first thing in its constructor. This is incorrect in this case, as the fields are assigned after that within Lombok's generated guard clause.
Looks like the problem is that it's not taking Lombok's @NonNull guard into account?
> Where is the form sources located?
Right next to the MyComponent.java file if that's what you mean.
Configuration looks good to me, don't see what can goes wrong. Could you provide a project sample to recreate this, please?
@Alexander, any ideas why compiler works like that?
---------------JAVA-------------------------
Hm,
Lombok generate code like:
if (FOO == null) { ....}
else {....}
and this broken our form byte code generator....
Please use
Thanks both of you for debugging this with me; I'll use @NotNull in this particular component for now.
However, I seem to have found another issue with this. When initializing an attribute in-line, for example via:
the `$$$setupUI$$$` method is also called at the wrong place.
Directly initializing a field such as above results in the following code being generated, i.e. the initialization is automatically moved into the constructor:
When the field is manually initialized within the constructor like this:
the code generation also doesn't work as expected when the field is required to be already initialized in `createUIComponents()`. Looks like only parameter-to-field assignments are prioritized higher than the `$$$setupUI$$$()` call:
This can be worked around by initializing the field in `createUIComponents()` instead of in-line or in the constructor.
BVollmerhaus, Please create issue in YouTrack with all corner cases.
Will do, but it may take quite a while until I get around to it.
So it is solved right now? I found the method
Chuanyi from docs https://www.jetbrains.com/help/idea/creating-form-initialization-code.html
Sometimes you might need to provide initialization code of your own. For example, you want a GUI component to be instantiated by a non-default constructor with certain parameters. In this case, IntelliJ IDEA will not generate instantiation of the component, and it is your responsibility to provide the call to constructor in the
createUIComponents()
method. Otherwise, a Null Pointer Exception will be reported. Follow the general technique described below.