Popup Window gets stuck behind the main Window - JavaFX - java

So this problem is a bit of a tricky one. This class is for when, in my main program, I can produce a warning window for potentially dangerous actions that the user might do. The nice thing about this window is that if the user clicks OK, then it will return true to my main class. This is done through the following:
private boolean showWarningWindow(String message)
{
ConfirmationBox warning = new ConfirmationBox(message);
warning.showAndWait();
if (warning.isSelected())
{
return true;
}
return false;
}
This method is in my main GUI class. The problem is below, within ConfirmationBox. The line initModality(Modality.APPLICATION_MODAL); doesn't work properly. If you accidentally click back to your original GUI window, you're screwed, because now your ConfirmationBox window is trapped under your main window AND because of Modality.APPLICATION_MODAL, you won't be able to click anything to get the window back. There isn't a separate application on your taskbar to get the window back in focus and you can't even alt-tab to try and fix it.
Clearly the Modality.APPLICATION_MODAL works but somehow it doesn't make the connection that it needs to interrupt the main window.
Try it in your own applications. Add the showWarningWindow method to your application and add the ConfirmationWindow class and you will see what I mean. I'm not quite sure how to solve this.
package application;
import javafx.beans.property.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;
public class ConfirmationBox extends Stage
{
private VBox layout = new VBox();
private ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper();
public boolean isSelected()
{
return selected.get();
}
public ReadOnlyBooleanProperty selectedProperty()
{
return selected.getReadOnlyProperty();
}
public ConfirmationBox(String question)
{
// Core functionality of the ConfirmationBox.
setTitle("Warning");
initStyle(StageStyle.UTILITY);
initModality(Modality.APPLICATION_MODAL);
setResizable(false);
layout.setSpacing(10);
layout.setPadding(new Insets(10));
createControls();
// Add the Label and Buttons to the Confirmation Box.
layout.getChildren().addAll(new Label(question + "\n\n\n"), createControls());
java.awt.Toolkit.getDefaultToolkit().beep();
setScene(new Scene(layout));
sizeToScene(); // workaround because utility stages aren't automatically sized correctly to their scene.
}
private HBox createControls()
{
final Button ok = new Button("OK");
ok.setOnAction(e -> {
selected.set(true);
close();
});
final Button cancel = new Button("Cancel");
cancel.setOnAction(e -> {
selected.set(false);
close();
});
final HBox controls = new HBox(10, ok, cancel);
controls.setAlignment(Pos.CENTER_RIGHT);
return controls;
}
}

Related

How to open a JavaFX Window only once time

I have a JavaFX application and I need simple to avoid user from opening the same window inside the application more than once.
I tried to find some solution, but nothing get applicable.
As a sample... I have a window that give me payments options, its not a modal, it's a new stage. While I click the button to open that window, it's open, doesn't matter if there is another instance of this same stage running, simple open new windows every click. I want to avoid this. Like switch to the already opened stage window when click the button, or simply miss the click if that window is already opened.
You just need to keep track of the stage and only open a new one if its not already shown. You could also choose to disable the Button if the new window is showing, but I prefer to have the new window simply brought back in front so the user knows it's there.
You can do this by creating a reference to your Stage and then checking if it is null or showing within the button's event handler.
Here is an MCVE to demonstrate:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
/**
* Reference to the new Window that will allow only one instance at a time.
*/
private Stage newWindowStage;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
VBox root = new VBox(5);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
Button openWindow = new Button("Open Window");
// **********************************************************************************************
// Set the button to open the new Window Stage
// **********************************************************************************************
openWindow.setOnAction(event -> {
// **********************************************************************************************
// Check if the Stage is already showing.
// **********************************************************************************************
if (newWindowStage == null || !newWindowStage.isShowing()) {
// **********************************************************************************************
// The new window is not currently open, so create/show it
// **********************************************************************************************
newWindowStage = new Stage();
newWindowStage.setWidth(300);
newWindowStage.setHeight(300);
newWindowStage.setScene(new Scene(
new VBox(
new Label("New Window!")
)
));
newWindowStage.show();
} else {
// **********************************************************************************************
// The window is already open, so bring it to the front of focus
// **********************************************************************************************
newWindowStage.toFront();
}
});
root.getChildren().add(openWindow);
primaryStage.setScene(new Scene(root));
primaryStage.setWidth(200);
primaryStage.setHeight(200);
primaryStage.setTitle("Test Application");
primaryStage.show();
}
}

