How do I bind CheckMenuItem.selectedProperty() to another ObservableValue? - java

I want to bind a CheckMenuItem's selectedProperty to another observable value, like cmi.selectedProperty().bind(myObs). However, this is not possible, since the framework sets the selection property when the check menu item is clicked (see line 1394 of ContextMenuContent.java).
Is there a way to intercept the click—so that I can do my own custom processing—and still bind the selection property to another observable?
I suppose I'm thinking of the click as a request to update some state. The user clicks the menu item, then the program attempts to change some state accordingly, and the selection changes if the state successfully updated. Under 'normal' conditions, the check should toggle upon every click; however, if something bad happens, I'd prefer that the check doesn't toggle and instead reflects the true state of the program.

One way to do this (without getting into writing a skin for the menu item) is to roll your own menu item with a graphic. You can just use a region for the graphic and steal the CSS from the standard modena stylesheet. Then bind the visible property of the graphic to the observable value, and toggle the observable value in the menu item's action handler:
import java.util.Random;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
public class VetoableMenuItemWithCheck extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
MenuBar menuBar = new MenuBar() ;
Menu choices = new Menu("Choices");
// observable boolean value to which we're going to bind:
BooleanProperty selected = new SimpleBooleanProperty();
// graphic for displaying checkmark
Region checkmark = new Region();
checkmark.getStyleClass().add("check-mark");
// bind visibility of graphic to observable value:
checkmark.visibleProperty().bind(selected);
MenuItem option = new MenuItem("Option", checkmark);
choices.getItems().add(option);
Random rng = new Random();
// when menu item action occurs, randomly fail (with error alert),
// or update boolean property (which will result in toggling check mark):
option.setOnAction(e -> {
if (rng.nextDouble() < 0.25) {
Alert alert = new Alert(AlertType.ERROR, "I'm sorry Dave, I'm afraid I can't do that", ButtonType.OK);
alert.showAndWait();
} else {
selected.set(! selected.get());
}
});
menuBar.getMenus().add(choices);
root.setTop(menuBar);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add("check-menu.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and
check-menu.css:
.check-mark {
-fx-background-color: -fx-mark-color;
-fx-shape: "M0,5H2L4,8L8,0H10L5,10H3Z";
-fx-scale-shape: false;
-fx-padding: 0em 0.11777em 0em 0em;
}
There may be a simpler approach, but this seems not too bad.
A version for a vetoable radio menu item could follow the same basic idea, but with
ObjectProperty<MenuItem> selectedItem = new SimpleObjectProperty<>();
and then for each menu item do
checkmark.visibleProperty().bind(selectedItem.isEqualTo(option));
option.setOnAction(e -> {
if (successful()) {
selectedItem.set(option);
}
});

Related

JavaFX: Hardcode a "Ctrl+C" keypress

I have a JavaFX application that has various TextField widgets in the main frame. I have a MenuBar that includes the MenuItem objects "Copy" and "Paste" like a standard production application would have. Since any or none of the various TextField objects could be selected at any given time, it seems easier to just hardcode a "Ctrl+C" or "Ctrl+V" key press in the setOnAction events of the "Copy" and "Paste" MenuItem objects rather than use a Clipboard object and loop iterating through all TextFields to find the highlighted text (if any).
Is there a way to hardcode this key press action in Java? I looked into the KeyCombination class but it does not actually trigger the action described by the given key combination.
I think by "Since any or none of the various TextField objects could be selected at any given time" you are referring to which (if any) text field has the keyboard focus.
You can easily get this information from the scene: just do
Node focusOwner = scene.getFocusOwner();
if (focusOwner instanceof TextField) {
TextField textField = (TextField) focusOwner ;
String selectedText = textField.getSelectedText();
// ...
}
Note also that TextInputControl defines a copy() method that copies the selected text to the system clipboard. (Similarly, there's a paste() method too.) So you can leverage those to make the functionality easy.
Here's a SSCCE:
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FocusMenuTest extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
VBox textFields = new VBox(5, new TextField("One"), new TextField("Two"), new TextField("Three"));
MenuBar menuBar = new MenuBar();
Menu edit = new Menu("Edit");
MenuItem copy = new MenuItem("Copy");
copy.setOnAction(e -> {
Node focusOwner = menuBar.getScene().getFocusOwner();
if (focusOwner instanceof TextInputControl) {
((TextInputControl)focusOwner).copy();
}
});
MenuItem paste = new MenuItem("Paste");
paste.setOnAction(e -> {
Node focusOwner = menuBar.getScene().getFocusOwner();
if (focusOwner instanceof TextInputControl) {
((TextInputControl)focusOwner).paste();
}
});
menuBar.getMenus().add(edit);
edit.getItems().addAll(copy, paste);
root.setCenter(textFields);
root.setTop(menuBar);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Creating a Autocomplete search form in javafx

To get an idea of what I want
When the textfield is clicked, the dropdown appears with suggestions that are filtered out as the user types in the text field. The height of the box should also adjust real-time to either contain all of the items, or a maximum of 10 items.
I managed to get this somewhat working using a ComboBox, but it felt a bit rough around the edges and it didn't seem possible to do what I wanted (The dropdown doesn't resize unless you close it and re-open it).
New idea, have a text field and then show a VBox of buttons as the dropdown. The only problem is that I don't know how to position the dropdown so that it doest stay in the noral flow so it can overlay any exisiting elements below the text field. Any ideas?
Please consider this Example, you can take the idea and apply it to your project.
import javafx.application.Application;
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.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class SearchFormJavaFX extends Application{
#Override
public void start(Stage ps) throws Exception {
String[] options = {"How do I get a passport",
"How do I delete my Facebook Account",
"How can I change my password",
"How do I write some code in my question :D"};
// note that you don't need to stick to these types of containers, it's just an example
StackPane root = new StackPane();
GridPane container = new GridPane();
HBox searchBox = new HBox();
////////////////////////////////////////////////////
TextField text = new TextField();
// add a listener to listen to the changes in the text field
text.textProperty().addListener((observable, oldValue, newValue) -> {
if(container.getChildren().size()>1){ // if already contains a drop-down menu -> remove it
container.getChildren().remove(1);
}
container.add(populateDropDownMenu(newValue, options),0,1); // then add the populated drop-down menu to the second row in the grid pane
});
// those buttons just for example
// note that you can add action listeners to them ..etc
Button close = new Button("X");
Button search = new Button("Search");
searchBox.getChildren().addAll(text,close,search);
/////////////////////////////////////////////////
// add the search box to first row
container.add(searchBox, 0, 0);
// the colors in all containers only for example
container.setBackground(new Background(new BackgroundFill(Color.GRAY, null,null)));
////////////////////////////////////////////////
root.getChildren().add(container);
Scene scene = new Scene(root, 225,300);
ps.setScene(scene);
ps.show();
}
// this method searches for a given text in an array of Strings (i.e. the options)
// then returns a VBox containing all matches
public static VBox populateDropDownMenu(String text, String[] options){
VBox dropDownMenu = new VBox();
dropDownMenu.setBackground(new Background(new BackgroundFill(Color.GREEN, null,null))); // colors just for example
dropDownMenu.setAlignment(Pos.CENTER); // all these are optional and up to you
for(String option : options){ // loop through every String in the array
// if the given text is not empty and doesn't consists of spaces only, as well as it's a part of one (or more) of the options
if(!text.replace(" ", "").isEmpty() && option.toUpperCase().contains(text.toUpperCase())){
Label label = new Label(option); // create a label and set the text
// you can add listener to the label here if you want
// your user to be able to click on the options in the drop-down menu
dropDownMenu.getChildren().add(label); // add the label to the VBox
}
}
return dropDownMenu; // at the end return the VBox (i.e. drop-down menu)
}
public static void main(String[] args) {
launch();
}
}
What you're trying to do has already been implemented, and is included in ControlsFx. It's open source, and I think it would suit you need. It looks some what like this
You can even add custom nodes to it, so that cross can be done too.
public void pushEmails(TextField Receptient) {
ArrayList<CustomTextField> list = new ArrayList<>();
for (int i = 0; i < Sendemails.size(); i++) {
CustomTextField logo=new CustomTextField(Sendemails.get(i));
ImageView logoView=new ImageView(new Image("/Images/Gmail.png"));
logo.setRight(logoView);
list.add(logo);
}
TextFields.bindAutoCompletion(Receptient, list);
}

JavaFX css tags

I'm trying to make a list of boxes which a user can select through with their mouse and when one box is selected, it highlights it with a color, and all the rest of the boxes turn white. Is there an equivalent to the css tag :target in javafx like there is an equivalent to :focus(:focused) or do I have to handle selecting items in a list my own way?
There is no built-in target pseudoclass, but there is an API for creating your own CSS PseudoClass objects.
Here is a simple example:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SelectableBoxes extends Application {
private static final PseudoClass SELECTED_PSEUDOCLASS = PseudoClass.getPseudoClass("selected");
private ObjectProperty<Pane> selectedBox = new SimpleObjectProperty<>();
#Override
public void start(Stage primaryStage) {
VBox container = new VBox(5);
container.setPadding(new Insets(20));
int numBoxes = 5 ;
for (int i = 0 ; i < numBoxes; i++) {
container.getChildren().add(createBox());
}
ScrollPane scroller = new ScrollPane(container);
Scene scene = new Scene(scroller, 400, 400);
scene.getStylesheets().add("selectable-boxes.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private Pane createBox() {
Pane pane = new Pane();
pane.setMinSize(50, 50);
pane.getStyleClass().add("box");
pane.setOnMouseClicked(e -> selectedBox.set(pane));
selectedBox.addListener((obs, oldSelection, newSelection) ->
pane.pseudoClassStateChanged(SELECTED_PSEUDOCLASS, newSelection == pane));
return pane ;
}
public static void main(String[] args) {
launch(args);
}
}
with the corresponding CSS file selectable-boxes.css:
.box {
-fx-border-width: 1 ;
-fx-border-color: black ;
}
.box:selected {
-fx-background-color: blue ;
}
This "answer" is to provide an alternate solution for the task you wish to accomplish (rather than directly answering your question regarding CSS tags).
For your task, you may wish to use ToggleButtons in a ToggleGroup, or a ListView.
Oracle provide a ToggleButton demo. By default a ToggleButton behaves a bit different from a radio button (e.g. it is possible to have nothing selected). If you want radio button style behavior, to ensure something is always selected, you can use the bit of code at: JDK-8090668
Need TogglePolicy for ToggleButton in ToggleGroup
toggleGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
public void changed(ObservableValue<? extends Toggle> ov, Toggle toggle, Toggle new_toggle) {
if (toggle != null && new_toggle == null) {
toggle.setSelected(true);
}
}
});
The in-built controls can be styled quite extensively using CSS to get custom looks. Refer to the modena.css file for default css styles for the controls which can be overriden.

