JavaFX Textfield - Override default behaviour when up key is released - java

I am trying to set TextField's cursor/caret to be at the end when the up key is released:
private void setTextFieldBehaviour() {
_textField.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent key) {
switch (key.getCode()) {
case UP:
if (key.isControlDown()) {
... // Do something for Ctrl + UP key
break;
} else {
... // Do something for plain old UP key
_textField.end();
// _textField's cursor will go back
// to the front when the UP key is released!
break;
}
default:
break;
}
}
});
}
However, as stated in the above code's comment, because I am only overriding the method setOnKeyPressed, this means that the default behaviour when the keys are released will kick in for JavaFX's TextField,
Consequently, this makes it difficult to set the TextField cursor at the end when the UP key is released.
I thought of overriding setOnKeyReleased just for UP (as shown below), but this has the ugly effect of the cursor jumping to the front of the TextField, then jumping back at the end.
_textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent key) {
switch(key.getCode()) {
case UP:
if(!(key.isControlDown())) {
... // Do something for plain old UP key
_textField.end();
// _textField's cursor will go back
// to the front when the UP key is released
// then jump back to the end again!
break;
}
default:
break;
}
}
});
Or visually, this is how it appears:
Before pressing anything: // Cursor is placed at the end of the string
TextField[oldString |]
Press UP: // Cursor is in front of string
TextField[| oldString]
Release UP: // Cursor is back of string
TextField[newString |]
So currently, I am able to set it to go to the back, but this entails the cursor jumping around. Is there anyway to do it better?

look at those sample application that show 2 way to achieve this:
by setting the event Handler and consuming the event.
public class main extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
TextField textField = new TextField();
textField.setOnKeyPressed(e -> {
if (e.getCode().equals(KeyCode.UP)) {
textField.end();
e.consume(); // this stop propagating the event
}
});
root.getChildren().add(textField);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
or by filtering the event and stop its propagation
public class main extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
TextField textField = new TextField();
textField.addEventFilter(KeyEvent.KEY_PRESSED, e -> { //event filter first catch the event
if(e.getCode().equals(KeyCode.UP)){
textField.end();
e.consume(); // this stop propagating the event
}
});
root.getChildren().add(textField);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
You should look at http://docs.oracle.com/javase/8/javafx/events-tutorial/events.htm to learn how event are processed in javaFX.

Related

Change the property of a rectangle by using another class JavaFX

