Images disappear when minimize windows into a JavaFX app - java

I'm fixing a JavaFX application on JDK 8. The app shows multiple images using something like "jLabel.setIcon(new Image(...))". I know their is a component to show images in JavaFX but it is not the issue (because I'm not allowed to modify neither that part from the source nor the FXML files).
My problem is when minimize or change the window ((using ALT+TAB)) and wait some time (even up 1 hour approx) to come back to the window, this doesn't show the images anymore. However, if I move the window, the images come back.
In an attempt to fix the problem (independently of use the minimize button or ALT+TAB), I added in the final of the init() method this piece of code:
public void init() {
...
stage.focusedProperty().addListener((observable, oldvalue, newvalue) -> {
if (newvalue) repaintImages();
});
}
private void repaintImages() {
Object obj = new Object();
synchronized (obj) {
jLabel.repaint();
jLabel.revalidate();
obj.notify();
}
try {
obj.wait();
} catch (Exception e) {
}
}
The code before runs repaintImages() after the windows is focused. I used synchronized with the idea to be ensured the code is executed in thread-safe (independently of lag), Anyway that doesn't fix the problem (images are missing yet). After that, I change this the code to use runLater():
public void init() {
...
stage.focusedProperty().addListener((observable, oldvalue, newvalue) -> {
if (newvalue) Platform.runLater(()->{repaintImages();});
});
}
private void repaintImages() {
Platform.runLater(() -> {
jLabel.repaint();
jLabel.revalidate();
});
}
And it doesn't work either. Please, any suggestion is welcome.
I know, I wrote a long question, but maybe a suggestion could be to move the window when it is focused. In that case, please give the correct way to do this because I tried and not worked too (using synchronized and runLater()). This is the piece of code I used to move the window (placing this into a synchronized block or in the lambda function in runLater()):
Translate elements = null;
if (positive_flag) {
elements = new Translate(1, 0, 0);
positive_flag = false;
} else {
elements = new Translate(-1, 0, 0);
positive_flag = true;
}
anchorPane.getTransforms().addAll(elements);
Finally, consider that to prove if any solution works, I need to wait even up 1h.

Related

Losing AWT DrawingSurface when Canvas is hidden

I am writing 3d rendering module for an AWT/Swing application.
To provide good FPS, I can't draw using Swing/AWT methods and graphics. Instead, I obtain the Drawing Surface from the Canvas element, and then render directly to it. Something like this:
public class Window {
private Component canvas;
private JAWTDrawingSurface ds;
public static final JAWT awt;
static {
awt = JAWT.calloc();
awt.version(JAWT_VERSION_1_4);
if (!JAWT_GetAWT(awt))
throw new AssertionError("GetAWT failed");
}
public void lock() throws AWTException {
int lock = JAWT_DrawingSurface_Lock(ds, ds.Lock());
if ((lock & JAWT_LOCK_ERROR) != 0)
throw new AWTException("JAWT_DrawingSurface_Lock() failed");
}
public void unlock() throws AWTException {
JAWT_DrawingSurface_Unlock(ds, ds.Unlock());
}
public void Init2()
{
this.ds = JAWT_GetDrawingSurface(canvas, awt.GetDrawingSurface());
try
{
lock();
// Create GL Capabilities
unlock();
}
}
It works fine when I call it the first time.
But when I hide the canvas for any reason (for example minimizing window or displaying another panel instead of Canvas), the ds variable remains the same, but it doesn't work after that.
Basically, even if I make sure I call the variable only when it is visible and on top - any call using ds will throw an exception. For example lock() function stops working.
I'm wondering why's that?
Also I tried to basically obtain a new DS if I minimize and then maximize the window again, but this also doesn't work - the new DS address is returned as it should, but I can't use that new object just as I couldn't use the original one.
There's probably something stupid I'm missing here, but I can't figure out what.
Please help me sort this out. Thank you!
The solution:
When the Canvas is hidden, call eglMakeCurrent(eglDisplay,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT) to unbind the current context.
When you need to start drawing again, you need to do something like this:
public void Reinit()
{
System.err.println("Context Reinit()");
this.ds = JAWT_GetDrawingSurface(canvas, awt.GetDrawingSurface());
try
{
lock();
try
{
JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo());
JAWTX11DrawingSurfaceInfo dsiWin = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo());
this.display = dsiWin.display();
this.drawable = dsiWin.drawable();
eglDisplay = eglGetDisplay(display);
surface = eglCreateWindowSurface(eglDisplay,fbConfigs.get(0),drawable,(int[])null);
eglMakeCurrent(eglDisplay,surface,surface,context);
GLES.setCapabilities(glesCaps);
JAWT_DrawingSurface_FreeDrawingSurfaceInfo(dsi, ds.FreeDrawingSurfaceInfo());
}
finally
{
unlock();
System.err.printf("Unlock \n");
}
}
catch (Exception e)
{
System.err.println("JAWT Failed" + e.getMessage());
}
}
As You can see, I re-create the display and surface, but I use the previously created context for rendering, without needing to re-create it.