How to cause a ComboBox EventHandler to be triggered when programmatically selecting items?

The ComboBox control has a method called setOnAction. This method takes in an EventHandler that is called as described by the documentation:
The ComboBox action, which is invoked whenever the ComboBox value
property is changed. This may be due to the value property being
programmatically changed, when the user selects an item in a popup
list or dialog, or, in the case of editable ComboBoxes, it may be when
the user provides their own input (be that via a TextField or some
other input mechanism.
When a Stage first loads, I don't want the ComboBox to default to an empty value, I want it to automatically select the first option in the ComboBox (if it has one). The getSelectionModel().selectFirst() methods does change the selection of the ComboBox, but it for some reason does not trigger the EventHandler. However, the EventHandler for a button that calls the exact same methods will cause the EventHandler to trigger. What am I doing wrong?
Here is a brief test case that shows this behavior using JDK 8u40:
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
public class Test extends Application {
public void start(Stage stage) throws Exception {
HBox pane = new HBox();
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().add("Hello");
comboBox.getItems().add("World");
comboBox.setOnAction((e) -> {
System.out.println(comboBox.getSelectionModel().getSelectedItem());
});
Button button = new Button("Select First");
button.setOnAction((e) -> {
comboBox.getSelectionModel().selectFirst();
});
comboBox.getSelectionModel().selectFirst();
pane.getChildren().add(comboBox);
pane.getChildren().add(button);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.show();
}
}
I don't entirely understand why this is necessary, but in order for the EventHandler passed to the setOnAction() method to trigger for the ComboBox control, the Stage must first be shown with the show() method.
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
public class Test extends Application {
public void start(Stage stage) throws Exception {
HBox pane = new HBox();
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().add("Hello");
comboBox.getItems().add("World");
comboBox.setOnAction((e) -> {
System.out.println(comboBox.getSelectionModel().getSelectedItem());
});
Button button = new Button("Select First");
button.setOnAction((e) -> {
comboBox.getSelectionModel().selectFirst();
System.out.println("The button did it!");
});
button.fire();
pane.getChildren().add(comboBox);
pane.getChildren().add(button);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.show();
comboBox.getSelectionModel().selectFirst();
}
}
This doesn't seem to be entirely true for all controls. In the above example, calling the fire() method on the button will trigger the EventHandler even before the stage is shown.

registering changelistener to group of nodes in javafx

Is there any way to add a changelistener to group of nodes for following changes?
For example, we can add a changelistener to a tabpane for getting tabselectedproperty.
I want to add changelistener a to a group of buttons for getting buttonActionedProperty! I want to get old button and new button....
Is there any way to do this?
When you compare the tabs in a tab pane to a collection of buttons, you're not really comparing like to like. A tab pane naturally has a sense of which tab is currently selected; buttons just generate events when they are pressed.
If you want your buttons to have a "selected" state, and want a collection of those grouped together so that only one is selected, then consider using ToggleButtons instead. You can put the toggle buttons into a ToggleGroup and register a listener with the toggle group's selectedToggle property:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ToggleButtonDemo extends Application {
#Override
public void start(Stage primaryStage) {
ToggleButton apples = new ToggleButton("Apples");
ToggleButton oranges = new ToggleButton("Oranges");
ToggleButton pears = new ToggleButton("Pears");
ToggleGroup fruitToggleGroup = new ToggleGroup();
fruitToggleGroup.getToggles().addAll(apples, oranges, pears);
fruitToggleGroup.selectedToggleProperty().addListener((obs, oldToggle, newToggle) ->
System.out.println("Selected toggle changed from "+oldToggle+" to "+newToggle));
HBox root = new HBox(5, apples, oranges, pears);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 75);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you really just want buttons, and don't have the notion of one of them being selected (I find it hard to see a use case for this), you can just create an ObjectProperty<Button> to store the last button on which an action occurred. Register an event listener with each button to update the property:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class LastActionTrackingDemo extends Application {
#Override
public void start(Stage primaryStage) {
Button apples = new Button("Apples");
Button oranges = new Button("Oranges");
Button pears = new Button("Pears");
ObjectProperty<Button> lastActionedButton = new SimpleObjectProperty<>();
EventHandler<ActionEvent> buttonActionHandler = event ->
lastActionedButton.set((Button) event.getSource());
apples.addEventHandler(ActionEvent.ACTION, buttonActionHandler);
oranges.addEventHandler(ActionEvent.ACTION, buttonActionHandler);
pears.addEventHandler(ActionEvent.ACTION, buttonActionHandler);
lastActionedButton.addListener((obs, oldButton, newButton) ->
System.out.println("Button changed from "+oldButton+" to "+newButton));
HBox root = new HBox(5, apples, oranges, pears);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 75);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Note there is a subtle different between the appearance of the two demos. The first (with toggle buttons) has a visual representation of which button is selected. The second does not. In both cases you can still set action listeners on the buttons if you need that functionality. There is also a (less subtle) difference in behavior: the toggle buttons can be "unselected"; so if you press the same toggle button twice, the selection goes back to null. This doesn't happen with the buttons.

Categories

Resources