How to define setOnAction for tableView rows? - java

I am writing a program with javafx and tableView feature.
My purpose is when I click on a row of this table,another window opens and shows something but I don't know how to define something like setOnMouseClicked feature for my table.
I searched a lot but I coudn't find a simple way
This is my existing code that define table columns and rows.(rows are defined with observable feature)
package sample;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TableView tableView = new TableView();
TableColumn<String, Account> column1 = new TableColumn<>("UserName");
column1.setCellValueFactory(new PropertyValueFactory<>("userName"));
column1.setMinWidth(100);
TableColumn<String, Account> column2 = new TableColumn<>("PassWord");
column2.setCellValueFactory(new PropertyValueFactory<>("passWord"));
column2.setMinWidth(100);
tableView.getColumns().add(column1);
tableView.getColumns().add(column2);
tableView.setItems(getAllAccounts());
VBox vbox = new VBox(tableView);
Scene scene = new Scene(vbox,200,200);
Stage window ;
window = primaryStage;
window.setScene(scene);
window.show();
}
private ObservableList<Account> getAllAccounts(){
ObservableList<Account> accounts= FXCollections.observableArrayList(Account.getAccounts());
return accounts;
}
}

You actually have two options:
Method 1:
Implement a click listener on the TableView and retrieve the item that was selected.
// Listen for a mouse click and access the selectedItem property
tblAccounts.setOnMouseClicked(event -> {
// Make sure the user clicked on a populated item
if (tblAccounts.getSelectionModel().getSelectedItem() != null) {
System.out.println("You clicked on " + tblAccounts.getSelectionModel().getSelectedItem().getUsername());
}
});
Method 2:
Create your own RowFactory for the TableView and handle your logic there. (I prefer this method)
// Create a new RowFactory to handle actions
tblAccounts.setRowFactory(tv -> {
// Define our new TableRow
TableRow<Account> row = new TableRow<>();
row.setOnMouseClicked(event -> {
System.out.println("Do your stuff here!");
});
return row;
});
Method #1 is the simplest approach and will work for most needs. You'll want to use method #2 for more complex needs, such as styling the individual rows, or to handle clicks on empty rows.

Related

JavaFX updating ComboBox

I'm getting tons of Unknown Source errors when updating content in my ComboBox.
Here is how I assign data to the combobox
DateBox.setItems(FXCollections.observableArrayList(dateList));
DateBox.getSelectionModel().selectFirst();
Then I add changelistener with this line
DateBox.valueProperty().addListener((obs, oldVal, newVal) -> listListener(newVal));
Now when I update the dateList and call the first segment of code again it works if I don't have that listener set. But if I call it with the listener set it spews out Unknown Source. Most likely I could bypass this by removing the listener before I update and then adding it back in but I couldn't find any way to do it. Also that wouldn't most likely be the correct way to do it.
How could I update the ComboBox (DateBox) without it causing all of those errors to popup. I assume the problem is that I change the content with setItems. I'd guess there is a way to cause the combobox to update but I couldn't find it.
Here is a runnable example where one combo box updates the other
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
public class Main extends Application {
#Override
public void start(Stage stage) {
ArrayList<String> list = new ArrayList<>(Arrays.asList("1", "2", "3"));
ComboBox<String> comboBoxOne = new ComboBox<>();
ComboBox<String> comboBoxTwo = new ComboBox<>();
comboBoxOne.getItems().addAll(list);
comboBoxOne.setOnAction(event -> {
comboBoxTwo.getItems().clear();
ArrayList<String> listTwo = new ArrayList<>();
for (int i = 0; i < 5; i++)
listTwo.add(comboBoxOne.getValue());
comboBoxTwo.getItems().addAll(listTwo);
});
VBox vBox = new VBox(comboBoxOne, comboBoxTwo);
Scene scene = new Scene(vBox);
stage = new Stage();
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) { launch(args); }
}

Trying to select programatically in JavaFX is not working

