To ease the use of my JavaFx application I want to allow the user to define a keyboard combination / shortcut to trigger the most important action of the application.
I know how to define a KeyCodeCombination in code and set it as Accelerator or use it in an KeyEvent listener but instead of hard-coding it, I would like to allow the user to define his own KeyCodeCombination by simply pressing it on his keyboard in a certain settings dialog.
Basically something along this pseudocode:
// how would I implement the next two lines
Dialog dialog = new KeyboardShortcutDefinitionDialog();
KeyCombination shortcut = dialog.recordKeyboardShortcut();
// I know how to do the rest from here
shortcutLabel.setText(shortcut.toString());
SettingsManager.storeShortcut(shortcut);
Application.setupShortcut(shortcut);
Here's a small example of listening for a KEY_PRESSED event and building a KeyCombination out of it.
import java.util.ArrayList;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyCombination.Modifier;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
var label = new Label();
label.setFont(Font.font("Segoe UI", 15));
primaryStage.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
if (!event.getCode().isModifierKey()) {
label.setText(createCombo(event).getDisplayText());
}
});
primaryStage.setScene(new Scene(new StackPane(label), 500, 300));
primaryStage.setResizable(false);
primaryStage.show();
}
private KeyCombination createCombo(KeyEvent event) {
var modifiers = new ArrayList<Modifier>();
if (event.isControlDown()) {
modifiers.add(KeyCombination.CONTROL_DOWN);
}
if (event.isMetaDown()) {
modifiers.add(KeyCombination.META_DOWN);
}
if (event.isAltDown()) {
modifiers.add(KeyCombination.ALT_DOWN);
}
if (event.isShiftDown()) {
modifiers.add(KeyCombination.SHIFT_DOWN);
}
return new KeyCodeCombination(event.getCode(), modifiers.toArray(Modifier[]::new));
}
}
Related
I have this code sample that implement input mask to TextField:
package com.example;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) {
TextField field = new TextField() {
#Override public void replaceText(int start, int end, String text) {
super.replaceText(start, end, "#");
}
};
Button button = new Button("Show Text");
button.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent event) {
System.out.println("TEXT: " + field.getText());
}
});
VBox root = new VBox(20);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(field, button);
Scene scene = new Scene(root, 500, 500);
stage.setScene(scene);
stage.show();
}
}
Whenever I type the word "LIFE" in the TextField and press the button, the output always returns TEXT: ####, I want the output to return TEXT: LIFE. It seems like getText is not working. How to fix this?
Your approach doesn't work, because the replaceText method calls setText; so the implementation in your subclass causes the text field's textProperty to contain only '#' characters. Hence when you call getText(), you get a string with all '#' characters.
If you want to use a subclass of TextField like that, you would have to keep track of the "real" text elsewhere, which would get quite difficult. (Also, I think your implementation doesn't behave properly with copy and paste, and fixing that would be a bit tricky too.)
Probably the way to do this is to use a PasswordField, and replace it with a TextField when you want to show the text.
Here's a simple example:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class RevealPasswordExample extends Application {
#Override
public void start(Stage primaryStage) {
PasswordField passwordField = new PasswordField();
TextField textField = new TextField();
passwordField.textProperty().bindBidirectional(textField.textProperty());
StackPane textContainer = new StackPane(passwordField);
CheckBox showText = new CheckBox("Show text");
showText.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected) {
textContainer.getChildren().setAll(textField);
} else {
textContainer.getChildren().setAll(passwordField);
}
});
VBox root = new VBox(5, textContainer, showText);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(24));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Actually getText() is working properly, that's why you got #### at the output.
The thing is you have used replaceText() method, which will replace all the characters of your text field to '#' from the start to end. I donno why u have used that. If you don't wanna show the string typed in the field try it as a password field like,
PasswordField field = new PasswordField();
field.getText();
by using this also you can get the field text but it won't be visible to the user.
I've made my own TimePicker that is supposed to work very much like DatePicker. I would like to know the best way to handle an event such as selecting a time and confirming it from the PopupWindow.
I could:
Make my TimePicker's popup node (a separate FXML and controller) define an interface and force the TimePicker parent to implement the methods to handle the selected date. (I'd MUCH like to avoid using interfaces in this manner. It seems like a terribly way to do things.)
Register some kind of custom EventHandler and listener to the popup window? Then, if I click OKAY after selecting a date from the PopupWindow, an event can be fired all the way up to the TimePicker.
Implement some kind of callback-like function. In android, for example, there were options for going to another screen solely to retrieve a result. I'm not sure if JavaFX has that kind of thing. The screens are quite separated from each other.
Just expose a ReadOnlyProperty representing the value. The user of your popup can then just observe the property.
Here's a proof of concept using a DatePicker:
import java.time.LocalDate;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Popup;
import javafx.stage.PopupWindow;
import javafx.stage.Stage;
public class DatePickerPopupExample extends Application {
#Override
public void start(Stage primaryStage) {
Label dateLabel = new Label(LocalDate.now().toString());
Button changeButton = new Button("Change");
HBox root = new HBox(5, dateLabel, changeButton);
root.setAlignment(Pos.CENTER);
changeButton.setOnAction(event -> {
DatePickerPopup popup = new DatePickerPopup();
popup.valueProperty().addListener((obs, oldDate, newDate) -> {
dateLabel.setText(newDate.toString());
});
Bounds buttonBds = changeButton.getBoundsInLocal();
Point2D loc = changeButton.localToScreen(buttonBds.getMaxX(), buttonBds.getMinY());
popup.showPopup(primaryStage, loc.getX(), loc.getY());
});
Scene scene = new Scene(root, 250, 150);
primaryStage.setScene(scene);
primaryStage.show();
}
public class DatePickerPopup {
private final ReadOnlyObjectWrapper<LocalDate> value = new ReadOnlyObjectWrapper<>();
private final Popup popup ;
public ReadOnlyObjectProperty<LocalDate> valueProperty() {
return value.getReadOnlyProperty();
}
public final LocalDate getValue() {
return valueProperty().get();
}
public DatePickerPopup(LocalDate date) {
value.set(date);
DatePicker picker = new DatePicker(date);
Button okButton = new Button("OK");
okButton.setOnAction(event -> {
popup.hide();
value.set(picker.getValue());
});
Button cancelButton = new Button("Cancel");
cancelButton.setOnAction(event -> {
popup.hide();
});
BorderPane root = new BorderPane();
root.setCenter(picker);
HBox buttons = new HBox(5, okButton, cancelButton);
buttons.setPadding(new Insets(5));
buttons.setAlignment(Pos.CENTER);
root.setBottom(buttons);
popup = new Popup();
popup.getContent().add(root);
}
public DatePickerPopup() {
this(LocalDate.now());
}
public void showPopup(Stage owner, double x, double y) {
popup.show(owner, x, y);
}
}
public static void main(String[] args) {
launch(args);
}
}
Is there any way to translate somethings like pop up windows(that are not Node) in java FX ?
for example fade transition , translate transition or any timeline transition .... .
Thank's
Create a property and use a Timeline to "animate" the property. Register a listener with the property and update the window when its value changes.
For example:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TranslateWindowExample extends Application {
#Override
public void start(Stage primaryStage) {
Button moveButton = new Button("Move");
moveButton.setOnAction(event -> {
double currentX = primaryStage.getX() ;
DoubleProperty x = new SimpleDoubleProperty(currentX);
x.addListener((obs, oldX, newX) -> primaryStage.setX(newX.doubleValue()));
KeyFrame keyFrame = new KeyFrame(Duration.seconds(1), new KeyValue(x, currentX + 100));
Timeline animation = new Timeline(keyFrame);
animation.play();
});
StackPane root = new StackPane(moveButton);
Scene scene = new Scene(root, 250, 150);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Can you give an example of what do you call popup window ?
For instance, you can create a new stage on the top of your main stage.
You can also use some Tooltip class to add some text over the top of your window.
If you want more classical windows, you should have a look to Alert class.
Anthony
I am using the following ControlFX project. Hence, created a Dialogs.java class in my package and pasted the code from there.
Since I am not Inside the package org.controlsfx.dialog , I have to do the following:
import org.controlsfx.dialog.LightweightDialog;
And I am getting the following error as shown in the image below:
When I went inside the package org.controlsfx.dialog and opened, LightweightDialog.class,
I wasn't able to make the class public.
How should I overcome this situation? Please advise.
If the class is not public, it is not part of the public API, so it's not intended (or really possible) for you to use.
To use a lightweight dialog in ControlsFX, you can either use the Dialogs class API and call the lightweight() method as part of the creation of your dialog, or you can call one of the Dialog constructors which takes a flag for the lightweight property.
Here's a complete example using the Dialogs fluent API:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.dialog.Dialogs;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,600,400);
TabPane tabPane = new TabPane();
Tab tab1 = new Tab("Tab 1");
BorderPane tab1Root = new BorderPane();
Button showDialogButton = new Button("Enter message...");
VBox messages = new VBox(3);
HBox buttons = new HBox(5);
buttons.setAlignment(Pos.CENTER);
buttons.setPadding(new Insets(5));
buttons.getChildren().add(showDialogButton);
tab1Root.setBottom(buttons);
ScrollPane messageScroller = new ScrollPane();
messageScroller.setContent(messages);
tab1Root.setCenter(messageScroller);
tab1.setContent(tab1Root);
Tab tab2 = new Tab("Tab 2");
tab2.setContent(new TextField("This is tab 2"));
tabPane.getTabs().addAll(tab1, tab2);
showDialogButton.setOnAction(event -> {
String response = Dialogs.create()
.lightweight()
.owner(tab1)
.masthead("Enter a new message")
.message("Enter your new message:")
.showTextInput();
if (response != null) {
messages.getChildren().add(new Label(response));
}
});
root.setCenter(tabPane);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Using the Dialog constructor you'd do something like this, though it's a lot more work:
// params are owner, title, lightweight:
Dialog dialog = new Dialog(someNode, "Dialog", true);
// lots of code here to configure dialog...
Action response = dialog.show();
The real beauty of ControlsFX is the very comprehensive documentation. Just check the Javadocs for Dialogs and for Dialog.
If you create a binding between a JavaFX TextField and a property, then this binding is invalidated on every keystroke, which causes a change to the text.
If you have a chain of bindings the default behavior could cause problems, because in the middle of the editing values may be not valid.
Ok, I know I could create an uni-directional binding from the property to the textfield and register a change listener to get informed when the cursor leaves the field and update the property manually if necessary.
Is there an easy, elegant way to change this behavior so that the binding is only invalidated when the editing is complete, e.g. when the cursor leaves the field?
Thanks
I think you've pretty much described the only way to do it. Here's about the cleanest way I can see to implement it (using Java 8, though it's easy enough to convert the lambdas back to be JavaFX 2.2 compatible if you need):
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CommitBoundTextField extends Application {
#Override
public void start(Stage primaryStage) {
TextField tf1 = new TextField();
createCommitBinding(tf1).addListener((obs, oldText, newText) ->
System.out.printf("Text 1 changed from \"%s\" to \"%s\"%n", oldText, newText));
TextField tf2 = new TextField();
createCommitBinding(tf2).addListener((obs, oldText, newText) ->
System.out.printf("Text 2 changed from \"%s\" to \"%s\"%n", oldText, newText));
VBox root = new VBox(5, tf1, tf2);
Scene scene = new Scene(root, 250, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
private StringBinding createCommitBinding(TextField textField) {
StringBinding binding = Bindings.createStringBinding(() -> textField.getText());
textField.addEventHandler(ActionEvent.ACTION, evt -> binding.invalidate());
textField.focusedProperty().addListener((obs, wasFocused, isFocused)-> {
if (! isFocused) binding.invalidate();
});
return binding ;
}
public static void main(String[] args) {
launch(args);
}
}
I realize that I am a little late with a response, but thought this might be useful to someone.
When using TextFields, I often attach a TextFormatter to help validate entries. You can attach a listener to the formatters' valueProperty. That property is updated when the text is committed, rather than on every keystroke.
Here's an example of what I am talking about using a TextField specialized for integer inputs. When you make edits in the text field, the changes will be reflected in the Label when you tap Enter, lose focus by clicking the button, switch to a different window, and so on.
import javafx.application.Application;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
class IntTextField extends TextField {
private final IntegerProperty value;
TextFormatter<Integer> formatter;
public double getValue() {
return value.getValue();
}
public void setValue(int newValue) {
value.setValue(newValue);
}
public IntegerProperty valueProperty() {
return value;
}
public StringBinding getStringBinding () {
return value.asString();
}
IntTextField(int initValue) {
value = new SimpleIntegerProperty(initValue);
setText(initValue + "");
formatter = new TextFormatter(new IntegerStringConverter(), initValue);
formatter.valueProperty().addListener((ObservableValue<? extends Integer> obs,
Integer oldValue, Integer newValue) -> value.setValue(newValue));
setTextFormatter(formatter);
}
IntTextField() {
this(0);
}
}
public class TFBindingDemo extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("TFBindingDemo");
IntTextField intTextField = new IntTextField(12345);
intTextField.setMaxWidth(150);
Label label = new Label("Type in the TextField");
label.textProperty().bind(intTextField.getStringBinding());
Button removeFocusButton = new Button("Click Here to Remove Focus");
VBox root = new VBox(20, intTextField, label, removeFocusButton);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
Scene scene = new Scene(root, 325, 200);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}