Using ContextMenu by releasing mouse button on an item

I couldn't really explain the question in the title.
What I want to do is that when a right mouse-press (not click!) is performed on a element a ContextMenu shows up (this part is easy). The problem is that when I (still holding the mouse button) move my cursor over the items they do not get highlighted and when I release the mouse button the MenuItem does not get selected. The ContextMenu does not seem to be aware of the cursor until I release the button from the press that opened it.
Basically a right mouse-press shows a ContextMenu and releasing the button over a MenuItem should select it. What I am talking about is available in basically every single program (browsers, IDEs, etc.).
var contextMenu = new ContextMenu();
var mi1 = new MenuItem("Item 1");
mi1.setOnAction(e -> System.out.println("Item 1"));
var mi2 = new MenuItem("Item 2");
mi2.setOnAction(e -> System.out.println("Item 2"));
contextMenu.getItems().addAll(mi1, mi2);
node.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.SECONDARY)
contextMenu.show(node, e.getScreenX(), e.getScreenY());
});
If you want to show the ContextMenu when the right mouse button is clicked, the following will work.
node.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
// If the menu is already open, then close it to avoid multiple
if (cm.isShowing()) {
cm.hide();
}
if (e.isSecondaryButtonDown()) {
cm.show(stage);
}
});
As for then moving the mouse to a MenuItem and releasing the already pressed secondary mouse button to activate the MenuItem, this doesn't seem to be supported.
The MenuItem's onAction is called by default even if the MouseButton is the secondary MouseButton, so unless you implement your own control, then this will be the best you can get.
I've included a complete runnable example of the behavior that I've described below:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Button buttonTest = new Button("Right-Click me!");
stage.setScene(new Scene(buttonTest));
stage.show();
ContextMenu cm = new ContextMenu();
MenuItem miTest = new MenuItem("Test");
miTest.setOnAction(e -> new
Alert(Alert.AlertType.INFORMATION,"Test").showAndWait());
cm.getItems().add(miTest);
buttonTest.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
if (cm.isShowing()) {
cm.hide();
}
if (e.isSecondaryButtonDown()) {
cm.show(stage);
e.consume();
}
});
}
}
Note: If you can give an example of an application that exhibits this behavior, maybe I could look into it further and see if there's an equivalent implementation in JavaFX. You mention IDEs, browsers, etc. but I'm unable to reproduce what I think you're describing.

Custom minimize button do not call MouseLeave event