JavaFX Concurrency - Using a task, which runs in a thread, but hangs the UI

There are a lot of questions and answer around concurrency, and mine could be similar to others, but for me it's not a duplicate as for some reason I must be missing something and hope to get some advice...
My question is more one where I need a second pair of eyes to point out what I'm doing incorrectly to enable my code to run in a background thread, but also updated the GUI, without freezing it.
Initially, a PDF file is uploaded to the application, using a task in a thread.
This works fine.
A progress bar is displayed, which animates without issue:
uploadFile()
public void uploadFile(File fileToProcess) {
fileBeingProcessed = fileToProcess;
Task<Parent> uploadingFileTask = new Task<Parent>() {
#Override
public Parent call() {
try {
progressBarStackPane.setVisible(true);
pdfPath = loadPDF(fileBeingProcessed.getAbsolutePath());
createPDFViewer();
openDocument();
} catch (IOException ex) {
java.util.logging.Logger.getLogger(MainSceneController.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
};
uploadingFileTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
fileHasBeenUploaded = true;
progressBarStackPane.setVisible(false);
uploadFilePane.setVisible(false);
tabPane.setVisible(true);
/* This is where I am getting issue, more so in createThumbnailPanels() */
setupThumbnailFlowPane();
createThumbnailPanels();
/****** ^^^^^^^^^^^^ ******/
}
});
uploadingFileTask.setOnFailed(evt -> {
uploadingFileTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(uploadingFileTask.getException().getSuppressed()));
});
Thread uploadingFileThread = new Thread(uploadingFileTask);
uploadingFileThread.start();
}
Once the document has been uploaded, it is displayed in a tab which allows the user to view the document.
There is a secondary tab, which, after upload, is disabled, until the completion of another task called createThumbnailPanelsTask;
However, before this task is ran, the FlowPane for the Thumbnail Panels is created. This seems to work without issue, and doesn't appear to be the cause of the GUI hanging (this is clearly a loop in createThumbnailPanelsTask, but for clarity I will show setupThumbnailFlowPane()):
setupThumbnailFlowPane()
public void setupThumbnailFlowPane() {
stage = model.getStage();
root = model.getRoot();
secondaryTabScrollPane.setFitToWidth(true);
secondaryTabScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
/**
This will be removed from here when refactored but for now it is here,
I don't think this is anything to do with my issue
**/
Set<Node> nodes = secondaryTabScrollPane.lookupAll(".scroll-bar");
for (final Node node : nodes) {
if (node instanceof ScrollBar) {
ScrollBar sb = (ScrollBar) node;
if (sb.getOrientation() == Orientation.VERTICAL) {
sb.setUnitIncrement(30.0);
}
if (sb.getOrientation() == Orientation.HORIZONTAL) {
sb.setVisible(false);
}
}
}
secondaryTab = new FlowPane();
secondaryTab.setId("secondaryTab");
secondaryTab.setBackground(new Background(new BackgroundFill(Color.LIGHTSLATEGRAY, new CornerRadii(0), new Insets(0))));
secondaryTab.prefWidthProperty().bind(stage.widthProperty());
secondaryTab.prefHeightProperty().bind(stage.heightProperty());
secondaryTab.setPrefWrapLength(stage.widthProperty().intValue() - 150);
secondaryTab.setHgap(5);
secondaryTab.setVgap(30);
secondaryTab.setBorder(new Border(new BorderStroke(Color.TRANSPARENT, BorderStrokeStyle.NONE, CornerRadii.EMPTY, new BorderWidths(8, 10, 20, 10))));
secondaryTab.setAlignment(Pos.CENTER);
}
Finally, createThumbnailPanels() is called, which is where I believe I am getting the problem.
What is suppose to happen is, after the document has uploaded, the upload file pane is hidden, revealing the Viewer Tab, and also the Secondary Tab.
The secondary tab is disabled at this point, and also has a loading image (a gif) on the left side of it.
The intended behaviour, is that the createThumbnailPanels() task will run in the background, and until it is complete, the tab will remain disabled, however, during this time, the gif image will be rotating, giving the impression there is some loading occurring.
Once the loading has completed, the gif is removed, and the tab is enabled, allowing the user to navigate to it, and see the generated thumbnail panels.
This all works, however, as mentioned, the task is hanging the GUI:
createThumbnailPanels()
public void createThumbnailPanels() {
Task<Void> createThumbnailPanelsTask = new Task<Void>() {
#Override
public Void call() {
if (model.getIcePdfDoc() != null) {
numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
for (int thumbIndex = 0; thumbIndex < numberOfPagesInDocument; thumbIndex++) {
ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
Thumbnail tn = new Thumbnail(tb);
model.setThumbnailAt(tn, thumbIndex);
eventHandlers.setMouseEventsForThumbnails(tb);
/*
I have added this in as I am under the impression that a task runs in a background thread,
and then to update the GUI, I need to call this:
*/
Platform.runLater(() -> {
secondaryTab.getChildren().add(tb);
});
}
}
return null;
}
};
createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
/*
Further GUI modification run in setOnSucceeded so it runs on main GUI thread(?)
*/
secondaryTabScrollPane.setContent(secondaryTab);
secondaryTab.setDisable(false);
secondaryTab.setGraphic(null);
}
});
createThumbnailPanelsTask.setOnFailed(evt -> {
createThumbnailPanelsTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
});
Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
createThumbnailPanelsThread.start();
}
Everything, bar the GUI hanging while it creates the panels, works fine.
Once they've been created, the GUI can be controlled again, the loading gif has been removed, the tab is enabled and the user can navigate to it and view the panels.
Clearly, there is something I am missing about concurrency here.
As mentioned, I was under the impression that a Task runs in a background thread, so I'm a little confused by why it doesn't appear to be doing this. Again, clearly something I am missing.
I have read, and read, and read about concurrency, but just can't seem to work out where in my approach I have gone wrong. I am tempted to try using a Service, however, I feel that I am just over complicating things by considering that, and that there is clearly a simply way to do what I want to achieve.
Any help will be greatly appreciated... a push in the right direction, or some clarification on where I have gone wrong in my understanding.
Thanks in advance, no doubt it's something obvious that once sorted will help me avoid this issue in future!
UPDATED CODE
createThumbnailPanels()
public void createThumbnailPanels() {
Task<Void> createThumbnailPanelsTask = new Task<Void>() {
//TODO: Need to check that it's a PDF
#Override
public Void call() {
if (model.getIcePdfDoc() != null) {
numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
for (int thumbIndex= 0; thumbIndex< numberOfPagesInDocument; thumbIndex++) {
ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
Thumbnail tn = new Thumbnail(tb);
eventHandlers.setMouseEventsForThumbnails(tb);
model.setThumbnailAt(tn, thumbIndex);
model.setThumbnailPanels(tb);
}
setThumbnailPanelsToScrollPane();
}
return null;
}
};
createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
// setThumbnailPanelsToScrollPane();
}
});
createThumbnailPanelsTask.setOnFailed(evt -> {
createThumbnailPanelsTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
});
Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
createThumbnailPanelsThread.start();
}
setThumbnailPanelsToScrollPane()
public void setThumbnailPanelsToScrollPane() {
Task<Void> setThumbnailPanelsToScrollPaneTask = new Task<Void>() {
//TODO: Need to check that it's a PDF
#Override
public Void call() {
Platform.runLater(() -> {
secondaryTab.getChildren().addAll(model.getThumbnailPanels());
secondaryTabScrollPane.setContent(main.informationExtractionPanel);
secondaryTab.setDisable(false);
secondaryTab.setGraphic(null);
});
return null;
}
};
setThumbnailPanelsToScrollPaneTask.setOnFailed(evt -> {
setThumbnailPanelsToScrollPaneTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(setThumbnailPanelsToScrollPaneTask.getException().getSuppressed()));
});
Thread setThumbnailPanelsToScrollPaneThread = new Thread(setThumbnailPanelsToScrollPaneTask);
setThumbnailPanelsToScrollPaneThread.start();
}
FYI: If I call setThumbnailPanelsToScrollPane(); in the setOnSucceeded, it doesn't appear to work.
getChildren().add is running on the JavaFX GUI thread(thats what Platform.runLater does), but its only required to run it in a Platform.runLater if the parent that you add children to is connected to the root of the shown gui, that means you should be able to add children to a parent that is not connected to any root, and add the whole parent to the root at the end of the children addition process, if you're doing Platform.runLater in any asynchronous code it will run on the gui thread in your case it is in your asynchronous for loop adding ThumbnailPanels and if the number of them is large the gui will hang.

