I'm trying to stop the arrow keys from navigating through the controls i have in my example. I'm not sure how to do this. Here is the example i have created:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class KeyEventTest extends Application{
private EventHandler<KeyEvent> keyEventHandler;
#Override
public void start(Stage stage) throws Exception {
// TODO Auto-generated method stub
Group root = new Group();
FlowPane f = new FlowPane();
Button r = new Button("button1");
Button r2 = new Button("button1");
Button r3 = new Button("button1");
f.getChildren().addAll(r,r2,r3);
root.getChildren().add(f);
Scene scene = new Scene(root,600,600);
keyEventHandler = new EventHandler<KeyEvent>() {
public void handle(final KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.LEFT || keyEvent.getCode() == KeyCode.UP) {
System.out.println("arrow keys");
}else{
System.out.println(keyEvent);
}
}
};
stage.addEventHandler(KeyEvent.KEY_PRESSED, keyEventHandler);
stage.setScene(scene);
stage.show();
}
public static void main(String args[]){
Application.launch(args);
}
}
Any help would be appreciated
Thanks
I had a similar Problem and found an easy workaround. Since the controls navigating Part triggers after the self-implemented EventHandler you can simply stop any further propagation of the KeyEvent by calling
keyevent.consume()
at the end of your handle() method.
First, the answer on this question is similar to answer on this question : JavaFX: How to change the focus traversal policy?
You can change traversal policy, so that traverse will not be done, for some situations.
The other decision - is to add listener of focused property, and drop focus back, when it is not wished (but it will work for all navigation keys):
node.focusedProperty().addListener(new ChangeListener<Boolean>(){
public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
Platform.runLater(new Runnable(){
public void run() {
node.requestFocus();
}
});
}
});
You should use runLater, because of such issue : JavaFx: After dialog, two textfields gains focus instead one
The way how you do this - handler on key press may not work always, because actions may happen on key release too.
In common case, behavior on key presses is determined in behavior class, which is accessible by skin class. If you want to change behavior on keys pressing, you can change behavior class.
Related
I am using ControlsFX's CustomTextField. When I click on one of the autocomplete options, I need to clear the TextField and create a Tag so I can add it to a FlowPane. How do I set up an OnClick or OnSelectionChange listener or override the current OnClick?
I took a look at the CustomTextField documentation and I can't find a clear way of doing what you want. So I will guess you have to implement it yourself or to find a workaround. In case you decide to choose the second choice here is something that I believe works very well :
import java.util.ArrayList;
import org.controlsfx.control.textfield.CustomTextField;
import org.controlsfx.control.textfield.TextFields;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestApplication extends Application {
private boolean addedBySelection = false;
private ArrayList<String> tagList = new ArrayList<>();
private FlowPane tagPane;
#Override
public void start(Stage primaryStage) throws Exception {
VBox mainPane = new VBox(10);
mainPane.setStyle("-fx-background-color : white");
mainPane.setPadding(new Insets(15));
mainPane.setAlignment(Pos.CENTER);
tagPane = new FlowPane(15, 10);
tagPane.setPrefHeight(50);
CustomTextField field = new CustomTextField() {
#Override
public void paste() {
super.paste();
addedBySelection = false;
}
};
field.setOnKeyPressed(e -> {
addedBySelection = false;
});
field.setOnKeyReleased(e -> {
addedBySelection = true;
});
field.textProperty().addListener(e -> {
if (addedBySelection) {
System.out.println("Text Changed from the suggession list ");
addTag(field.getText());
addedBySelection = false;
field.clear();
addedBySelection = true;
} else {
System.out.println("User Input (Mouse paste, or typing) ");
}
});
TextFields.bindAutoCompletion(field, new String[] { "Java", "C++", "C#", "Python", "Haskell" });
mainPane.getChildren().addAll(field, tagPane);
Scene scene = new Scene(mainPane, 200, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
private void addTag(String tag) {
if (!tagList.contains(tag)) {
tagList.add(tag);
Label tagLabel = new Label(tag);
tagLabel.setStyle("-fx-background-color : #E1ECF4; -fx-text-fill : #6A739D;");
tagPane.getChildren().add(tagLabel);
}
}
public static void main(String[] args) {
launch(args);
}
}
I tried to keep it a simple as it could. The code above is doing exactly what you are after. The logic is to set a listener on the textProperty ( cause we can't set one on selection with the mouse from the autocomplete list ) and somehow find out if the user actually triggers the event using the autocomplete list or not. Thus I have a flag looking for user inputs (ex. key press) and 'releasing' the flag each time the keys are released. We have to catch the paste action as well in order to avoid mistakes if the user pastes a text on the field. One more last thing is the way we clear the field. We have to set our flag to false because the field.clear() will trigger an event as well and we don't want to fall into an event loop.
Note : With the current workaround, you will see that you are able to make a selection from the autocomplete list by pressing the enter key as well.
I'm using javaFX to create an application.
I have a hyper-link somewhere and I've set an (onAction) for it as shown below
Hyperlink studentList = ...; // It's given proper object
studentList.setOnAction(...);
now somewhere else i used this method to simluate a click on this hyperlink
studentList.fire();
now my problem is that how can i distinguish real click/keyPress from fire() method ?
Here's one way to do it. Just add an EventHandler to the setOnMousePressed property. Be sure to add it to setOnMousePressed and not e.g. setOnMouseClicked, since setOnMousePressed is invoked before the fire() is invoked while setOnMouseClicked is invoked after.
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MCVE extends Application {
#Override
public void start(Stage stage) {
VBox content = new VBox(5);
content.setPadding(new Insets(10));
Hyperlink link = new Hyperlink("Hyperlink");
Button fireButton = new Button("Fire hyperlink");
fireButton.setOnAction(e -> link.fire());
BooleanProperty mouseClicked = new SimpleBooleanProperty(false);
link.setOnMousePressed(e -> {
System.out.println("Mouse click");
mouseClicked.set(true);
});
link.setOnAction(e -> {
if (!mouseClicked.get()) {
System.out.println("No mouse click");
}
mouseClicked.set(false);
});
content.getChildren().addAll(link, fireButton);
stage.setScene(new Scene(content));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I'm trying to make a simple Java transaction app with JavaFX as UI.
What I want to do now is to detect user idle state from my application which has 1 primary stage and many scenes.
Example : if user idle for 3 minutes then go back to main menu.
I already try some examples on the web about how to detect JavaFX idle state, but what I found is always -one function idle state detection which is occuring all scenes- method which is (I think) dangerous for transaction app (ex : apps detect idle state in the middle of transaction process).
It's possible to detect user idle state on every single scene? how?
Thanks.
EDIT :
Examples that I already try :
http://tomasmikula.github.io/blog/2014/06/04/timers-in-javafx-and-reactfx.html
and
http://ochafik.com/blog/?p=98
I don't really understand the point you are making about transactional behavior. Transactions concern guarantees about the data, and your transactional behavior should be defined at the data level and should not be impacted by what is happening in the UI. In other words, your atomic behavior should complete or rollback even if the UI resets due to the user being idle.
Maybe this will help, though. (Note I used Java 8 code in these examples, but you can fairly easily make it JavaF 2.2 compliant if you need.) This follows Tomas Mikula's general approach in that it uses a Timeline to implement the idle check. I didn't use Tomas' FX Timer wrapper but you could certainly do so if you like. This class encapsulates a monitor for whether the user is idle. You can register any node (or scene) and type of event: if an event of that type occurs on that node (or scene), the user is determined not to be idle. If the specified time elapses without any registered events occurring, the provided runnable is executed (on the FX Application Thread). This gives you the flexibility to create multiple monitors, if needed, and to register one or more nodes with each.
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.util.Duration;
public class IdleMonitor {
private final Timeline idleTimeline ;
private final EventHandler<Event> userEventHandler ;
public IdleMonitor(Duration idleTime, Runnable notifier, boolean startMonitoring) {
idleTimeline = new Timeline(new KeyFrame(idleTime, e -> notifier.run()));
idleTimeline.setCycleCount(Animation.INDEFINITE);
userEventHandler = e -> notIdle() ;
if (startMonitoring) {
startMonitoring();
}
}
public IdleMonitor(Duration idleTime, Runnable notifier) {
this(idleTime, notifier, false);
}
public void register(Scene scene, EventType<? extends Event> eventType) {
scene.addEventFilter(eventType, userEventHandler);
}
public void register(Node node, EventType<? extends Event> eventType) {
node.addEventFilter(eventType, userEventHandler);
}
public void unregister(Scene scene, EventType<? extends Event> eventType) {
scene.removeEventFilter(eventType, userEventHandler);
}
public void unregister(Node node, EventType<? extends Event> eventType) {
node.removeEventFilter(eventType, userEventHandler);
}
public void notIdle() {
if (idleTimeline.getStatus() == Animation.Status.RUNNING) {
idleTimeline.playFromStart();
}
}
public void startMonitoring() {
idleTimeline.playFromStart();
}
public void stopMonitoring() {
idleTimeline.stop();
}
}
Here's a test. The "Start" buttons are perhaps stand-ins for logging in. The main UI has a tab pane with two tabs: each individual tab starts with its own "Start" button and then the main content has a label, text field, and button.
The tab contents each have a (short, for testing) idle monitor associated with them. Any event on the content of the tab will reset the idle monitor, but events outside of the tab content will not reset it. There's also a "global" idle monitor for the entire window which resets the whole UI after 30 seconds.
Note that the data is preserved: i.e. if you timeout due to the idle, any text you type in the text field is preserved properly. This is why I think the issue with "transactions" should not matter at all.
import javafx.application.Application;
import javafx.event.Event;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
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.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class IdleTest extends Application {
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
Parent mainUI = buildMainUI();
Scene scene = new Scene(root, 350, 150);
Parent startUI = buildStartUI(() -> root.getChildren().setAll(mainUI));
root.getChildren().add(startUI);
IdleMonitor idleMonitor = new IdleMonitor(Duration.seconds(30),
() -> root.getChildren().setAll(startUI), true);
idleMonitor.register(scene, Event.ANY);
primaryStage.setScene(scene);
primaryStage.show();
}
private Parent buildStartUI(Runnable start) {
Button button = new Button("Start");
button.setOnAction(e -> start.run());
StackPane root = new StackPane(button);
return root ;
}
private Parent buildMainUI() {
TabPane tabPane = new TabPane();
Tab tab1 = new Tab("One");
Parent tab1Content = buildTabUI("Tab 1");
Parent tab1StartContent = buildStartUI(() -> tab1.setContent(tab1Content));
tab1.setContent(tab1StartContent);
IdleMonitor tab1IdleMonitor = new IdleMonitor(Duration.seconds(5),
() -> tab1.setContent(tab1StartContent), true);
tab1IdleMonitor.register(tab1Content, Event.ANY);
Tab tab2 = new Tab("Two");
Parent tab2Content = buildTabUI("Tab 2") ;
Parent tab2StartContent = buildStartUI(() -> tab2.setContent(tab2Content));
tab2.setContent(tab2StartContent);
IdleMonitor tab2IdleMonitor = new IdleMonitor(Duration.seconds(10),
() -> tab2.setContent(tab2StartContent), true);
tab2IdleMonitor.register(tab2Content, Event.ANY);
tabPane.getTabs().addAll(tab1, tab2);
return tabPane ;
}
private Parent buildTabUI(String text) {
Button button = new Button("Click here");
button.setOnAction(e -> System.out.println("Click in "+text));
VBox content = new VBox(10, new Label(text), new TextField(), button);
content.setAlignment(Pos.CENTER);
return content ;
}
public static void main(String[] args) {
launch(args);
}
}
I want to listen to some KeyEvent in my scene, say KeyCode.ESCAPE(close the scene when pressed).
scene.addEventHandler(KeyEvent.ANY, event -> {
if (event.isConsumed())
return;
switch (event.getCode()) {
case ESCAPE:
stage.hide();
event.consume();
break;
default:
break;
}
});
Now, the nodes inside the scene could have listened to ESCAPE too.
// ....
someOtherNode.addEventHandler(KeyEvent.ANY, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
// do stuff
e.consume();
}
});
// ....
How do I make sure that the KeyEvent will be consumed from the node and not the scene?
Based on the diagram from Oracle, A workaround would be adding a dummy Node at the end of the Node hierarchy that listens to KeyCodes
But is there a better solution, like inverting the propagation route?
EDIT:
The use case:
A popup-like node that blocks other nodes would need to listens to the ESC key or focusProperty() so that it can close itself.
There's two ways you can affect events:
Use the Node.addEventFilter(...) method to register a filter. A filter will execute on the capturing phase of the event (as the window is getting more specific, determining which Nodes should get the event).
Use the Node.addEventHandler(...) method to register a handler. The handler will execute starting at the most specific node found in the capturing phase, heading down until it is consumed.
So in the capturing phase, a stack is created. Starting with the window (topmost parent), each node that this event could potentially execute on is added to the stack (ending with the bottom most child). A filter can interrupt this process, or just execute an event during this process.
In the bubbling phase, the event handlers will start firing from the top of the stack (created in the capturing phase) until the stack is empty or the event is consumed.
In your case, you really shouldn't have anything to worry about. If any node cares about processing the "ESC" event, they will do so in the bubbling phase (and they should consume the event to prevent further processing). You can see this behavior in the ComboBox. If they don't care, it will bubble up to your Scene and that handler will execute. Just make sure any custom code you create that processes an "ESC" press also consumes that event.
For more information, there is a explanation and tutorial here: http://docs.oracle.com/javafx/2/events/jfxpub-events.htm
And here is some sample code demonstrating the Escape functionality. Pressing ESC while focused on the ComboBox will not cause the application to close, while it will close with the other controls.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.converter.DefaultStringConverter;
public class FXEventFiltering extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(final Stage stage) throws Exception {
//All the controls are added here
VBox box = new VBox();
ComboBox<String> dropdown = new ComboBox<>();
TextField field = new TextField();
CheckBox check = new CheckBox("Check");
RadioButton radio = new RadioButton("Radio!");
TextArea area = new TextArea();
TableView<String> table = new TableView<String>(FXCollections.observableArrayList(new String[]{"one","two"}));
TableColumn<String, String> tc = new TableColumn<String, String>("Column1");
tc.setEditable(true);
tc.setCellFactory(TextFieldTableCell.<String,String>forTableColumn(new DefaultStringConverter()));
tc.setCellValueFactory(new Callback<CellDataFeatures<String,String>, ObservableValue<String>>(){
#Override
public ObservableValue<String> call(CellDataFeatures<String, String> arg0) {
return new SimpleStringProperty(arg0.getValue());
}});
table.getColumns().add(tc);
box.getChildren().addAll(dropdown, field, check, radio, area, table);
//Setting up your scene
Scene scene = new Scene(box);
stage.setScene(scene);
scene.addEventHandler(KeyEvent.ANY, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
System.out.println("KEYS!" + event.getEventType().getName());
switch (event.getCode()) {
case ESCAPE:
System.out.println("Escape!");
stage.hide();
event.consume();
break;
default:
break;
}
}
});
box.requestFocus(); // Removing default focus
stage.show();
}
}
Maybe you could loop over all nodes after catching the event in the scene to find out which node has actual focus? Then you could call node method to close?
I want to create a switch button like above
I am a swt developer where i used to get this widget Switchbutton.
Can i have something similar in javafx
First inclination is to extend the JavaFX Label and add a Button as a graphic and a SimpleBooleanProperty for listening. Set an ActionEvent handler on the button that toggles the Label's text, style, and graphic content alignment. The code below will get you started and you can play with styling and bounding.
package switchbutton;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
public class SwitchButton extends Label
{
private SimpleBooleanProperty switchedOn = new SimpleBooleanProperty(true);
public SwitchButton()
{
Button switchBtn = new Button();
switchBtn.setPrefWidth(40);
switchBtn.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent t)
{
switchedOn.set(!switchedOn.get());
}
});
setGraphic(switchBtn);
switchedOn.addListener(new ChangeListener<Boolean>()
{
#Override
public void changed(ObservableValue<? extends Boolean> ov,
Boolean t, Boolean t1)
{
if (t1)
{
setText("ON");
setStyle("-fx-background-color: green;-fx-text-fill:white;");
setContentDisplay(ContentDisplay.RIGHT);
}
else
{
setText("OFF");
setStyle("-fx-background-color: grey;-fx-text-fill:black;");
setContentDisplay(ContentDisplay.LEFT);
}
}
});
switchedOn.set(false);
}
public SimpleBooleanProperty switchOnProperty() { return switchedOn; }
}
Couldn't this be done with two toggle buttons that are bound together (bind()) where each button get's it's own CSS styling? It actually seems like the CSS would be the tricky (but doable) part to get right.
Then you would just have your app listen to the toggle button of the two that actually does what you want?
The controls FX project provides a switch button like you seek.