I have a simple stage with StageStyle.TRANSPARENT (no default buttons).
Therefore I tried to create my own custom buttons, represented each by an ImageView with the next events activated: setOnMouseEntered, setOnMouseExited and of course setOnMouseClicked.
Problem is for the Minmized Button. Is a simple implementation like below
ImageView.setOnMouseClicked((MouseEvent event) -> {
stage.setIconified(true);
});
Lets imagine that my ImageView is a White rectangle. On mouse enter event, it changes its color into Black. On mouse exit, it is going back to White color.
When the ImageView is clicked, the window will be minimized, everything perfectly workable until now.
Problem is when the application is restored (maximized), the Minimized custom button is stuck with color Black (the color that represent the button is hovered), instead of White (default color when is not focused).
P.S. it seems that everything like relocate, setImage etc. inside the onMouseClicked handler is cut by the the setInconified(true);
Any help would be most appreciated.
Thank you for your time of reading this.
Updates to clear a bit the question
The normal print-screen image (when it is not hovered)
The hover print-screen (when it is hovered)
As you can observe, everything works perfectly. In the moment when "-" button (minimize button) is pressed, when the application is restored, it will remain stuck in hover mode, until the mouse cursor will hover again the button (then everything comes back to normal). Sadly neither CSS approach or event listeners on image view dose not seems to solve this issue.
Update code loaded
This is a simple one source file with just a button that call minimize
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
private Scene scene;
private Stage stage;
#Override
public void start(Stage stage) {
try {
this.stage = stage;
stage.initStyle(StageStyle.TRANSPARENT);
stage.setAlwaysOnTop(true);
stage.setFullScreen(true);
stage.setFullScreenExitHint("");
createScene(stage);
stage.setScene(scene);
stage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
private void createScene(Stage stage) {
Pane layer = new Pane();
layer.setPickOnBounds(false);
scene = new Scene(layer, 800, 600);
scene.getStylesheets().add("application/application.css");
layer.getChildren().add(buildMinimizeImage());
}
private ImageView buildMinimizeImage() {
ImageView imv = new ImageView();
int width = 43 ;
int height = 36;
imv.setId("myImage");
imv.setFitWidth(width);
imv.setFitHeight(height);
imv.setOnMouseClicked((MouseEvent event) -> {
stage.setIconified(true);
});
imv.relocate(100, 100);
return imv;
}
public static void main(String[] args) {
launch(args);
}
}
And the application.css is very simple as well
#myImage
{
-fx-image: url("minimize.png");
}
#myImage:hover
{
-fx-image: url("minimizeIn.png");
}
Issue is reproducible on Ubuntu 14.04 and Windows 10. I do not think is an OS problem
RESOLVED
Please find enclose the Harry Mitchel solution (thank you one more time for it). It is perfectly workable.
If you want to fix the code from above I by adding the setOnMousePressed event.
imv.setOnMousePressed((MouseEvent event) -> {
imv.setImage(image);
});
You can listen to the maximized property of the Stage class. Inside the changed() method, set the ImageView's image.
stage.maximizedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
//Display the desired icon here.
}
});
Here is a custom minimize button. You provide the two images and the stage as parameters. When the mouse is not over the button, it will show the image referenced in the constructor's first parameter. When the mouse is over the button, it will show the image referenced in the constructor's second parameter. When you click the image the stage will be minimized.
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class MinimizeButton extends Button {
/**
*
* #param img the image when the button is NOT selected
* #param imgHover the image when button is selected
* #param stage the stage that will be minimized
*/
public MinimizeButton(Image img, Image imgHover, Stage stage) {
ImageView imgView = new ImageView(img);
this.setGraphic(imgView);
this.addEventHandler(MouseEvent.MOUSE_ENTERED, (MouseEvent e) -> {
imgView.setImage(imgHover);
});
this.addEventHandler(MouseEvent.MOUSE_EXITED, (MouseEvent e) -> {
imgView.setImage(img);
});
this.setOnAction((ActionEvent event) -> {
stage.setIconified(true);
imgView.setImage(img);
});
}
}
Here is an example app that uses the MinimizeButton class.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class CustomMinimize extends Application {
#Override
public void start(Stage stage) {
Image imgWhite = new Image(getClass().getResourceAsStream("imgWhite.png")); //your image here
Image imgGreen = new Image(getClass().getResourceAsStream("imgGreen.png")); //your hover image here
MinimizeButton btnMinimize = new MinimizeButton(imgWhite, imgGreen, stage);
btnMinimize.setStyle("-fx-background-color: black;");
btnMinimize.setPrefSize(50, 50);
Button btnExit = new Button("X");
btnExit.setMinSize(50,50);
btnExit.setOnAction((ActionEvent event) -> {
System.exit(0);
});
btnExit.setStyle("-fx-background-color: black;");
HBox hBox = new HBox();
hBox.setSpacing(2);
hBox.getChildren().addAll(btnMinimize, btnExit);
AnchorPane anchorPane = new AnchorPane();
anchorPane.getChildren().addAll(hBox);
AnchorPane.setRightAnchor(hBox, 5.0);
AnchorPane.setTopAnchor(hBox, 5.0);
Scene scene = new Scene(anchorPane);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Your question is not very clear (not that it is very unclear though), so I will attempt to solve your problem.
I am assuming that your color change is done through ImageView.setOnMouseEntered() and ImageView.setOnMouseExited(). If this is so, you should instead use CSS.
.myImageView
{
-fx-image: url("my_white_image.png");
}
.myImageView:hovered
{
-fx-image: url("my_black_image.png");
}
For the things in your "PS" section, I couldn't understand, so I would not be able to give any advice on that.

JavaFX progress bar causes severe window lag after a resize

Problem
I have a decent sized javaFX window that appears to work perfectly well, until i resize any window in the application. After the resize all components that have a drop-down or similar action (ie. Menu, ComboBox, TabPane) become horribly slow.
Cause
I have narrowed the problem down to a progress bar, if i remove the progress bar from the scene i can resize the window(s) as much as i want, if i add it then any window resize and they start to become unresponsive for about half a second; sometimes as much as two seconds if i do a lot of resizing.
The window
A view of the window so that you can see all the components.
I can't add all the window code as its simply way too much to post.
Class with the progress bar
/**
* The class that holds and displays the progress bar
*/
public class BottomToolBarImpl extends ToolBar {
/**
* The label that display the "Waiting for input" text at the bottom of the
* window
*
* The {#code LLabel()} class is a label that gets its text from a
* properties file
*/
private final Label text = new LLabel().setTextKey("waiting").register();
/**
* This is the progress bar itself that is causing the problem
*/
private final ProgressBar progressBar = new ProgressBar();
/**
* Constructs the tool bar and adds the components
*/
public BottomToolBarImpl() {
super();
addItems();
}
/**
* Adds the progress bar and label the this object
*/
private void addItems() {
Region r = new Region();
r.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(r, Priority.ALWAYS);
progressBar.setMinWidth(192);//This line has no effect on the performance
this.getItems().add(r);
this.getItems().add(text);
this.getItems().add(progressBar);//If i comment out this line then all works perfectly
}
}
Additional information
Most of the visible components on the window (ie. TableView, ToolBar, ListView) are implementations. I doubt this is the problem
A lot of the widely used components such as Buttons and Labels are implementations that implement an interface that allows them to use a key that then gets the key value from a language file for its text. This doesn't do much on the rendering side nor is it called very often so i also doubt this is the problem.
The window starts up rather fast (less than a second).
I have a gaming pc so my hardware should not be a problem.
Java version: 1.8.0_40 (build 1.8.0_40-b25) 64-Bit
I suppose what i'm asking here is has any body else has this problem and if so how did you fix it?
Do you know what the problem could be? I don't think this is a bug as google doesn't have any/many results on it.
Any help would be appreciated as I'm completely stuck here.
An mcve that reproduces the results
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Reproduce problem");
final StackPane root = new StackPane();
primaryStage.setScene(new Scene(root, 500, 400));
final VBox layout = new VBox(10);
layout.getChildren().addAll(new MenuImpl(), new ProgressToolBar());
root.getChildren().add(layout);
primaryStage.show();
}
private class ProgressToolBar extends ToolBar {
private final Label text = new Label("Random Text Here");
private final ProgressBar progressBar = new ProgressBar();
public ProgressToolBar() {
super();
addItems();
}
private void addItems() {
Region r = new Region();
r.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(r, Priority.ALWAYS);
progressBar.setMinWidth(192);
this.getItems().add(r);
this.getItems().add(text);
this.getItems().add(progressBar); //Still causes the problem
}
}
private class MenuImpl extends MenuBar {
public final Menu FILE = new Menu("File", null, new MenuItem("A"), new MenuItem("B"), new MenuItem("C"));
public MenuImpl() {
super();
this.getMenus().addAll(FILE);
}
}
}
Click on the 'File' Menu and scroll through the items before and after resizing the window.
The problem seems to be related to this bug:
[Windows] Very poor performance of application (or Menus) when using an Animation.
As a workaround, run the program with -Dprism.vsync=false or -Dprism.order=sw as VM argument.