Codename one new gui builder-back command from EVERY form navigation

I am navigating between forms in the NEW GUI builder. The old one had a back button on every form by default.
How do I enable the back button on new gui builder in every form, every time i navigate in a new form? Tried through constants in theme.res. It is still not enabled by default.
Furthermore, is the method "new form1.show" the best way to navigate between forms ? (see code)
Assuming name files:
Main.java, myapplication.java, Form1 ,Form2 ,Form3
Code for navigation, assuming names button1 and Form3:
public void onbutton1ActionEvent(com.codename1.ui.events.ActionEvent ev) {
new Form3().show();
}
Back command from old gui builder, not working here:
public Form showForm(String resourceName, Command sourceCommand) {
try {
Form f = (Form)formNameToClassHashMap.get(resourceName).newInstance();
Form current = Display.getInstance().getCurrent();
if(current != null && isBackCommandEnabled() && allowBackTo(resourceName)) {
f.putClientProperty("previousForm", current);
setBackCommand(f, new Command(getBackCommandText(current.getTitle())) {
public void actionPerformed(ActionEvent evt) {
back(null);
}
});
}
if(sourceCommand != null && current != null && current.getBackCommand() == sourceCommand) {
f.showBack();
} else {
f.show();
}
return f;
} catch(Exception err) {
err.printStackTrace();
throw new RuntimeException("Form not found: " + resourceName);
}
}
I've tried:
form.setBackCommand(cmd);
public Command setBackCommand(String title, ActionListener<ActionEvent> listener)
public void setBackCommand(Command cmd)
public Command setBackCommand(String title, BackCommandPolicy policy, ActionListener<ActionEvent> listener)
public void setBackCommand(Command cmd, BackCommandPolicy policy)
boolean onBack() {
return true;
}
https://www.codenameone.com/blog/toolbar-back-easier-material-icons.html
on main.java and myapplication.java did not accept the commands.
Form3.getToolbar().setBackCommand("", e -> Form3.showBack());
althouth is should not work only for form3, but every form.
Did not work either. Putting "back command" on every sidemenu would not be the ideal solution, because we might be navigating to each form from different forms.
EXTRA:
Is there a way to enable global toolbar and global commands for all forms, so i do not copy paste the toolbar code for each new form? If not answered here, i might make a new thread.
Thanks.
The old GUI builder handled navigation as it was designed at a time when Nokia was the worlds leader in the mobile phone industry and a 4in device was considered large. Back then we assumed the UI was simpler for each form and the navigation was the hard part.
This changed. But the bigger problem for most developers was the concept of stateless navigation which triggered a lot of issues both in design and functionality.
The new GUI builder doesn't include any navigation code or any global code. Each form stands on its own.
Having said that you can implement your own state machine by just keeping form instances and showing the form you want to navigate to e.g.:
public static class Controller {
private static Form1 f1;
private static Form2 f2;
public static void showF1() {
if(f1 == null) f1 = new Form1();
f1.show();
}
// etc...
}
I used static context for simplicity but you can implement your own strategy. Notice that you can also insert global logic here e.g. add the toolbar as a function like:
private static void initForm(Form f) {
// add global commands to the toolbar
}
Alternatively you can derive all the forms from a common base class as the new GUI builder doesn't restrict your inheritance.

