Goal
I need to create a javafx stage which hides when mouse move over it, but show when mouse is not over my stage. My stage should,
Hide when I bring mouse pointer on to it
Show when mouse pointer is on any place of screen other than on my stage
Allow to click/focus through it, when it is hidden
Problem
Although it seems like a very easy task, I cannot figure out how to do it correctly. Yes, we can easily implement the logic to hide the stage (using mouse event listeners). But, when showing the hidden stage, it seems so difficult as the hidden stage cannot listen to any mouse event.
Can we implement this without low-level system-wide logic?
If so, how can we implement it? Can anyone suggest a good approach?
If we need to use low-level system-wide logic how can we do it while keeping platform independent support?
In JavaFX you can use the javafx.scene.robot.Robot to get the mouse pointer locations. Then the job will be very easy for you.
As an example you could do something like this,
Bounds interfaceBounds = ...;
Robot robot = new Robot();
An assume you have two functions for hide/show the interface.
void hide() { ... }
void show() { ... }
Then you the following code in another thread (timer).
Point2D urMouse = robot.getMousePosition();
if( !interfaceBounds.contains( urMouse ) )
{
hide();
}else{
show();
}
Works only with StageStyle.TRANSPARENT. Only option I can see to it make work with any StageStyle is to create two stages. One should be only hoverProperty listener.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class App extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
StackPane stackPane = new StackPane();
Scene scene = new Scene(stackPane, 400, 400);
scene.setFill(null);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
stage.show();
ChangeListener<Boolean> listener = (observable, oldValue, newValue) -> {
if (newValue) {
stackPane.setStyle("-fx-background-color: rgb(0, 0, 0, 0.01);");
} else {
stackPane.setStyle("-fx-background-color: red;");
}
};
stackPane.hoverProperty().addListener(listener);
listener.changed(stackPane.hoverProperty(), null, stackPane.isHover());
}
}
Related
I am in the process of teaching myself JavaFX. Coming from the Swing world there are a lot of similarities between the 2. Especially event processing. Part of my process is to try and mimic an existing application as closely as possible. One of the things I am doing is creating a dialog that will allow the user to select a font to use. There is a text field for them to type in the font name and a list where they can scroll and select one. When they start typing the list will automatically scroll to through the list to start matching what the user is typing. I am also trying to populate the text field with the currently matched font name and then highlight the portion that the user has not typed yet so they can continue to type until the correct match is found.
For example if the user types the letter 't' on Windows the first font found is Tahoma. So the text field will be set to Tahoma and the carat will be positioned right after the 'T' and the 'ahoma' will be highlighted. What happens instead is that the field is populated with Tahoma and the carat is positioned at the end and nothing is highlighted. So it is like it is ignoring the 2 lines of code for positioning and highlighting or the event processor is causing my calls to JavaFX libraries to be run out of order.
I think this may be a bug with JavaFX but it could also be my misunderstanding of the event system. Please let me know which one and why.
Here is a complete sample code showing the problem. Just start typing in the text field to try it out.
package test;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
textChange = (observable, oldValue, newValue) -> {
text.textProperty().removeListener(textChange);
for (String family : Font.getFamilies()) {
if (family.equalsIgnoreCase(newValue) || family.toLowerCase().startsWith(newValue.toLowerCase())) {
text.setText(family);
text.positionCaret(newValue.length());
text.selectEnd();
break;
}
}
text.textProperty().addListener(textChange);
};
text.textProperty().addListener(textChange);
}
public static void main(String... args) {
launch(args);
}
}
Wrap caret position and select end into Platform.runLater. The problem is in events order. I don't know correct details about this issue so I will not provide you a detailed answer, only solution.
Platform.runLater(()-> {
text.positionCaret(newValue.length());
text.selectEnd();
});
Here's an alternative approach entirely, which uses a TextFormatter to modify changes to the text. The advantage here is that it doesn't rely on the "timing" of various property changes with respect to event handling, which is not documented and thus could possibly change in later JavaFX versions. It also avoids the slightly ugly "remove the listener and add it back" idiom.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
UnaryOperator<Change> filter = c -> {
// for delete, move the caret, or change selection, don't modify anything...
if (c.getText().isEmpty()) {
return c ;
}
for (String family : Font.getFamilies()) {
if (family.toLowerCase().startsWith(c.getControlNewText().toLowerCase())) {
c.setText(family.substring(c.getRangeStart(), family.length()));
c.setAnchor(c.getControlNewText().length());
break ;
}
}
return c ;
};
text.setTextFormatter(new TextFormatter<String>(filter));
}
public static void main(String... args) {
launch(args);
}
}
So this is the code i have that launches the GUI i read on a previous post that it maybe has to do with the fact that my root is of type Group but i wasn't able to figure out how to implement any other way. The content inside the GUI gets eaten up when i try to resize the main frame manually.I want it to stay center and resize with the frame.
package view;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import singleton.MainModel;
public class MainView extends Application {
#Override
public void start(Stage primaryStage) {
// Initialize mainController.
MainController mainController = new MainController();
// Add the controller to the singleton.
MainModel.getModel().getMainData().setMainController(mainController);
// Initialize display components.
Group root = new Group();
Scene scene = new Scene(root, 1280, 720);
// Add mainController.
root.getChildren().addAll(mainController);
// Pin the root to scene and display it.
primaryStage.setScene(scene);
primaryStage.show();
// Properly terminate the application if the user presses the "X" window button.
primaryStage.setOnCloseRequest(event -> {
mainController.closeApplication();
stop();
});
// Set the title and make the application a fixed size.
primaryStage.setTitle("Visual Earth Modelling System");
primaryStage.setResizable(true);
primaryStage.sizeToScene();
// Add the stage to the singleton.
MainModel.getModel().getMainData().setMainStage(primaryStage);
// Go to the first screen.
mainController.goToLoginScreen();
}
/**
* To destroy resources upon application close. Should be called in all instances of a properly closed JavaFX application.
*/
#Override
public void stop() {
if (MainModel.getModel().getNetworkData().isHandlerSet())
MainModel.getModel().getNetworkData().closeHandler();
}
/**
* This method is actually not used in a correctly deployed JavaFX application. Instead, the start method above is called. This main serves as a fallback in case of improper configuration.
*/
public static void main(String[] args) {
launch(args);
}
}
Replace Group with StackPane. StackPane by default sets alignment of children to center. Just to be sure that alignment is correct you can set it explicity by StackPane.setAlignment(Pos value).
StackPane root = new StackPane();
root.setAlignment(Pos.CENTER);
root.getChildren().add(mainController);
Scene scene = new Scene(root, 1280, 720);
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.
In one of my recent projects I want to implement a hidden page. I want to be able to reach it by just typing the password without anything showing on the screen. I tried to just set a PasswordField as visible(false). However that didn't work. Also I would like the hidden page to pop up without having to press enter after typing the password. Is there a way for a simple javafx application to behave like that?
You could use a KeyListener. Though you need to press the screen once for the keypresses to register. And make sure to add the keylistener to the JFrame, I always forget that. This will look for keys, but requires a window to be shown, this can be empty though.
If you don't want a window at all, you can use the external library jnativehook it looks for keypresses globally.
You can add an event filter to the scene that keeps track of what has been typed.
Here is a simple example (type "secret" with the main window focussed to show the popup window; press enter if you mistype to clear the hidden text):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class OpenSecretWindow extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label("Type the secret password\nto open the secret window");
label.setTooltip(new Tooltip("The secret password is \"secret\""));
StackPane root = new StackPane(label);
Scene scene = new Scene(root, 400, 400);
StringBuilder typedText = new StringBuilder();
scene.addEventFilter(KeyEvent.KEY_TYPED, e -> {
switch(e.getCharacter()) {
case "\n":
case "\r":
typedText.delete(0, typedText.length());
break ;
default:
typedText.append(e.getCharacter());
}
if ("secret".equals(typedText.toString())) {
openSecretWindow(primaryStage);
typedText.delete(0, typedText.length());
}
});
// handle backspace and delete:
scene.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.BACK_SPACE || e.getCode() == KeyCode.DELETE) {
if (typedText.length() > 0) {
typedText.delete(typedText.length()-1, typedText.length());
}
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
private void openSecretWindow(Stage owner) {
Stage stage = new Stage();
StackPane root = new StackPane(new Label("You have found\nthe secret window!"));
Scene scene = new Scene(root, 300, 180);
stage.setScene(scene);
stage.initOwner(owner);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I believe you could set the foreground of the JPasswordField to be the same as the background colour but don't quote me on that. Something like:
JPasswordField.SetForeground(Color.RED);
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;
}
}