Consider a tableview inside a JavaFX application. The tableview contains data about people:
id firstname lastname
1 John Doe
Each of the rows of the tableview can be used to instantiate a Person object:
new Person(Integer id, String firstname, String lastname).
Here is what I want to do:
I want to select one of the Persons inside the tableview and drag it with my mouse into a textarea. Inside the textarea I want to print the String:
1 John Doe.
I know that I have to implement three functions to achieve this: onDragDetected(), onDragOver() and onDragDropped().
I was thinking to start with:
public void onDragDetected(DragEvent dragevent) {
Person person = tvPersonData.getSelectionModel().getSelectedItem();
}
This works. But I have to somehow get my Person into the DragBoard object (dragevent.getDragboard()). I think that I do. And then maybe my other functions can use whatever is inside my dragboard.
Can anyone help me out here?
In order to use a custom object in a DragAndDrop gesture, you need to define a custom DataFormat for the object:
DataFormat personDataFormat = new DataFormat("com.package.Person");
You obviously need to replace the com.package.Person will the full path to the class you are creating this for.
Note also that in order for a class to be used as a DataFormat, that class must be Serializable. This is accomplished by implementing the Serializable interface in your Person object:
class Person implements Serializable {}
Another thing to remember is that JavaFX properties are not Serializable so if you use StringProperty in your Person object, this will not work; you'll need to create a wrapper class that holds the data you need.
Once you have your DataFormat and Person implementing Serializable, you can copy your Person object to the Clipboard just like any other content; you just need to specify the DataFormat for it:
ClipboardContent content = new ClipboardContent();
content.put(personDataFormat, tableView.getSelectionModel().getSelectedItem());
Dropping onto the TextArea is going to be similar. You'll define the DataFormat and then add the code to process as desired:
Person droppedPerson = (Person) db.getContent(personDataFormat);
textArea.appendText(droppedPerson.getName() + "\n");
Full example application below:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.Serializable;
import java.util.Arrays;
public class DragObject extends Application {
private final TableView<Person> tableView = new TableView<>();
private final TextArea textArea = new TextArea();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// **********************************************************************************************
// Create a basic layout
// **********************************************************************************************
VBox root = new VBox(5);
root.setAlignment(Pos.TOP_CENTER);
root.setPadding(new Insets(10));
// **********************************************************************************************
// Configure a regular TableView
// **********************************************************************************************
configureTableView();
tableView.setItems(sampleData());
tableView.setPrefHeight(200);
root.getChildren().add(tableView);
root.getChildren().add(textArea);
// **********************************************************************************************
// This method will configure our drag-and-drop functionality
// **********************************************************************************************
initDragAndDrop();
// **********************************************************************************************
// Set the Scene for the stage
// **********************************************************************************************
primaryStage.setScene(new Scene(root));
// **********************************************************************************************
// Configure the Stage
// **********************************************************************************************
primaryStage.setTitle("Test Application");
primaryStage.show();
}
private void configureTableView() {
// **********************************************************************************************
// Just a standard sample TableView
// **********************************************************************************************
TableColumn<Person, String> colName = new TableColumn<>("Name");
TableColumn<Person, String> colEmail = new TableColumn<>("Email");
colName.setCellValueFactory(new PropertyValueFactory<>("name"));
colEmail.setCellValueFactory(new PropertyValueFactory<>("email"));
tableView.getColumns().addAll(colName, colEmail);
}
private void initDragAndDrop() {
DataFormat personDataFormat = new DataFormat("Person");
// **********************************************************************************************
// Add drag and drop to the TableView
// **********************************************************************************************
tableView.setOnDragDetected(event -> {
// **********************************************************************************************
// Drag was detected, allow any type of transfer
// **********************************************************************************************
Dragboard db = tableView.startDragAndDrop(TransferMode.ANY);
// **********************************************************************************************
// Add the selected Person to the clipboard, using our custom DataFormat
// **********************************************************************************************
ClipboardContent content = new ClipboardContent();
content.put(personDataFormat, tableView.getSelectionModel().getSelectedItem());
db.setContent(content);
});
// **********************************************************************************************
// Add drag and drop to the TextArea
// **********************************************************************************************
textArea.setOnDragOver(event -> {
// **********************************************************************************************
// Person was dragged over the TextArea; accept it only if dragged from a node other than itself.
// In this case, this condition isn't necessary, but still good practice
// **********************************************************************************************
if (event.getGestureSource() != textArea
&& event.getDragboard().hasContent(personDataFormat)) {
// **********************************************************************************************
// For this example, we'll allow ANY transfer type again
// **********************************************************************************************
event.acceptTransferModes(TransferMode.ANY);
}
// **********************************************************************************************
// Consume the event. We'll handle placing content into the TextArea next
// **********************************************************************************************
event.consume();
});
// **********************************************************************************************
// Add handler to the TextArea to accept a Person object that's been dropped and add the person's
// name to the TextArea
// **********************************************************************************************
textArea.setOnDragDropped(event -> {
// **********************************************************************************************
// First, ensure the object being dropped was a Person object
// **********************************************************************************************
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasContent(personDataFormat)) {
// **********************************************************************************************
// Get a reference to the Person being dropped. Note that you need to
// cast the object to a Person so Java knows what object type you're
// working with.
// **********************************************************************************************
Person droppedPerson = (Person) db.getContent(personDataFormat);
textArea.appendText(droppedPerson.getName() + "\n");
success = true;
}
// **********************************************************************************************
// Complete the event
// **********************************************************************************************
event.setDropCompleted(success);
event.consume();
});
}
private ObservableList<Person> sampleData() {
// **********************************************************************************************
// Just some sample Persons for our TableView
// **********************************************************************************************
return FXCollections.observableList(Arrays.asList(
new Person("John Williams", "jwilliams#et.com"),
new Person("Howard Shore", "hshore#shire.com"),
new Person("Danny Elfman", "delfman#scissorpalace.org")));
}
}
RESULT:
Related
I have been trying to create a javafx.scene.control.TableView such that all the selection events are blocked when their origin is user interaction. In other words, it must be possible for me to programmatically alter the selection in a given table view.
I tried solutions from the following questions:
Setting the whole table view as mouse transparent (see article). This approach is unacceptable, because, for instance, user cannot change the width of the columns
Setting the selection model to null (see article). This one is unacceptable, because the currently selected row is not highlighted properly- see image below:
Originally, I wanted to decorate the default existing table view selection model with my own. Something like this was created:
private final class TableViewSelectionModelDecorator< S >extends TableViewSelectionModel< S >
{
private final TableViewSelectionModel< S > delegate;
private TableViewSelectionModelDecorator( TableViewSelectionModel< S > aDelegate )
{
super( aDelegate.getTableView() );
delegate = Objects.requireNonNull( aDelegate );
}
// Overriding the methods and delegating the calls to the delegate
}
The problem with my decorator is that the function getSelectedIndex() from the selection model is marked as final, which means I cannot override it and delegate the call to my decorated selection model. As a result, whenever a client asks for currently selected index the result is -1.
Requirements that I must meet:
Selection change events coming from either the mouse click or the keyboard (or any other input source) is blocked.
User must be able to interact with the table as long as the selection is not modified (e.g. changing the width of the columns)
Selected entry is properly highlighted (instead of just some frame around the selected index)
For now there is no multiselection support involved, but preferably I'd appreciate a solution that does support it.
Last note is I use Java 11.
Thanks for any pointers.
Please do consider the comments mentioned about the xy problem and other alternatives mentioned.
If you still want to solve this as the way you mentioned, you can give a try as below.
The idea is to
block all KEY_PRESSED events on tableView level and
set mouse transparent on tableRow level
so that we are not tweaking with any default selection logic. This way you can still interact with columns and scrollbar using mouse.
Below is the quick demo of the implementation:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableViewSelectionBlockingDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Person> persons = FXCollections.observableArrayList();
for (int i = 1; i < 11; i++) {
persons.add(new Person(i + "", "A" + i));
}
TableView<Person> tableView = new TableView<>();
TableColumn<Person, String> idCol = new TableColumn<>("Id");
idCol.setCellValueFactory(param -> param.getValue().idProperty());
idCol.setPrefWidth(100);
TableColumn<Person, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(param -> param.getValue().nameProperty());
nameCol.setPrefWidth(150);
tableView.getColumns().addAll(idCol,nameCol);
tableView.setItems(persons);
// Selection Blocking logic
tableView.addEventFilter(KeyEvent.KEY_PRESSED, e->e.consume());
tableView.setRowFactory(personTableView -> new TableRow<Person>(){
{
setMouseTransparent(true);
}
});
ComboBox<Integer> comboBox = new ComboBox<>();
for (int i = 1; i < 11; i++) {
comboBox.getItems().add(i);
}
comboBox.valueProperty().addListener((obs, old, val) -> {
if (val != null) {
tableView.getSelectionModel().select(val.intValue()-1);
} else {
tableView.getSelectionModel().clearSelection();
}
});
HBox row = new HBox(new Label("Select Row : "), comboBox);
row.setSpacing(10);
VBox vb = new VBox(row, tableView);
vb.setSpacing(10);
vb.setPadding(new Insets(10));
VBox.setVgrow(tableView, Priority.ALWAYS);
Scene scene = new Scene(vb, 500, 300);
primaryStage.setScene(scene);
primaryStage.setTitle("TableView Selection Blocking Demo");
primaryStage.show();
}
class Person {
private StringProperty name = new SimpleStringProperty();
private StringProperty id = new SimpleStringProperty();
public Person(String id1, String name1) {
name.set(name1);
id.set(id1);
}
public StringProperty nameProperty() {
return name;
}
public StringProperty idProperty() {
return id;
}
}
}
Note: This may not be the approach for editable table.
In my application, there are two scenes: mainScene and bossScene where mainScene is used when starting up the application.
I'm trying to implement the boss key functionality where by pressing the 'b' key on the the keyboard should change the scene to bossScene. And also by pressing the button in bossScene should switch back to mainScene.
I'm getting an error on InteliJ saying "Cannot resolve method setOnKeyPressed in List
My Code:
public void start(Stage stage) throws Exception {
stage.setTitle("BossKey Example");
// Scene and layout for the main view
VBox root = new VBox();
Scene mainScene = new Scene(root, 500, 300);
// Scene for the BOSS view
Scene bossScene = new Scene(new Label("Nothing suspicious here"), 500, 300);
List<TextField> fields = new ArrayList<TextField>();
for (int i = 0; i < 10; i++) {
fields.add(new TextField());
}
fields.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCharacter()){
case "b": stage.setScene(bossScene); break;
}
}
});
/////// Added addEventFilter, still not working
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, new
EventHandler<KeyEvent() {
#Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCharacter()){
case "b": stage.setScene(bossScene); break;
}
keyEvent.consume();
}
});
// Create components for main view
root.getChildren().addAll(fields);
root.getChildren().add(new Button("Hello!"));
stage.setScene(mainScene);
stage.show();
}
}
KeyCombination filters
You should use a key combination in an event filter, e.g., CTRL+B or SHORTCUT+B.
For details on how to apply key combinations, see:
javafx keyboard event shortcut key
Why a key combination is superior to filtering on the character "b":
If you filter on a "b" character, the feature won't work if caps lock is down.
If you filter on a "b" character, you will be unable to type "b" in the text field.
You might think you could write scene.setOnKeyPressed(...), however, that won't work as expected in many cases. A filter is required rather than a key press event handler because the key events may be consumed by focused fields like text fields if you use a handler, so a handler implementation might not activate in all desired cases.
Filtering on a key combination avoids the issues with trying to handle a character key press. The key combinations rely on key codes which represent the physical key pressed and don't rely on the state of other keys such as caps lock unless you explicitly add additional logic for that.
If you don't understand the difference between an event filter and an event handler and the capturing and bubbling phases of event dispatch, then study:
the oracle event handling tutorial.
KeyCombination filter implementation
final EventHandler<KeyEvent> bossEventFilter = new EventHandler<>() {
final KeyCombination bossKeyCombo = new KeyCodeCombination(
KeyCode.B,
KeyCombination.CONTROL_DOWN
);
public void handle(KeyEvent e) {
if (bossKeyCombo.match(e)) {
if (stage.getScene() == mainScene) {
stage.setScene(bossScene);
} else if (stage.getScene() == bossScene) {
stage.setScene(mainScene);
}
e.consume();
}
}
};
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
bossScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
Accelerator alternative
An accelerator could be used instead of an event filter. Information on applying an accelerator is also in an answer to the linked question, I won't detail this alternative further here.
Example Solution
Standalone executable example code:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.io.IOException;
public class SceneSwap extends Application {
#Override
public void start(Stage stage) throws IOException {
final Scene mainScene = new Scene(
createLayout(
"Press CTRL+B to enter boss mode",
Color.PALEGREEN
)
);
final Scene bossScene = new Scene(
createLayout(
"Press CTRL+B to exit boss mode",
Color.PALEGOLDENROD
)
);
final EventHandler<KeyEvent> bossEventFilter = new EventHandler<>() {
final KeyCombination bossKeyCombo = new KeyCodeCombination(
KeyCode.B,
KeyCombination.CONTROL_DOWN
);
public void handle(KeyEvent e) {
if (bossKeyCombo.match(e)) {
if (stage.getScene() == mainScene) {
stage.setScene(bossScene);
} else if (stage.getScene() == bossScene) {
stage.setScene(mainScene);
}
e.consume();
}
}
};
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
bossScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
stage.setScene(mainScene);
stage.show();
}
private VBox createLayout(String text, Color color) {
VBox mainLayout = new VBox(10,
new Label(text),
new TextField()
);
mainLayout.setPadding(new Insets(10));
mainLayout.setStyle("-fx-background: " + toCssColor(color));
return mainLayout;
}
private String toCssColor(Color color) {
int r = (int) Math.round(color.getRed() * 255.0);
int g = (int) Math.round(color.getGreen() * 255.0);
int b = (int) Math.round(color.getBlue() * 255.0);
int o = (int) Math.round(color.getOpacity() * 255.0);
return String.format("#%02x%02x%02x%02x" , r, g, b, o);
}
public static void main(String[] args) {
launch();
}
}
This works:
getTableView().itemsProperty().addListener((observableValue, oldVal, newVal) -> button.setDisable(newVal == null || newVal.isEmpty()));
When the tableview items are null or empty, the button is disabled.
I was wondering if there is a "binding" solution and how it would be implemented.
button.disableProperty().bind(Bindings.or(Bindings.isNull(getTableView().itemsProperty()), Bindings.isEmpty(getTableView().getItems())));
The above does not work because itemsProperty is never null and getItems() may be null causing the Bindings.isEmpty() to fail.
The ItemsProperty of the TableView is a simple ObservableProperty which does not provide any specialized functionality for tracking the items in an ObservableList.
You will want to create a ListProperty instead, which in turn provides the emptyProperty that you can use for your purposes:
ListProperty<String> listProperty = new SimpleListProperty<>();;
Now bind that property to observe the ItemsProperty of the TableView:
listProperty.bind(tableView.itemsProperty());
The last step is just binding the disableProperty of your Button:
button.disableProperty().bind(listProperty.emptyProperty());
Below is a complete example using a ListView instead of a TableView. The functionality is identical but for the purposes of this example, a ListView is just quicker to implement.
import javafx.application.Application;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ButtonBindingToList extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// **********************************************************************************************
// Create a basic layout
// **********************************************************************************************
VBox root = new VBox(5);
root.setAlignment(Pos.TOP_CENTER);
root.setPadding(new Insets(10));
// **********************************************************************************************
// Creat the ListView
// **********************************************************************************************
ListView<String> listView = new ListView<>();
// **********************************************************************************************
// Create a Button to add items to the list (will always be enabled).
// **********************************************************************************************
Button btnAdd = new Button("Add item");
btnAdd.setOnAction(event -> listView.getItems().add("Added item #" + listView.getItems().size()));
// **********************************************************************************************
// Create button to remove the last item in the ListView
// **********************************************************************************************
Button btnRemove = new Button("Remove last item");
btnRemove.setOnAction(event -> listView.getItems().remove(listView.getItems().size() - 1));
// **********************************************************************************************
// In order to bind to the emptiness of the ListView, we need to create a ListProperty that
// is bound to the ListView's itemsProperty
// **********************************************************************************************
ListProperty<String> listProperty = new SimpleListProperty<>();
listProperty.bind(listView.itemsProperty());
// **********************************************************************************************
// Now bind the "Remove" button's disable property to the listProperty. This will disable
// the button as long as the ListView is empty
// **********************************************************************************************
btnRemove.disableProperty().bind(listProperty.emptyProperty());
root.getChildren().addAll(listView, btnAdd, btnRemove);
// **********************************************************************************************
// Set the Scene for the stage
// **********************************************************************************************
primaryStage.setScene(new Scene(root));
// **********************************************************************************************
// Configure the Stage
// **********************************************************************************************
primaryStage.setTitle("Test Application");
primaryStage.show();
}
}
I have looked days on any ready solution for the subject of having TOGETHER in javafx (pure) :
Combobox
Multiselect of items through Checkboxes
Filter items by the "editable" part of the Combobox
I have had no luck finding what I was looking for so I have now a working solution taken from different separate solution... Thank you to all for this !
Now I would like to know if what I have done follows the best practices or not... It's working... but is it "ugly" solution ? Or would that be a sort of base anyone could use ?
I tied to comment as much as I could, and also kept the basic comment of the sources :
user:2436221 (Jonatan Stenbacka) --> https://stackoverflow.com/a/34609439/14021197
user:5844477 (Sai Dandem) --> https://stackoverflow.com/a/52471561/14021197
Thank you for your opinions, and suggestions...
Here is the working example :
package application;
import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Callback;
#SuppressWarnings ("restriction") // Only applies for PROTECTD library : com.sun.javafx.scene.control.skin.ComboBoxListViewSkin
public class MultiSelectFiltered2 extends Application {
// These 2 next fields are used in order to keep the FILTERED TEXT entered by user.
private String aFilterText = "";
private boolean isUserChangeText = true;
public void start(Stage stage) {
Text txt = new Text(); // A place where to expose the result of checked items.
HBox vbxRoot = new HBox(); // A basic root to order the GUI
ComboBox<ChbxItems> cb = new ComboBox<ChbxItems>() {
// This part is needed in order to NOT have the list hided when an item is selected...
// TODO --> Seems a little ugly to me since this part is the PROTECTED part !
protected javafx.scene.control.Skin<?> createDefaultSkin() {
return new ComboBoxListViewSkin<ChbxItems>(this) {
#Override
protected boolean isHideOnClickEnabled() {
return false;
}
};
}
};
cb.setEditable(true);
// Create a list with some dummy values.
ObservableList<ChbxItems> items = FXCollections.observableArrayList();
items.add(new ChbxItems("One"));
items.add(new ChbxItems("Two"));
items.add(new ChbxItems("Three"));
items.add(new ChbxItems("Four"));
items.add(new ChbxItems("Five"));
items.add(new ChbxItems("Six"));
items.add(new ChbxItems("Seven"));
items.add(new ChbxItems("Eight"));
items.add(new ChbxItems("Nine"));
items.add(new ChbxItems("Ten"));
// Create a FilteredList wrapping the ObservableList.
FilteredList<ChbxItems> filteredItems = new FilteredList<ChbxItems>(items, p -> true);
// Add a listener to the textProperty of the combo box editor. The
// listener will simply filter the list every time the input is changed
// as long as the user hasn't selected an item in the list.
cb.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
// This needs to run on the GUI thread to avoid the error described here:
// https://bugs.openjdk.java.net/browse/JDK-8081700.
Platform.runLater(() -> {
if (isUserChangeText) {
aFilterText = cb.getEditor().getText();
}
// If the no item in the list is selected or the selected item
// isn't equal to the current input, we re-filter the list.
filteredItems.setPredicate(item -> {
boolean isPartOfFilter = true;
// We return true for any items that starts with the
// same letters as the input. We use toUpperCase to
// avoid case sensitivity.
if (!item.getText().toUpperCase().startsWith(newValue.toUpperCase())) {
isPartOfFilter = false;
}
return isPartOfFilter;
});
isUserChangeText = true;
});
});
cb.setCellFactory(new Callback<ListView<ChbxItems>, ListCell<ChbxItems>>() {
#Override
public ListCell<ChbxItems> call(ListView<ChbxItems> param) {
return new ListCell<ChbxItems>() {
private CheckBox chbx = new CheckBox();
// This 'just open bracket' opens the newly CheckBox Class specifics
{
chbx.setOnAction(new EventHandler<ActionEvent>() {
// This VERY IMPORTANT part will effectively set the ChbxItems item
// The argument is never used, thus left as 'arg0'
#Override
public void handle(ActionEvent arg0) {
// This is where the usual update of the check box refreshes the editor' text of the parent combo box... we want to avoid this ;-)
isUserChangeText = false;
// The one line without which your check boxes are going to be checked depending on the position in the list... which changes when the list gets filtered.
getListView().getSelectionModel().select(getItem());
// Updating the exposed text from the list of checked items... This is added here to have a 'live' update.
txt.setText(updateListOfValuesChosen(items));
}
});
}
private BooleanProperty booleanProperty; //Will be used for binding... explained bellow.
#Override
protected void updateItem(ChbxItems item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
// Binding is used in order to link the checking (selecting) of the item, with the actual 'isSelected' field of the ChbxItems object.
if (booleanProperty != null) {
chbx.selectedProperty().unbindBidirectional(booleanProperty);
}
booleanProperty = item.isSelectedProperty();
chbx.selectedProperty().bindBidirectional(booleanProperty);
// This is the usual part for the look of the cell
setGraphic(chbx);
setText(item.getText() + "");
} else {
// Look of the cell, which has to be "reseted" if no item is attached (empty is true).
setGraphic(null);
setText("");
}
// Setting the 'editable' part of the combo box to what the USER wanted
// --> When 'onAction' of the check box, the 'behind the scene' update will refresh the combo box editor with the selected object reference otherwise.
cb.getEditor().setText(aFilterText);
cb.getEditor().positionCaret(aFilterText.length());
}
};
}
});
// Yes, it's the filtered items we want to show in the combo box...
// ...but we want to run through the original items to find out if they are checked or not.
cb.setItems(filteredItems);
// Some basic cosmetics
vbxRoot.setSpacing(15);
vbxRoot.setPadding(new Insets(25));
vbxRoot.setAlignment(Pos.TOP_LEFT);
// Adding the visual children to root VBOX
vbxRoot.getChildren().addAll(txt, cb);
// Ordinary Scene & Stage settings and initialization
Scene scene = new Scene(vbxRoot);
stage.setScene(scene);
stage.show();
}
// Just a method to expose the list of items checked...
// This is the result that will be probably the input for following code.
// -->
// If the class ChbxItems had a custom object rather than 'text' field,
// the resulting checked items from here could be a list of these custom objects --> VERY USEFUL
private String updateListOfValuesChosen(ObservableList<ChbxItems> items) {
StringBuilder sb = new StringBuilder();
items.stream().filter(ChbxItems::getIsSelected).forEach(cbitem -> {
sb.append(cbitem.getText()).append("\n");
});
return sb.toString();
}
// The CHECKBOX object, with 2 fields :
// - The boolean part (checked ot not)
// - The text part which is shown --> Could be a custom object with 'toString()' overridden ;-)
class ChbxItems {
private SimpleStringProperty text = new SimpleStringProperty();
private BooleanProperty isSelected = new SimpleBooleanProperty();
public ChbxItems(String sText) {
setText(sText);
}
public void setText(String text) {
this.text.set(text);
}
public String getText() {
return text.get();
}
public SimpleStringProperty textProperty() {
return text;
}
public void setIsSelected(boolean isSelected) {
this.isSelected.set(isSelected);
}
public boolean getIsSelected() {
return isSelected.get();
}
public BooleanProperty isSelectedProperty() {
return isSelected;
}
}
public static void main(String[] args) {
launch();
}
}
I have a javafx application with multiple textboxes that the user can enter information in. I also have a keyboard built into the application that when pressed adds that text to the textbox.
My issue is that since I have multiple textboxes, I don't know which one to add the buttons text to. Is there a way in javafx to check if a user has clicked on a certain textbox so I can check which one has been selected and add the text there?
You can use the Scene.focusOwner property of the active scene to get the focused node. Check, if it's a TextInputControl and call the appropriate method for the button clicked. Note that clicking a button may move the focus, if focusTraversable is true for that button. (By default this is the case.)
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
final Scene scene = new Scene(grid);
for (int i = 0; i < 4; i++) {
grid.add(new TextField(), 0, i);
final String buttonValue = Character.toString((char) ('a'+i));
Button button = new Button(buttonValue);
button.setFocusTraversable(false); // prevent buttons from stealing focus
button.setOnAction(evt -> {
Node fo = scene.getFocusOwner();
if (fo instanceof TextInputControl) {
((TextInputControl) fo).replaceSelection(buttonValue);
}
});
grid.add(button, 1, i);
}
primaryStage.setScene(scene);
primaryStage.show();
}
You should create a listener for each TextField's focusProperty and set an instance variable.
Once you have a global reference to the currently focused TextField, you can do any processing on it that you choose.
Here is a quick application to demonstrate. I've included a couple extra details in the code itself:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
// Instance variable to hold the currently-selected TextField
private TextField selectedTextField;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Create TextFields
TextField txt1 = new TextField();
TextField txt2 = new TextField();
TextField txt3 = new TextField();
TextField txt4 = new TextField();
// This method sets the same change listener on each textfield
installListener(txt1, txt2, txt3, txt4);
VBox pane = new VBox(5);
pane.setPadding(new Insets(5));
// Add the TextFields to the layout
pane.getChildren().addAll(
new HBox(5, new Label("Txt1: "), txt1),
new HBox(5, new Label("Txt2: "), txt2),
new HBox(5, new Label("Txt3: "), txt3),
new HBox(5, new Label("Txt4: "), txt4)
);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
// Accepts multiple TextFields
private void installListener(TextField... textFields) {
// Install the same listener on all of them
for (TextField textField : textFields) {
textField.focusedProperty().addListener((observableValue, oldValue, newValue) -> {
// Set the selectedTextField to null whenever focus is lost. This accounts for the
// TextField losing focus to another control that is NOT a TextField
selectedTextField = null;
if (newValue) {
// The new textfield is focused, so set the global reference
selectedTextField = textField;
System.out.println("Selected Text: " + selectedTextField.getText());
}
});
}
}
}