JavaFX - Accelerator not working when textfield has focus

In my application I have a screen where I use Accelerators. I'm using the function key F3 to execute an operation in my application. It works fine everytime, but when I click in any TextField on this screen the function key doesn't execute.
Here is the code where I set the Accelerator:
scene.getAccelerators().put(
new KeyCodeCombination(KeyCode.F3),
new Runnable() {
#Override
public void run() {
// do sth here
}
}
);
When I click my textfield and then hit the F3 function key it doesn't work.
Someone knows the solution?
This works for me using Java 1.8.0_45. However I encounter a similar issue with an editable Combobox field.
Edited:
Upon further investigation it does seem to occur with text field as well. I worked around it using the following custom class in place of text field:
public class ShortcutFriendlyTextField extends TextField{
public ShortcutFriendlyTextField() {
super();
addEventHandler(KeyEvent.KEY_RELEASED,new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
//handle shortcuts if defined
Runnable r=getScene().getAccelerators().get(new KeyCodeCombination(event.getCode()));
if(r!=null) {
r.run();
}
}
});
}
}
This answer is based on tikerman's. I added the code to handle modifier keys.
if (!event.getCode().isModifierKey()) {
Consumer<KeyCombination.Modifier[]> runAccelerator = (modifiers) -> {
Runnable r = getScene().getAccelerators().get(new KeyCodeCombination(event.getCode(), modifiers));
if (r != null) {
r.run();
}
};
List<KeyCombination.Modifier> modifiers = new ArrayList<>();
if (event.isControlDown()) modifiers.add(KeyCodeCombination.CONTROL_DOWN);
if (event.isShiftDown()) modifiers.add(KeyCodeCombination.SHIFT_DOWN);
if (event.isAltDown()) modifiers.add(KeyCodeCombination.ALT_DOWN);
runAccelerator.apply(modifiers.toArray(new KeyCombination.Modifier[modifiers.size()]));
}
Edit: fix consumer spelling.
If you have accelerators setup else where, such as a MenuBar, using some of the other solutions will cause the shortcut to be executed twice.
In my case, using modifiers with my shortcut (SHIFT + F3) works while in focus in the text area, but not while using just an accelerator such as just F3.
I set mine up to only use the event handler for shortcuts that do not use a modifier.
public static final double JAVA_VERSION = Double.parseDouble(System.getProperty("java.specification.version"));
public static void makeInputFieldShortCutFriendly(Node node) {
//this bug wasn't fixed until 9.0
if (JAVA_VERSION < 9) {
node.addEventHandler(KeyEvent.KEY_RELEASED, (KeyEvent event) -> {
if (!event.getCode().isModifierKey()) {
if (!event.isAltDown() && !event.isShiftDown() && !event.isAltDown()) {
Runnable r = node.getScene().getAccelerators().get(new KeyCodeCombination(event.getCode(), KeyCodeCombination.SHORTCUT_ANY));
if (r != null) {
r.run();
}
}
}
});
}
}
Edit: No modifier shortcuts was fixed in Java 9. Given the popularity of JDK 8 right now, we should probably provide some backwards capatability or request users update there JRE to 9+.
I you are using JavaFx+Scene builder then just make a function that redirect all the event to the respective functions and then add this function to every element in the scene with OnKeyReleased event

