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.
Related
I do not understand why when I left-click the MenuButton that the ContextMenu does not simply appear and stay, or disappear when I click a second time like a visibility toggle. A short code example and what I experience is detailed below.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.*;
public class BtnTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new MenuButton("Options", null, new MenuItem("test1"), new MenuItem("test2")), 650, 500);
primaryStage.setTitle("Testing Btn");
primaryStage.setScene(scene);
primaryStage.show();
}
}
I run this simple code in BlueJ IDE, on Windows 10
If I click on the MenuButton, the application window seems to be focused then lose focus, and the button's ContextMenu appears and then disappears.
If I click a second time, the ContextMenu appears and stays (even if I click off the application window onto another program).
If I click a third time, the ContextMenu disappears.
If I click a fourth time, the ContextMenu flickers on and off again.
If I click a fifth time, the ContextMenu appears and stays.
If I click a sixth time, the ContextMenu disappears.
And this continues to repeat.
I could be wrong, but the problem appears to be that the autoHide boolean property for the ContextMenu/PopupWindow is set to true by default. MenuButton uses a default ContextMenu when you click it to show the MenuItems.
I tried to setContextMenu for the MenuButton to a predefined ContextMenu, one with autoHide set to false, but this either causes an overlapping of events causing it to show then hide on click, or the ContextMenu is being set back to null by MenuButton?
Either way, I found a solution by switching to using a Button and a ContextMenu separately from one another, with the ContextMenu's autoHide set to false:
import javafx.geometry.Side;
import javafx.event.Event;
import javafx.event.EventHandler;
public class BtnTest2 extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Button btn = new Button("Options");
MenuItem item1 = new MenuItem("test1");
MenuItem item2 = new MenuItem("test2");
ContextMenu cm = new ContextMenu();
cm.setAutoHide(false);
cm.getItems().addAll(item1, item2);
btn.addEventHandler(ActionEvent.ACTION, e -> {
cm.show(btn, Side.BOTTOM, 0, 0);
e.consume();
});
Scene scene = new Scene(btn, 650, 500);
primaryStage.setTitle("Testing Btn");
primaryStage.setScene(scene);
primaryStage.show();
}
}
I have a JavaFX application with 2 tabs in a tabPane. And I would like each tab to have a default button (a button with defaultButton="true"). However, only the button in the first tab reacts on Enter key presses. The button in the second tab ignores Enter key presses.
Hypothesis: Oracle documentation states:
A default Button is the button that receives a keyboard VK_ENTER
press, if no other node in the scene consumes it.
Hence, I guess the problem is that both buttons are in a single scene. Do you know how to get 2 tabs in JavaFX, each with a working default button?
There can only be one default button: you want the button in the currently selected tab to be the default button. Simply add a listener to the selected property of each tab and make the corresponding button the default button, or use a binding to achieve the same:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MultipleDefaultButtons extends Application {
#Override
public void start(Stage primaryStage) {
TabPane tabPane = new TabPane();
tabPane.getTabs().addAll(createTab("Tab 1"), createTab("Tab 2"));
primaryStage.setScene(new Scene(tabPane, 400, 400));
primaryStage.show();
}
private Tab createTab(String text) {
Tab tab = new Tab(text);
Label label = new Label("This is "+text);
Button ok = new Button("OK");
ok.setOnAction(e -> System.out.println("OK pressed in "+text));
VBox content = new VBox(5, label, ok);
tab.setContent(content);
ok.defaultButtonProperty().bind(tab.selectedProperty());
return tab ;
}
public static void main(String[] args) {
launch(args);
}
}
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.
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;
}
}
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);
}
}