I am trying to customize a DatePicker and would like to add a red border to the TextField/Editor of the DatePicker while the user input is invalid and remove it again as soon as it becomes valid. The only problem I have with this is triggering the validation, I can't seem to get the fromString method in the converter to execute (which then updates a property and so on ..., this is all fine).
I was under the impression the fromString method would execute on user input, but apparently it does not, so my question basically comes down to "when does the fromString method execute".
It shouldn't matter when DatePicker uses the converter, but when you want the conversion to be checked. You could simply do this with a listener to the text property of the editor.
The binding in the following code registers such a listener:
#Override
public void start(Stage primaryStage) throws Exception {
DatePicker datePicker = new DatePicker();
TextField editor = datePicker.getEditor();
StringConverter<LocalDate> converter = datePicker.getConverter();
editor.styleProperty().bind(Bindings.createStringBinding(() -> {
try {
converter.fromString(editor.getText());
} catch (Exception e) {
return "-fx-background-color: red;";
}
return null;
}, editor.textProperty()));
Scene scene = new Scene(new VBox(datePicker), 200, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
Edit: the converter is used when the text is input is "commited" using Enter while focusing the editor.
Related
My controller class has a moveButton method that on button click moves the button to a new location. This works fine and is called by a number of buttons which do the same thing. I want to add a key listener so when a button has been clicked once, until a different button is clicked, the user can use the up arrow to move the button (ie call the same moveButton function). The below is how I have tried to implement it, I also tried putting the key listener in the initialize method but neither seem to be working. Any advice would be greatly appreciated!
public void moveButton(ActionEvent actionEvent) {
Button buttonPressed = (Button) actionEvent.getSource();
double newAnchor = getNewAnchor(AnchorPane.getBottomAnchor(buttonPressed)) // separate method that returns new anchor location
AnchorPane.setBottomAnchor(buttonPressed, newAnchor);
buttonPressed.getScene().setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.UP){
moveButton(actionEvent);
}
}
});
}
Don't treat the events like data that you need to pass around. Use them as triggers to do work. Generally, don't write generic event handlers that are called from multiple events and multiple nodes. Write short event handlers that just call methods to do something, and pass them the minimum from the event that they need to do the job.
If you do this, then it changes your thinking about how all of this stuff works and then it's just plain old Java, with no magic. And it's simple:
public class MoveButton extends Application {
private Node activeButton;
private Pane pane;
#Override
public void start(Stage primaryStage) throws Exception {
pane = new Pane();
Scene scene = new Scene(pane, 1200, 800);
primaryStage.setScene(scene);
primaryStage.show();
Button button1 = new Button("Button 1");
Button button2 = new Button("Button 2");
button2.setTranslateX(80);
button1.setOnAction(evt -> buttonClick(button1));
button2.setOnAction(evt -> buttonClick(button2));
pane.getChildren().addAll(button1, button2);
pane.setOnKeyPressed(evt -> moveButton(evt.getCode()));
}
private void moveButton(KeyCode keyCode) {
switch (keyCode) {
case UP -> activeButton.setTranslateY(activeButton.getTranslateY() - 30);
case RIGHT -> activeButton.setTranslateX(activeButton.getTranslateX() + 30);
case DOWN -> activeButton.setTranslateY(activeButton.getTranslateY() + 30);
case LEFT -> activeButton.setTranslateX(activeButton.getTranslateX() - 30);
}
}
private void buttonClick(Node button) {
activeButton = button;
pane.requestFocus();
}
}
JavaFX does not execute events like the ActionEvent for Button or CheckBox, if a modifier key like CTRL or SHIFT is pressed. As far as I understand this behavior is implemented in ButtonBehavior (e.g. note the expression ! keyDown in the following method from that class):
#Override public void mouseReleased(MouseEvent e) {
// if armed by a mouse press instead of key press, then fire!
final ButtonBase button = getControl();
if (! keyDown && button.isArmed()) {
button.fire();
button.disarm();
}
}
First of all, I do not really understand the reason for this. What is the purpose of not firing a button if a key is pressed?
This is my use-case: I want to implement a checkbox that can be checked/unchecked as normal. It will toggle some state in a model. But it should have an additional feature: if the user presses some key like CTRL while checking/unchecking with the mouse, an additional flag called "locked" or "protected" should be set in the model, which will prevent that the state can be overwritten by some other logic of the application.
This should give an idea about the use-case, but if not it doesn't really matter for my actual question: How can I make it possible that a CheckBox can still be toggled (or a Button be pressed) even though the user presses a modifier key?
Thanks for your help!
That is odd you can implement it yourself like so
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
CheckBox checkBox = new CheckBox();
checkBox.setOnMouseClicked(event -> {
if(event.isControlDown()) {
System.out.print("Control down click ");
checkBox.setSelected(!checkBox.isSelected());
}
else
System.out.print("Normal click ");
System.out.println("Checked Status:"+checkBox.isSelected());
});
Button button = new Button("Button");
button.setOnMouseClicked(event -> {
if(event.isControlDown())
System.out.println("Control down click");
else
System.out.println("Normal click");
});
vBox.getChildren().addAll(new Label("Click the box"),checkBox,button);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
The output for CheckBox:
Normal click Checked Status:true
Normal click Checked Status:false
Control down click Checked Status:true
Control down click Checked Status:false
The output for Button:
Normal click
Control down click
Just as the title says, how do I stop shortcut keys (accelerators) being picked up as key events in TextArea? I have tried the method suggested here with different modifications: TextArea ignore KeyEvent in JavaFX with no luck.
If you want to stop specific accelerators from working when the TextArea has focus simply add an event filter for KEY_PRESSED events.
public class AcceleratorFilter implements EventHandler<KeyEvent> {
// blacklist of KeyCombinations
private final Set<KeyCombination> combinations;
public AcceleratorFilter(KeyCombination... combinations) {
this.combinations = Set.of(combinations);
}
#Override
public void handle(Event event) {
if (combinations.stream().anyMatch(combo -> combo.match(event)) {
event.consume();
}
}
}
TextArea area = new TextArea();
area.addEventFilter(KeyEvent.KEY_PRESSED, new AcceleratorFilter(
KeyCombination.valueOf("shortcut+o"),
KeyCombination.valueOf("shortcut+s") // etc...
));
If you want to indiscriminately block all accelerators registered with the Scene then you can query the Scenes accelerators and consume the KeyEvent if appropriate.
TextArea area = new TextArea();
area.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
var scene = ((Node) event.getSource()).getScene();
// #getAccelerators() = ObservableMap<KeyCombination, Runnable>
var combos = scene.getAccelerators().keySet();
if (combos.stream().anyMatch(combo -> combo.match(event)) {
event.consume();
}
});
This latter option may cause issues if you're not careful. For instance, if you have a default Button in the Scene then the above event filter may interfere with the ENTER key. Also, this option won't necessarily stop things like shortcut+c, shortcut+v, etc. because those shortcuts are registered with the TextInputControl, not the Scene.
A JavaFX MenuItem can respond to most KeyPress events by setting an ActionEvent EventHandler. However, while the event handler does catch a KeyPress of KeyCode.ENTER, it does not catch a KeyCode.TAB KeyPress event. Apparently, some key events like TAB are handled at a deeper level. For example, the arrow keys enable traversal of the menu.
My ContextMenu is a list of completions of an email address string the user has started typing in a TextField. The users want to press the arrow keys to select the desired item, and the TAB key to execute the completion.
I can attach an event handler to the ContextMenu itself and catch the TAB keypress. But the event's Source is then the ContextMenu, and I can find no variables in the ContextMenu indicating which MenuItem was highlighted when the TAB key was pressed. MenuItem allows css style to control appearance of the menu item in focus, but it does not have any properties telling whether it is in focus or not.
I have tried futzing with the EventDispatchChain via MenuItem buildEventDispatchChain() to no avail. There seems to be no way to intercept the TAB KeyPress or otherwise determine which menu item was in focus when the TAB key was pressed.
Any suggestions?
If I get this right, you want to override the default keypressed listener to add your own response, so for that we have to find where it's applied.
To get this working, we've got to get our hands dirty with private API...
ContextMenu skin (ContextMenuSkin) uses a ContextMenuContent object, as a container with all the items. Each of these items are also in a ContextMenuContent.MenuItemContainer container.
We can override the keypressed listener on the parent container, while we can add a focusedProperty listener to the items on the items container.
Using this private API
import com.sun.javafx.scene.control.skin.ContextMenuContent;
this is working for me:
private ContextMenuContent.MenuItemContainer itemSelected=null;
#Override
public void start(Stage primaryStage) {
MenuItem cmItem1 = new MenuItem("Item 1");
cmItem1.setOnAction(e->System.out.println("Item 1"));
MenuItem cmItem2 = new MenuItem("Item 2");
cmItem2.setOnAction(e->System.out.println("Item 2"));
final ContextMenu cm = new ContextMenu(cmItem1,cmItem2);
Scene scene = new Scene(new StackPane(), 300, 250);
scene.setOnMouseClicked(t -> {
if(t.getButton()==MouseButton.SECONDARY || t.isControlDown()){
cm.show(scene.getWindow(),t.getScreenX(),t.getScreenY());
ContextMenuContent cmc= (ContextMenuContent)cm.getSkin().getNode();
cmc.setOnKeyPressed(ke->{
switch (ke.getCode()) {
case UP: break;
case DOWN: break;
case TAB: ke.consume();
if(itemSelected!=null){
itemSelected.getItem().fire();
}
cm.hide();
break;
default: break;
}
});
VBox itemsContainer = cmc.getItemsContainer();
itemsContainer.getChildren().forEach(n->{
ContextMenuContent.MenuItemContainer item=(ContextMenuContent.MenuItemContainer)n;
item.focusedProperty().addListener((obs,b,b1)->{
if(b1){
itemSelected=item;
}
});
});
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
Excellent! Thank you #jose! I ended up writing somewhat different code but
the key is using com.sun.javafx.scene.control.skin.ContextMenuContent, which provides
access to the ContextMenuContent.MenuItemContainer objects that hold the MenuItems.
In order to not break the existing UP/DOWN key behavior, I added a new handler
to the ContextMenuContent object; this handler only consumes the TAB KeyPress and
everthing else passes through to their normal handlers.
Looking at the ContextMenuContent class, I borrowed their existing method for
finding the focused item, so didn't have to add focusedProperty listeners.
Also, I'm on Java 1.7 and don't have lambdas and I use a very basic programming style.
public class MenuItemHandler_CMC <T extends Event> implements EventHandler {
public ContextMenuContent m_cmc;
public AddressCompletionMenuItemHandler_CMC(ContextMenuContent cmc){
m_cmc = cmc;
}
#Override
public void handle(Event event){
KeyEvent ke = (KeyEvent)event;
switch(ke.getCode()){
case TAB:
ke.consume();
MenuItem focused_menu_item = findFocusedMenuItem();
if(focused_menu_item != null){
focused_menu_item.fire();
}
break;
default: break;
}
}
public MenuItem findFocusedMenuItem() {
VBox items_container = m_cmc.getItemsContainer();
for (int i = 0; i < items_container.getChildren().size(); i++) {
Node n = items_container.getChildren().get(i);
if (n.isFocused()) {
ContextMenuContent.MenuItemContainer menu_item_container = (ContextMenuContent.MenuItemContainer)n;
MenuItem menu_item = menu_item_container.getItem();
return menu_item;
}
}
return null;
}
}
...Attach the additional handler
if(m_context_menu.getSkin() != null){
ContextMenuContent cmc = (ContextMenuContent)m_context_menu.getSkin().getNode();
MenuItemHandler_CMC menu_item_handler_cmc = new MenuItemHandler_CMC(cmc);
cmc.addEventHandler(KeyEvent.KEY_PRESSED, menu_item_handler_cmc);
}
I'm trying to set a variable value when a MenuItem i chosen in a MenuButton object.
I've tried to search for this but I've came up empty handed.
Here's the code to set the MenuItems:
private ObservableList<MenuItem> templateMenuItems = FXCollections.observableArrayList();
#FXML private MenuButton menu = new MenuButton();
#FXML
protected void getTemplates() throws IOException {
CaspReturn tls = this.socket.runCmd(new Tls(""));
String tlsList = tls.getResponse();
String[] tlsListSplitt = tlsList.split("\\n");
for (int i = 0; i < tlsListSplitt.length; i++) {
String[] tlsLine = tlsListSplitt[i].split("\"");
this.templateMenuItems.add(new MenuItem(tlsLine[1]));
}
this.menu.getItems().setAll(this.templateMenuItems);
}
I'm not sure how to write the code to get the text from a menuItem or which field in scenebuilder the method should be in.
It's not clear what your asking, but I'll assume that you want to know the text of a menu item when it is clicked. To do that, you need to add an event handler onto the menu item. The following is clipped from the JavaDoc for ContextMenu:
MenuItem item1 = new MenuItem("About");
item1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("About");
}
});
You can get the event source, cast in to MenuItem and then get the text from that.
There's a real problem with your code the way it's written, though. You have calls to an external database in getTemplates, and as it's implemented as an #FXML element that almost guarantees that it'll be run on the FXAT, which is really, really bad.
I'd refactor that so that the database access is in a Task, and then the MenuItem creation is a handler for the onSucceeded event of the Task. Then you need instantiate a ContextMenu and install the MenuItem's on it in that event handler.
The getTemplates() method should be called as the onAction event for the button.