I am in the process of teaching myself JavaFX. Coming from the Swing world there are a lot of similarities between the 2. Especially event processing. Part of my process is to try and mimic an existing application as closely as possible. One of the things I am doing is creating a dialog that will allow the user to select a font to use. There is a text field for them to type in the font name and a list where they can scroll and select one. When they start typing the list will automatically scroll to through the list to start matching what the user is typing. I am also trying to populate the text field with the currently matched font name and then highlight the portion that the user has not typed yet so they can continue to type until the correct match is found.
For example if the user types the letter 't' on Windows the first font found is Tahoma. So the text field will be set to Tahoma and the carat will be positioned right after the 'T' and the 'ahoma' will be highlighted. What happens instead is that the field is populated with Tahoma and the carat is positioned at the end and nothing is highlighted. So it is like it is ignoring the 2 lines of code for positioning and highlighting or the event processor is causing my calls to JavaFX libraries to be run out of order.
I think this may be a bug with JavaFX but it could also be my misunderstanding of the event system. Please let me know which one and why.
Here is a complete sample code showing the problem. Just start typing in the text field to try it out.
package test;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
textChange = (observable, oldValue, newValue) -> {
text.textProperty().removeListener(textChange);
for (String family : Font.getFamilies()) {
if (family.equalsIgnoreCase(newValue) || family.toLowerCase().startsWith(newValue.toLowerCase())) {
text.setText(family);
text.positionCaret(newValue.length());
text.selectEnd();
break;
}
}
text.textProperty().addListener(textChange);
};
text.textProperty().addListener(textChange);
}
public static void main(String... args) {
launch(args);
}
}
Wrap caret position and select end into Platform.runLater. The problem is in events order. I don't know correct details about this issue so I will not provide you a detailed answer, only solution.
Platform.runLater(()-> {
text.positionCaret(newValue.length());
text.selectEnd();
});
Here's an alternative approach entirely, which uses a TextFormatter to modify changes to the text. The advantage here is that it doesn't rely on the "timing" of various property changes with respect to event handling, which is not documented and thus could possibly change in later JavaFX versions. It also avoids the slightly ugly "remove the listener and add it back" idiom.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
UnaryOperator<Change> filter = c -> {
// for delete, move the caret, or change selection, don't modify anything...
if (c.getText().isEmpty()) {
return c ;
}
for (String family : Font.getFamilies()) {
if (family.toLowerCase().startsWith(c.getControlNewText().toLowerCase())) {
c.setText(family.substring(c.getRangeStart(), family.length()));
c.setAnchor(c.getControlNewText().length());
break ;
}
}
return c ;
};
text.setTextFormatter(new TextFormatter<String>(filter));
}
public static void main(String... args) {
launch(args);
}
}

JavaFX Textfield behind one character when grabbing text

This code below which is triggered by a typed key into a JavaFX textfield is always behind one character. For instance, if user types in k, the string printed for searchBar.getText() equals "". If a user types in another k it will equal "k", and so on.
//this is triggered when a key is typed into search bar
#FXML public void onKeyPressedOnSearch(){
File[] fileCollection = model.getFileCollection();
System.out.println(searchBar.getText());
System.out.println(fileCollection[0].getName().substring(0, searchBar.getText().length()));
List<String> list = new ArrayList<String>();
ObservableList<String> tempObservableList = FXCollections.observableList(list);
/* for(int i = 0; i < fileCollection.length; i++){
if(!(searchBar.getText().equals(fileCollection[i].getName().substring(0, searchBar.getText().length())))){
tempObservableList.remove(i);
}
}
if(searchBar.getText() == null || searchBar.getText() == ""){
songList.setItems(observableList);
}else{
songList.setItems(tempObservableList);
} */
}
I would suggest to add a ChangeListener to the TextField, so you can grab any change in it from there, Please consider this example:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class CharByCharGrabbing extends Application {
#Override
public void start(Stage stage) throws Exception {
// create simple root and add two text fields to it
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
// just styling
root.setBackground(new Background(new BackgroundFill(Color.MAGENTA, null,null)));
TextField textField = new TextField();
TextField textField1 = new TextField();
root.getChildren().addAll(textField, textField1);
Scene scene = new Scene(root, 300,200);
stage.setScene(scene);
stage.setTitle("Grabbing Char by Char");
stage.show();
// Now you can add a change listener to the text property of the text field
// it will keep you updated with every single change (char by char)
// either adding to or removing from the TextField
textField.textProperty().addListener((observable, oldText, newText)->{ // lambda style
textField1.setText(newText); // update me when any change happens
// so you can grab the changes from here.
});
}
public static void main(String[] args) {
launch();
}
}
Test

Multiple selection lost when displaying ContextMenu

When using multiple selection in a TableView, clicking with the secondary button on a selected row to use the ContextMenu of the table will deselect any selected rows except the one that has been clicked. Why does this happen? What is the point of multiple selection if you can't choose an option in the context menu that can apply to that selection?
I'm thinking this might be a bug. This is because in some cases when I select more than one row in my example below, the selection is retained. I couldn't figure out any pattern for when the selection is retained, it seems to happen stochastically.
Update:
The comments have pointed out that this behaviour isn't stochastic. If you click on a part of the row that isn't a real column, the selection is lost. To me, this behaviour doesn't feel intuitive.
The following is an MCVE of a basic TableView that allows multiple selection.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class TestMultipleSelection extends Application {
#Override
public void start(Stage stage) {
// Table
TableView<ObservableList<String>> table = new TableView<ObservableList<String>>();
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// Column
TableColumn<ObservableList<String>, String> col = new TableColumn<ObservableList<String>, String>("Column");
col.setCellValueFactory(e -> new SimpleStringProperty(e.getValue().get(0)));
table.getColumns().add(col);
// Data
ObservableList<ObservableList<String>> items = FXCollections.observableArrayList(
FXCollections.observableArrayList("One"), FXCollections.observableArrayList("Two"), FXCollections.observableArrayList("Three"));
table.setItems(items);
// ContextMenu
ContextMenu contextMenu = new ContextMenu();
MenuItem item1 = new MenuItem("Item1");
contextMenu.getItems().add(item1);
table.setContextMenu(contextMenu);
stage.setScene(new Scene(table));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Tested platforms:
Windows 8.1 Enterprise, 64-bit; Java 8u92.
Windows 7 Enterprise SP1, 64-bit; Java 8u92.

Handling events from within a PopupWindow

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);
}
}

Categories