How can I fire internal close request?

I have trouble when closing a window in JavaFX.
I define my setOnCloseRequest as I wanted and it works when I click the x in the window. However, I also need a button to close the window and this onCloseRequest has to work, the problem is it does not. The event does not fire at all.
I am using JavaFX 2.2 (Java 7) and I notice that the reference for setOnCloseRequest says close the window on external request
Solution
Fire an event from your internal close request (on the button push), so that the application thinks it received an external close request. Then your close request logic can be identical whether the request came from an external event or an internal one.
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
// close event handling logic.
// consume the event if you wish to cancel the close operation.
}
...
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
Note
This is a Java 8+ solution, for JavaFX 2, you will need to convert the lambda functions in anonymous inner classes and will be unable to use the Alert dialog box but will need to provide your own alert dialog system as JavaFX 2 does not feature an in-built one. I strongly recommend upgrading to Java 8+ rather than staying with JavaFX 2.
Sample UI
Sample Code
The sample code will show the user a close confirmation alert and cancel the close request if the user does not confirm the close.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.*;
import javafx.stage.WindowEvent;
import java.util.Optional;
public class CloseConfirm extends Application {
private Stage mainStage;
#Override
public void start(Stage stage) throws Exception {
this.mainStage = stage;
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
StackPane layout = new StackPane(closeButton);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button) closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
// normally, you would just use the default alert positioning,
// but for this simple sample the main stage is small,
// so explicitly position the alert so that the main window can still be seen.
closeConfirmation.setX(mainStage.getX());
closeConfirmation.setY(mainStage.getY() + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args) {
launch(args);
}
}

Categories