I'm working on a project and I want a rectangle to appear when I press a button. However, I want to do this by directing the button click to a different class. Here is what I've tried:
Here is my first class, "Main"
static boolean btnClicked = false;
#Override
public void start(Stage primaryStage) {
Button btn = new Button("Make Popup Visible");
Rectangle menu = new Rectangle(40,40,200,200);
menu.setFill(Color.BLACK);
menu.setOpacity(0);
btn.addEventHandler(MouseEvent.MOUSE_CLICKED,(MouseEvent e) ->{
AddRect.showMenu();
});
if(btnClicked == true) {
menu.setOpacity(1);
}
Group root = new Group();
root.getChildren().addAll(btn, menu);
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
And my second class, "AddRect"
public class AddRect {
static void showMenu() {
Main.btnClicked = true;
}
}
However, this isn't working, and I don't know why. Can somebody help me? I don't even know if this is the best way to do it (Using two classes), but if there is a better way please let me know. Thanks in advance!
I figured it out! I just had to move the conditional inside the EventHandler
btn.addEventHandler(MouseEvent.MOUSE_CLICKED,(MouseEvent e) ->{
AddRect.showMenu();
if(btnClicked == true) {
menu.setOpacity(1);
}
});

How to check is mouse cursor is on the button in Java FX?

I'm new to Java and I'm wondering if it is possible to check if mouse cursor is for example on the button? I mean not getting clicking events but just moving cursor on the button.
I had working code getting click and then printing something, but I want to change it a little and I can't find out why it doesn't work.
public class Main extends Application implements EventHandler<MouseEvent> {
Button button;
Stage window;
Scene scene;
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Where's the button?");
button.setText("Click me!");
button.setOnMouseMoved(this);
StackPane layout = new StackPane();
layout.getChildren().add(button);
Scene scene = new Scene(layout, 300,350);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
#Override
public void handle(MouseEvent actionEvent) {
System.out.println("You clicked the button!");
}
}
I have made small code for you. Take a look. It prints in the console "Ho-Ho-Ho-Hovereed!" once you hover over your button.
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Hover over me.'");
btn.hoverProperty().addListener((event)->System.out.println("Ho-Ho-Ho-Hovereeed!"));
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Mouse manpulation example in JavaFX!");
primaryStage.setScene(scene);
primaryStage.show();
}
i guess you can do that with event handler or css, like...
button.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("Cursor Over Button");
}
});
or/with styles (css)
.button:hover{
-fx-background-color: red;
}
Each Node provides a hover property to track whether the mouse cursor is hovering over it or not. By using a simple listener, you can detect when the mouse starts and stops hovering:
button.hoverProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
System.out.println("Hovering...");
} else {
System.out.println("Retreating...");
}
});
With this listener, newValue will always be true if the mouse is currently hovering over the button and change to false when the mouse leaves the area.
There is also a useful check on a Node - isHover() You can use it on MouseEvents to check if the mouse is hovering over the needed node. Such a tip ;)
button.setOnMouseEntered( (event ) -> {
button.setTranslateX(button.getTranslateX() + 20);
button.setTranslateY(button.getTranslateY() + 20);
});`
Replace button.setOnMouseMoved(this); with my code. This will do!

Double tab change event on tab closing in JavaFX

I have the following program:
public class MainClass extends Application {
public static void main(String[] arg) {
launch(arg);
}
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabPane = new TabPane();
tabPane.getSelectionModel().selectedItemProperty().addListener((ov, oldTab, newTab) -> {
System.out.println("Tab change: " + oldTab + "/" + newTab);
});
Tab tab = new Tab("Test tab");
tab.setOnCloseRequest((event) -> {
System.out.println("Removing tab");
event.consume();
//I need to remove tab manually
tabPane.getTabs().remove(tab);
});
System.out.println("Adding tab");
tabPane.getTabs().add(tab);
Group root = new Group(tabPane);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
When I run it and click close icon on Tab I have the following output of the program:
Adding tab
Tab change: null/javafx.scene.control.Tab#70b1aa69
Removing tab
Tab change: javafx.scene.control.Tab#70b1aa69/null
Tab change: null/javafx.scene.control.Tab#70b1aa69
As you see I get two Tab change events when I closing tab but I need only one. How to fix it?
Interesting bug - was puzzled as to why/how the removed tab can still be the selected tab even though no longer in the tabs list.
First question was, where exactly the selection happens: that's done in the mousePressedHandler installed by the TabHeaderSkin
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent me) {
if (getTab().isDisable()) {
return;
}
if (me.getButton().equals(MouseButton.MIDDLE)) {
if (showCloseButton()) {
Tab tab = getTab();
if (behavior.canCloseTab(tab)) {
removeListeners(tab);
behavior.closeTab(tab);
}
}
} else if (me.getButton().equals(MouseButton.PRIMARY)) {
behavior.selectTab(getTab());
}
}
});
But then: how comes that this handler is still active after removal of the tab (and hopefully its visuals as well)? The cleanup of the visual parts is the task of TabPaneSkin, it listens to the tabs list and removes the TabHeaderSkin (aka: the component that shows the tab above its content). But the cleanup is not immediately complete for two reasons:
fade-out animation keeps the header alive until the animation is ready, that's fine
header's internal cleanup (messaged via header.removeListeners) is incomplete, as it removes children and listeners, but fails to remove the mouseHandler - and that's the bug.
Code from TabHeaderSkin:
private void removeListeners(Tab tab) {
listener.dispose();
inner.getChildren().clear();
getChildren().clear();
// following line is missing:
setOnMousePressed(null)
}
A way to hack around is to register our own listener on tabs, and force the handler to null on removal. Note: the listener must be notified after core did its job, so either install in a custom skin or after the tabPane's skin has been set.
To illustrate, I modified your example accordingly:
public class TabPaneRemoveSelected extends Application {
public static void main(String[] arg) {
launch(arg);
}
public static class MyTabSkin extends TabPaneSkin {
public MyTabSkin(TabPane pane) {
super(pane);
pane.getTabs().addListener(this::tabsChanged);
}
protected void tabsChanged(Change<? extends Tab> c) {
while (c.next()) {
if (c.wasRemoved()) {
// lookup all TabHeaderSkins
Set<Node> tabHeaders = getSkinnable().lookupAll(".tab");
tabHeaders.stream()
.filter(p -> p instanceof Parent)
.map(p -> (Parent) p)
.forEach(p -> {
// all children removed indicates being in the process
// of being removed
if (p.getChildrenUnmodifiable().size() == 0) {
// complete removeListeners
p.setOnMousePressed(null);
}
}
);
}
}
}
}
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabPane = new TabPane() {
#Override
protected Skin<?> createDefaultSkin() {
return new MyTabSkin(this);
}
};
tabPane.getSelectionModel().selectedItemProperty().addListener((ov, oldTab, newTab) -> {
System.out.println("Tab change: " + oldTab + "/" + newTab );
});
Tab tab = new Tab("Test tab");
Tab second = new Tab("second");
installHandler(tabPane, tab, second);
installHandler(tabPane, second);
System.out.println("Adding tab");
tabPane.getTabs().addAll(tab, second);
Group root = new Group(tabPane);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
protected void installHandler(TabPane tabPane, Tab... tab) {
for (Tab tab2 : tab) {
tab2.setOnCloseRequest((event) -> {
System.out.println("Removing tab");
event.consume();
//I need to remove tab manually
tabPane.getTabs().remove(tab2);
});
}
}
}
It seems to be a bug so I opened a bug issue http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8189424 (https://bugs.openjdk.java.net/browse/JDK-8189424) and accept this answer (as soon as SO lets me do it).

displaying a tooltip in javafx brings its stage into the foreground

I am trying to work around this bug in the jdk: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8088624
public class Blubb extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Button btn = new Button("Click");
btn.setTooltip(new Tooltip("Blubb"));
Scene scene = new Scene(new BorderPane(btn), 320, 240);
primaryStage.setScene(scene);
primaryStage.show();
Stage secondStage = new Stage();
secondStage.setScene(new Scene(new BorderPane(new Button("Click")), 320, 240));
//secondStage.initOwner(primaryStage);
secondStage.show();
}
}
If the button on the primary stage is hovered, it will come in front of the second stage. I found that calling initOwner() on a Stage will eliminate this behavior.
Now my problem is following: I have multiple "popups" that have a common owner (the primary stage). Hovering over controls on the primary stage doesn't cause any unexpected behavior after the initOwner() workaround. If you however hover over controls in a popup while another popup was in focus, the hovered popup will steal focus.
Is there a way I can work around this bug for not only the primary stage but also the popups?
UPDATE: turns out my workaround has undesired side-effects. Javadocs for Stage state following:
A stage will always be on top of its parent window.
So additionally, what would be a workaround that makes the popup not "always on top" and minimizable?
There is a way to get around it by overlaying StackPanes. Create your Scene with a StackPane so that you can add another StackPane when the stage has lost its focus. The overlayed pane will prevent Tooltips or anything else happening on mouse-over while the pane is not in focus. You may also minimize any of your stages and they won't be always-on-top.
public class Blubb extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Button button_1 = new Button("Button #1");
button_1.setTooltip(new Tooltip("Blubb #1"));
StackPane primary = new StackPane(new BorderPane(button_1));
primaryStage.setScene(new Scene(primary, 320, 240));
addStageFocusListener(primaryStage, primary);
primaryStage.show();
Button button_2 = new Button("Button #2");
button_2.setTooltip(new Tooltip("Blubb #2"));
StackPane second = new StackPane(new BorderPane(button_2));
Stage secondStage = new Stage();
addStageFocusListener(secondStage, second);
secondStage.setScene(new Scene(second, 320, 240));
secondStage.show();
}
public void addStageFocusListener(Stage stage, StackPane stackPane) {
stage.focusedProperty().addListener(new ChangeListener<Boolean>(){
public final StackPane preventTooltip = new StackPane();
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(stage.isFocused()) {
if(stackPane.getChildren().contains(preventTooltip)) {
stackPane.getChildren().remove(preventTooltip);
}
} else {
stackPane.getChildren().add(preventTooltip);
}
}
});
}
}
You can try this:
public static final disableMouseEventOnUnfocus(final Stage stage)
{
if (stage == null
|| stage.getScene() == null
|| stage.getScene().getRoot() == null)
return;
else
{
stage.getScene().getRoot().mouseTransparentProperty().bind(stage.focusedProperty().not());
}
}
I didn't try it though, but if it works, this should be a good alternative. There is no need to restructure your layout, and you can leave all your layout in FXML, without specifying fx:id for the tooltips.
I've come up with this alternative solution, as I've found it easier in my case to subclass Tooltip and apply a fix there. I just overload the show() method to only show if the owning window is focused. It's working like a charm for me...
public class FixedTooltip extends Tooltip {
public FixedTooltip(String string) {
super(string);
}
#Override
protected void show() {
Window owner = getOwnerWindow();
if (owner.isFocused())
super.show();
}
}
You could try to unset the tooltip whenever the node's window loses focus. Such as below:
public class Blubb extends Application {
public static void main(String[] args) {
launch(args);
}
public static void installTooltip(Node n, Tooltip tp)
{
Window w = n.getScene().getWindow();
w.focusedProperty().addListener((val, before, after) -> {
if (after)
Tooltip.install(n, tp);
else
Tooltip.uninstall(n, tp);
});
if (w.isFocused())
Tooltip.install(n, tp);
else
Tooltip.uninstall(n, tp);
}
#Override
public void start(Stage primaryStage) throws Exception {
Tooltip tp = new Tooltip("Blubb");
Button btn = new Button("Click");
Scene scene = new Scene(new BorderPane(btn), 320, 240);
primaryStage.setScene(scene);
//primaryStage.show();
Stage secondStage = new Stage();
secondStage.setScene(new Scene(new BorderPane(new Button("Click")), 320, 240));
//secondStage.initOwner(primaryStage);
secondStage.show();
primaryStage.show();
installTooltip(btn, tp);
}
}
Of course, you would have to call installTooltip after the node is added to the component.

JavaFX: modify button text in real time

I have a button with a ContextMenu which contains a MenuItem rename.
rename.setOnMouseClicked is supposed to give the possibility to change the text in the button : The user can actually delete a letter in the text or write it then press enter to validate what he was typing, somehow working as textField.
Is it possible to do this ? if so, how can i ? Thanks!
Yes
Here's how:
public class EditableButtonApp extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
root.setCenter(new EditableButton("Editable Button"));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
class EditableButton extends Button {
TextField tf = new TextField();
public EditableButton(String text) {
setText(text);
setOnMouseClicked(e -> {
tf.setText(getText());
setText("");
setGraphic(tf);
});
tf.setOnAction(ae -> {
// if (validateText(tf.getText())) {// this is where you would validate the text
setText(tf.getText());
setGraphic(null);
// }
});
}
}
public static void main(String[] args) { launch(args); }
}

Categories

Resources