Save on change of CommandStack

probably a really easy newbie question but I can't manage to get it for already a pair of days, so I'll aks it here. My scope: I have an RCP application with some Graphical Editors (extensions of EditorPart). In my editor I want to catch any changes and save them directly. For that I catch the moment in which my CommandStack gets changed and start a doSave method. The problem is that my save method calls a CommadStack change event and if i skip this call than my changes are saved but my editor has still got a dirty flag. My wished behaviour would be that the dirty flag is away(like a normal save behaviour).
Both my CommandStack change and my doSave method are below. Could you please help me?
#Override
public void commandStackChanged(EventObject event) {
firePropertyChange(IEditorPart.PROP_DIRTY);
doSave(null);
setDirty(false);
}
public void doSave(final IProgressMonitor progressMonitor) {
editorSaving = true;
SafeRunner.run(new SafeRunnable() {
public void run() throws Exception {
IFile targetFile = getFile();
List<GraphicalEditPart> editParts = DiagramUtil.getAllEditParts(NetEditor.this);
Rectangle offsetBounds = DiagramUtil.getBounds(editParts);
saveDiagramProperties();
List<INetTransition> transitionsToExpand = saveSubdiagramGroups();
FileUtil.saveDiagram(getNetDiagram(), targetFile,
NetEditor.this);
getCommandStack().markSaveLocation();
for (INetTransition trans : transitionsToExpand) {
trans.setExpanded(true);
}
}
});
setDirty(false);
editorSaving = false;
}
With getCommandStack().markSaveLocation() I call once more the commandStackChanged and get in a loop. How can I solve this problem? Without it my editor remains dirty after save.
Thanks and Greets,
Jeff
How about adding IPropertyListener to your Editor and listen for IEditorPart.PROP_DIRTY. For the listener implementation perform save if editor is dirty otherwise do nothing.

Categories

Resources