I have a TextField, and I would like to do something if the user clicks anywhere that is not the TextField itself.
Apparently, the onMouseClicked event won't trigger if you don't click the node itself, so that wouldn't work.
Listening the focusedProperty may have been a good idea, but the problem is that almost the entirety of my application is not focus traversable, so in many cases clicking outside the textfield won't unfocus it, so the listener won't be notified.
The only thing left in my mind is to put an event filter on the scene itself, intercept mouse clicks, get the click coordinates and determine if they fall within the bounds of the textfield. I find this a little bit overkill and I may be missing something more obvious.
Is there another way to determine if the user clicked anywhere outside my TextField node?
The only thing left in my mind is to put an event filter on the scene itself, intercept mouse clicks, get the click coordinates and determine if they fall within the bounds of the textfield. I find this a little bit overkill and I may be missing something more obvious.
In fact I consider this your best option, but with a variation:
Use the PickResult provided by the MouseEvent to get the target Node and check, if it's in the hierarchy of the Node.
(This can be necessary, if the Node has children; e.g. a TextField also contains a Text element.)
In fact this seems to be the only option that cannot be broken by consuming the event somewhere in the scene graph.
Example
This moves the focus to the root pane in case the user clicks somewhere except the TextField.
public static boolean inHierarchy(Node node, Node potentialHierarchyElement) {
if (potentialHierarchyElement == null) {
return true;
}
while (node != null) {
if (node == potentialHierarchyElement) {
return true;
}
node = node.getParent();
}
return false;
}
#Override
public void start(Stage primaryStage) {
TextField textField = new TextField();
textField.setMinSize(400, Region.USE_PREF_SIZE);
textField.setMaxWidth(400);
textField.setEditable(false);
textField.textProperty().bind(Bindings.when(textField.focusedProperty()).then("Got the Focus!").otherwise("Please give me the focus!"));
StackPane root = new StackPane();
root.getChildren().add(textField);
Scene scene = new Scene(root, 500, 200);
scene.addEventFilter(MouseEvent.MOUSE_CLICKED, evt -> {
if (!inHierarchy(evt.getPickResult().getIntersectedNode(), textField)) {
root.requestFocus();
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
Assume you top level container of your app is Pane. You can apply mouse click event to that pane and inside it handle the mouse click event for the textfield. So this way, if the user clicks on the textfield or clicks somewhere else both events will be notified. Here's is the sample code.
Pane.setOnMouseClicked((MouseEvent evt) -> {
System.out.println("Click outside textfield");
textField.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("textfield clicked");
}
});
});
If you have any other controls in that Pane. This mouse click wont interfere with them, it will be only called when you click directly on the pane.
Related
I am creating a game where the user can control the human object. However, I would like to add a circle object to the pane to indicate where the human has already travelled. In simpler terms, I would like to leave a trail indicating where the user has travelled.
I would like to know how to add a circle object to the pane every time I press a directional button.
Here is what I have:
//Button Action Event for up button
Up.setOnAction(new EventHandler <ActionEvent>() {
#Override
public void handle(ActionEvent e) {
//Move object Up
human.setLayoutY(human.getLayoutY()-80);
//Keep object within the boundaries
if(human.getLayoutY()<40) {
human.setLayoutY(human.getLayoutY()+80);
}
}
});
I'm currently made an Form with JavaFX.
Always i press a Button, i call the "addAnswer()"-Method.
In that I create a RadioButton, a Label and a delete-Button, which i bundle in a HBox. All that HBoxes i pack in a vBox.
The Problem now is the delete-Button. I want to delte just THAT HBox in which the clicked Button is.
Here is my code:
public void addAnswer() {
this.rB = new RadioButton();
checkAnswer.getToggles().add(rB);
hBox = new HBox();
tF = new TextField();
delAnswer = new Button("Löschen");
delAnswer.setId(Integer.toString(counter));
hBox.getChildren().addAll(rB, tF, delAnswer);
hBox.setId(Integer.toString(counter));
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBox.getId())));
System.out.println(delAnswer.getId());
vBox.getChildren().addAll(hBox);
counter++;
}
public void delAnswer(int e){
vBox.getChildren().remove(delAnswer.getId());
}
i tried this one above but i realized, that all the delAnswers-Buttons have the same ID: the number of how often i pressed the add-Button.
Is there any solution where i can just select that one i pressed with that dynamic way? Cause i don't kow how often somebody will press or delete something.
Thanks
hbox is a field and this is why always the HBox last added is used. (hBox is evaluated, when lambda body is executed, not at the time of the lambda creation). This would be different, if you used a (effectively) final local variable:
final HBox hBoxLocal = hBox;
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBoxLocal.getId())));
However I'd like to present a different solution which would allow you to use the same EventHandler<ActionEvent> for all delete Buttons:
You can get the Node that triggered the event using getSource. From this Node you can get the parent, which is the HBox. You can remove this from the VBox using the remove(Object) method
delAnswer.setOnAction(e -> {
// get button
Node source = (Node) e.getSource();
// remove parent of button from VBox
vBox.getChildren().remove(source.getParent());
});
I think your problem is that you give the same event to all your button,Begin by creating a list that stores your buttons and then increments the value of the ID after affecting it to an item :
List<Button> buttons = new ArrayList<>();
/*
Create Button and call IDEvt method to create new event
for each button
*/
private void IDEvt(Button btn){
btn.setId(String.valueOf(IDRank));
btn.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println(btn.getId());
}
});
IDRank++;
}
Is there a way to add listener whenever I resize my splitpane?
I currently have
split.getDividers().get(0).positionProperty().addListener(new ChangeListener<Number>(){
public void changed(ObservableValue<? extends Number> observableValue, Number oldWindowWidth, Number newWindowWidth){
//code
}
});
which detects whenever the window or divider size is changed, but I only need to know when the divider position is changed, like when I drag it with the mouse. Is there a way to do that? Any help will be appreciated!
There is no clean way to do this but it is possible for example by using CSS lookup on the SplitPane.
As the dividers have the CSS class of split-pane-divider you can get the dividers from the scene-graph, and they are actually StackPane instances.
On these StackPane you can register a mouse pressed and a mouse release event listener, and update a class member that indicates that the divider is "in a drag" at the moment. And then in the position property listener you can check this class member: if it is true that means that the divider is being moved by the mouse, otherwise the change can be ignored.
Example:
public class Main extends Application {
// Indicates that the divider is currently dragged by the mouse
private boolean mouseDragOnDivider = false;
#Override
public void start(Stage primaryStage) throws Exception{
SplitPane sp = new SplitPane();
sp.getItems().addAll(new StackPane(), new StackPane());
sp.setDividerPositions(0.3f);
// Listen to the position property
sp.getDividers().get(0).positionProperty().addListener((obs, oldVal, newVal) -> {
if(mouseDragOnDivider)
System.out.println("It's a mouse drag to pos: " + newVal.doubleValue());
});
primaryStage.setScene(new Scene(sp, 300, 275));
sp.requestLayout();
sp.applyCss();
// For each divider register a mouse pressed and a released listener
for(Node node: sp.lookupAll(".split-pane-divider")) {
node.setOnMousePressed(evMousePressed -> mouseDragOnDivider = true);
node.setOnMouseReleased(evMouseReleased -> mouseDragOnDivider = false );
}
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Note:
As the lookup only works if the layout is created and the CSS is applied, hence it is important that the requestLayout() and the applyCss() methods are already executed and also that the SplitPane is already added to the scene-graph (attached to a Scene).
I am showing a Keyboard if the user clicks into a my extended TextField with the code shown below. When scrolling with a mouse you don't loose the focus to the TextField, but when scrolling by touch the focus is lost - and keyboard dispears of course. Is there a way to get the same behavior on touchScroll as on mouseScroll? I don't want the keyboard to disapear if the user is scrolling with touch!
focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(final ObservableValue<? extends Boolean> observable, final Boolean oldValue,
final Boolean newValue) {
KeyboardUtils.INSTANCE.setVisible(newValue);
}
});
This is pretty basic sir, when you are scrolling with a Touchscroll you definitely Touch a scrollable Pane area, and that Pane requestFocus()by the touch, so your TextField will loose its focus.
so to solve it you send focus back to your TextField if you detect a touch either by using the Scrolling listener of that Pane or Node or go for setOnTouchStationary() or setOnTouchReleased(), to help tweak the visibility of your keyboard instead of lying on focus of your TextField.
EDIT
Try this
Node lastFocusedNode =null; // lastly known node to have focus
//now every node or child in your ScrollPane or Scrollable parent
//that you care about will have a focusable listener-including
// your textfield
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(
ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
if(!newValue){//if they loose focus
lastFocusedNode = textField;
//if they loose focus attach them to lastFocusedNode
}
}
});
//the above saves you iterations
then when your ScrollPane/scrollable Node Receives focus you set them to the lastFocusedNode since its just going to allow scrolling.
sp.focusedProperty().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
if (lastFocusedNode != null) {
lastFocusedNode.requestFocus();
}
}
});
the above assumes your ScrollPane will just not do anything consuming aside from scrolling..
if you ScrollPane/scrollable parent is not going with that assumption then
you go with this approach-detect when the user scrolls after touching your content area of your Scrollable Node-this works only if user attempts to scroll after touching.
//approach loaded
final InvalidationListener lis = new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
//here it is changing
if(sp.isFocused())
lastFocusedNode.requestFocus();//take the focus away
}
};
using the above invalidation listener you set it on the hvalueProperty() and vvalueProperty() or your ScrollPane - which Scrollable parent are you using?
sp.hvalueProperty().addListener(lis);
sp.vvalueProperty().addListener(lis);
then you are done. any of the above solution will cause No problemo
EDIT 2
from what i know TouchEvent is for Touch enabled computers, so maybe go with MouseEvent and you can detect Pane.setOnMousePressed(); etc etcc
Hope it helps
This is the function which called on clicking save button in save wizard FXML.
#FXML
public void Onsave() {
// Handle Button event.
System.out.println(myButton9.getId());
myButton9.setOnAction((event) - > {
String text1 = textfield.getText();
System.out.println(text1);
System.out.println("Save Clicked....");
pane.setVisible(false);
{
Node node = (Node) event.getSource();
// pane.setVisible(false);
Stage stage = (Stage) node.getScene().getWindow();
stage.close();
System.out.println(flag4);
// Call Create Tree View Node function(args1)
//args1 = Test Suite Name
}
});
On clicking the save button an FXML is closed which leads to a main screen where i have to hide a pane. Now when i click on save there is a null pointer exception. The Pane ID in scene builder is "pane". Is there is way somehow that the main FXML and SAVE dialogue FXML can be linked and the on clicking the save button i can get the refrence of the pane and it does not show null value. I tried to use the flag but after the stage is closed the flag changes to its default value. Also if anyone can suggest do help me in creating the tree view in main FXML with the arguements of text1.