My TableView has a column with a ToggleButton. All the buttons belong to one group, you can only select one button (one row).
But my TableView has a lot of rows and the ToggleGroup seems to work.
That is until I scroll drown.
When I select one ToggleButton and scroll down no other button should be selected but there is always one that is selected per view.
Is this fixable?
Edit: Here is a SSCCE :
MainApp.java :
package p1;
import java.io.IOException;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class MainApp extends Application {
private Stage primaryStage;
private AnchorPane rootLayout;
private ObservableList<Person> personData = FXCollections.observableArrayList();
public MainApp(){
for(int i=0;i<40;i++){
personData.add(new Person("person " +i));
}
}
public ObservableList<Person> getPersonData(){
return personData;
}
#Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
try{
FXMLLoader loader =new FXMLLoader();
loader.setLocation(MainApp.class.getResource("People.fxml"));
rootLayout = (AnchorPane)loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
PeopleController controller = loader.getController();
controller.setMainApp(this);
} catch(IOException e){
e.printStackTrace();
}
}
public Stage getPrimaryStage(){
return primaryStage;
}
public static void main(String[] args){
launch(args);
}}
People.fxml :
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="p1.PeopleController">
<children>
<TableView fx:id="personTable" layoutX="160.0" layoutY="49.0" prefHeight="351.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="49.0">
<columns>
<TableColumn fx:id="nameColumn" prefWidth="75.0" text="Name" />
<TableColumn fx:id="previewColumn" prefWidth="75.0" text="Preview"/>
</columns>
</TableView>
</children>
</AnchorPane>
PeopleController.java :
package p1;
import com.sun.prism.impl.Disposer;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.util.Callback;
public class PeopleController{
#FXML private TableView<Person> personTable;
#FXML private TableColumn<Person, String> nameColumn;
#FXML private TableColumn previewColumn;
private MainApp mainApp;
final ToggleGroup group = new ToggleGroup();
#FXML
public void initialize() {
nameColumn.setCellValueFactory(cellData -> cellData.getValue().NameProperty());
previewColumn.setCellFactory(
new Callback<TableColumn<Disposer.Record, Boolean>, TableCell<Disposer.Record, Boolean>>() {
#Override
public TableCell<Disposer.Record, Boolean> call(TableColumn<Disposer.Record, Boolean> p) {
ButtonCell cell = new ButtonCell(group);
cell.setAlignment(Pos.CENTER);
return cell;
}
});
}
public void setMainApp(MainApp mainApp){
this.mainApp = mainApp;
personTable.setItems(mainApp.getPersonData());
}
public class ButtonCell extends TableCell<Disposer.Record, Boolean> {
final ToggleButton cellButton = new ToggleButton("click");
public ButtonCell(ToggleGroup group){
cellButton.setToggleGroup(group);
}
#Override
protected void updateItem(Boolean t, boolean empty) {
super.updateItem(t, empty);
if(!empty){
setGraphic(cellButton);
}
}}}
Person.java :
package p1;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final StringProperty name;
public Person(){
this(null);
}
public Person(String name){
this.name = new SimpleStringProperty(name);
}
public String getName(){
return name.get();
}
public void setName(String name){
this.name.set(name);
}
public StringProperty NameProperty(){
return name;
} }
The reason that the toggle seems to "jump" when scrolling is the re-use of the cells: the selected state sticks to the button, not the item. Consequently, you can't use a toggleGroup (except in the not-so-common and not recommended case that the items in your data implement Toggle) to keep the toggle state. You need to implement the toggle-logic yourself.
One option is a custom SingleSelectionModel and a custom ButtonCell that talks to the model (as do all other collaborators). Unfortunately, FX doesn't have a publicly accessible concrete implementation of the model. As often, the heavy lifting - which in this case is to update itself on modifications to the items - is left to client code (and not done in this example as well ;-)
Something like:
public class ToggleButtonTableExample extends Application {
public static class DataSelectionModel<S> extends SingleSelectionModel<S> {
private ListProperty<S> listProperty;
public DataSelectionModel(Property<ObservableList<S>> items) {
//listProperty = BugPropertyAdapters.listProperty(items);
listProperty = new SimpleListProperty<>();
listProperty.bindBidirectional(items);
ListChangeListener<S> itemsContentObserver = c -> {
itemsChanged(c);
};
listProperty.addListener(itemsContentObserver);
}
protected void itemsChanged(Change<? extends S> c) {
// TODO need to implement update on modificatins to the underlying list
}
#Override
protected S getModelItem(int index) {
if (index < 0 || index >= getItemCount()) return null;
return listProperty.get(index);
}
#Override
protected int getItemCount() {
return listProperty.getSize();
}
}
public static class ButtonCellX<S, T> extends TableCell<S, T> {
private ToggleButton cellButton;
private SingleSelectionModel<S> model;
public ButtonCellX(SingleSelectionModel<S> group) {
this.model = group;
cellButton = new ToggleButton("click");
cellButton.setOnAction(e -> updateToggle());
updateToggle();
setAlignment(Pos.CENTER);
}
protected void updateToggle() {
model.select(cellButton.isSelected()? getIndex() : -1);
}
#Override
protected void updateItem(T t, boolean empty) {
super.updateItem(t, empty);
if (empty) {
setGraphic(null);
} else {
cellButton.setSelected(model.isSelected(getIndex()));
setGraphic(cellButton);
}
}
}
private Parent getContent() {
TableView<Person> table = new TableView<>();
table.setItems(Person.persons());
TableColumn<Person, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("lastName"));
SingleSelectionModel<Person> model = new DataSelectionModel<>(table.itemsProperty());
TableColumn<Person, Boolean> toggle = new TableColumn<>("Preview");
toggle.setCellFactory(c -> new ButtonCellX<Person, Boolean>(model));
toggle.setCellValueFactory(f -> {
Object value = f.getValue();
return Bindings.equal(value, model.selectedItemProperty());
});
table.getColumns().addAll(name, toggle);
Button select = new Button("Select 0");
select.setOnAction(e -> {
model.select(0);
});
VBox content = new VBox(10, table, select);
return content;
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
//primaryStage.setTitle(FXUtils.version());
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(ChoiceBoxTableCellDynamic.class.getName());
}
Related
I have been endeavouring to implement a TableView using data generated in the initialize() method of the Controller and a user interface provide by the fxml.
However, the TableView fails to poulate and displays an empty view with the message 'No content in table'.
Consequently I have created a simplified application as follows.
If anybody can suggest what I may have omitted it will be much appreciated.
Controller
package sample;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
public class Controller {
#FXML
private TableView tvNicknames;
#FXML
private TableColumn tcName;
#FXML
private TableColumn tcNickname;
#FXML
public void initialize(){
ObservableList<DataModel> nickList =
FXCollections.observableArrayList(
new DataModel(new SimpleStringProperty("Alan"), new SimpleStringProperty("Pip")),
new DataModel(new SimpleStringProperty("Bertie"), new SimpleStringProperty("Beets")),
new DataModel(new SimpleStringProperty("Charlie"), new SimpleStringProperty("Collie")),
new DataModel(new SimpleStringProperty("Dave"), new SimpleStringProperty("Daffy")),
new DataModel(new SimpleStringProperty("Ernie"), new SimpleStringProperty("Einstein"))
);
//specify cell factories for each TableColumn
tcName.setCellValueFactory(new PropertyValueFactory<>("name"));
tcNickname.setCellValueFactory(new PropertyValueFactory<>("nickname"));
}
}
DataModel
package sample;
import javafx.beans.property.SimpleStringProperty;
public class DataModel {
private SimpleStringProperty name;
private SimpleStringProperty nickname;
public DataModel() {
}
public DataModel(SimpleStringProperty name, SimpleStringProperty nickname) {
this.name = name;
this.nickname = nickname;
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getNickname() {
return nickname.get();
}
public SimpleStringProperty nicknameProperty() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname.set(nickname);
}
}
fxml
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.FlowPane?>
<FlowPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<TableView fx:id="tvNicknames" prefWidth="250" prefHeight="200">
<columns>
<TableColumn fx:id="tcName" text="Name" prefWidth="100"/>
<TableColumn fx:id="tcNickname" text="Nickname" prefWidth="100" />
</columns>
</TableView>
</FlowPane>
You simply forgot to add the list to the TableView. You basically need to add the following line to method initialize in class Controller.
tvNicknames.setItems(nickList);
In your DataModel class, you don't need a default constructor. I removed it. Also, I prefer to send property values to the constructor and instantiate properties in the constructor. Hence I changed the other constructor in class DataModel. I don't know if it's better, it just feels more correct to me. Here is DataModel class with my changes.
package sample;
import javafx.beans.property.SimpleStringProperty;
public class DataModel {
private SimpleStringProperty name;
private SimpleStringProperty nickname;
public DataModel(String name, String nickname) {
this.name = new SimpleStringProperty(this, "name", name);
this.nickname = new SimpleStringProperty(this, "nickname", nickname);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getNickname() {
return nickname.get();
}
public SimpleStringProperty nicknameProperty() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname.set(nickname);
}
}
Here is Controller class with the fix that I described above. Notice also that the creation of the list has changed because of the change to the DataModel constructor. Also I added type parameters to the types for the variables. Again, not mandatory, but I feel that it is more correct.
package sample;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
public class Controller {
#FXML
private TableView<DataModel> tvNicknames;
#FXML
private TableColumn<DataModel, String> tcName;
#FXML
private TableColumn<DataModel, String> tcNickname;
#FXML
public void initialize(){
ObservableList<DataModel> nickList =
FXCollections.observableArrayList(
new DataModel("Alan", "Pip"),
new DataModel("Bertie", "Beets"),
new DataModel("Charlie", "Collie"),
new DataModel("Dave", "Daffy"),
new DataModel("Ernie", "Einstein")
);
tvNicknames.setItems(nickList); // ADDED THIS LINE
//specify cell factories for each TableColumn
tcName.setCellValueFactory(new PropertyValueFactory<>("name"));
tcNickname.setCellValueFactory(new PropertyValueFactory<>("nickname"));
}
}
Of-course I created a class that extends javafx.application.Application in order to test the above code. Here is the code of that class.
Note that I named the FXML file nickname.fxml and I did not change anything in it. It is the same as in your question.
package sample;
import java.net.URL;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class NickName extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
URL location = getClass().getResource("nickname.fxml");
FXMLLoader loader = new FXMLLoader(location);
FlowPane root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In JavaFX 8, I have created a custom TableCell with a TextField to get more control than with a "regular" TextFieldTableCell. What I want to achieve is to have that TextField automatically selected when an Add button is clicked (so the user doesn't have to click on that TextField or Tab to it after the Add-Record button is fired. Unfortunately, I only manage to select the row/cell but not the underlying TextField (and the startEdit() method doesn't seem to be reached).
Here is my code (Main FX Application code, POJO, FXML code created in Scene Builder, Controller code, and CustomTextFieldTableCell class):
Main.java
package test;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
POJO
package test;
import javafx.beans.property.SimpleStringProperty;
class TestObject {
private final SimpleStringProperty field1 = new SimpleStringProperty();
private final SimpleStringProperty field2 = new SimpleStringProperty();
public SimpleStringProperty field1Property() {
return field1;
}
public void setField1(String string) {
field1.set(string);
}
public SimpleStringProperty field2Property() {
return field2;
}
public void setField2(String string) {
field2.set(string);
}
}
FXMLDocument.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="425.0" prefWidth="517.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.171" fx:controller="test.FXMLDocumentController">
<children>
<Label fx:id="label" layoutX="126" layoutY="120" minHeight="16" minWidth="69" />
<TableView fx:id="tableView" layoutX="14.0" layoutY="52.0" prefHeight="359.0" prefWidth="489.0" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="52.0">
<columns>
<TableColumn fx:id="col1" prefWidth="150.0" text="C1" />
<TableColumn fx:id="col2" minWidth="0.0" prefWidth="141.0" text="C2" />
</columns>
</TableView>
<Button fx:id="btnAdd" layoutX="14.0" layoutY="14.0" onAction="#handleAdd" text="_Add" />
</children>
</AnchorPane>
FXMLDocumentController.java
package test;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
public class FXMLDocumentController implements Initializable {
#FXML
private Button btnAdd;
#FXML
private TableView<TestObject> tableView;
#FXML
private TableColumn<TestObject, String> col1;
#FXML
private TableColumn<TestObject, String> col2;
#FXML
private void handleAdd(ActionEvent event) {
TestObject testObject = new TestObject();
tableView.getItems().add(testObject);
tableView.edit(tableView.getItems().indexOf(testObject), col1);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
col1.setCellValueFactory(cellData -> cellData.getValue().field1Property());
col1.setCellFactory((final TableColumn<TestObject, String> column) -> new CustomTextFieldTableCell());
col1.setOnEditStart((TableColumn.CellEditEvent<TestObject, String> event) -> {
//Inside code doesn't seem to ever get reached
System.out.println("Edit started");
});
col2.setCellValueFactory(cellData -> cellData.getValue().field2Property());
col2.setCellFactory((final TableColumn<TestObject, String> column) -> new TableCell<>());
}
}
CustomTextFieldTableCell.java
package test;
import javafx.event.ActionEvent;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
public class CustomTextFieldTableCell extends TableCell<TestObject, String> {
private final TextField textField;
public CustomTextFieldTableCell() {
textField = new TextField();
textField.setOnAction((ActionEvent event) -> {
commitEdit(textField.getText());
event.consume();
});
textField.prefWidthProperty().bind(prefWidthProperty().subtract(3));
}
/** {#inheritDoc} */
#Override
public void startEdit() {
if (!isEditable()
|| getTableView() == null
|| getTableColumn() == null
|| getTableView() != null && !getTableView().isEditable()
|| getTableColumn() != null && !getTableColumn().isEditable()) {
return;
}
super.startEdit();
if (isEditing()) {
/*
textField.setText(getItem().trim());
this.setText(textField.getText());
*/
//Platform.runLater(() -> textField.requestFocus());
textField.requestFocus();
}
}
/** {#inheritDoc} */
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (empty) {
setGraphic(null);
} else {
textField.setText(item == null ? "" : item.trim());
setGraphic(textField);
}
}
}
What visually occurs:
What I need my code to do is to have caret placed inside the TextField or TextField focused, but this doesn't occur with my code right now.
Any idea what I am missing out?
In my JavaFX application, I would like to display live data from a background thread. Does anyone know how to update a linechart from a background thread? Thank you. Below some sample code.
TM
Preview image
Sample controller
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.Button;
public class SampleController {
#FXML
Button btnStart;
#FXML
Button btnStop;
#FXML
LineChart myChart;
Process process;
#FXML
public void initialize() {
process = new Process();
}
public void start(ActionEvent event) {
process.start();
}
public void stop(ActionEvent event) {
process.stop();
}
}
Process class. Launches the thread.
public class Process {
private Task task = new Task();
public void start(){
task.start();
}
public void stop(){
task.kill();
}
}
Task class. The thread class which executes the tasks
public class Task extends Thread {
private boolean isActive = true;
#Override
public void run() {
while (isActive) {
try {
// Simulate heavy processing stuff
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Add a new number to the linechart
// Remove first number of linechart
}
}
public void kill(){
isActive = false;
}
}
Main class
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 500, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0"
xmlns="http://javafx.com/javafx/10.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="sample.SampleController">
<children>
<Button fx:id="btnStart" mnemonicParsing="false" onAction="#start" text="Start" />
<LineChart fx:id="myChart">
<xAxis>
<CategoryAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</LineChart>
<Button fx:id="btnStop" mnemonicParsing="false" onAction="#stop"
text="Stop" />
</children>
</VBox>
You need to insert the values to display in the LineChart from the UI Thread. Therefore you can simply use something like this:
public class Task extends Thread {
private boolean isActive = true;
private LineChart<String, Number> chart;
private Series<String, Number> series = new Series<>();
public Task(LineChart<CategoryAxis, NumberAxis> chart) {
this.chart.getData().add(series);
}
#Override
public void run() {
while (isActive) {
try {
// Simulate heavy processing stuff
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(() -> {
// Add a new number to the linechart
this.series.getData().add(new Data<>("",0));
// Remove first number of linechart
this.series.getData().remove(0);
});
}
}
public void kill(){
isActive = false;
}
}
or if you want to execute something after you added and removed the values use something like:
public class Task extends Thread {
private boolean isActive = true;
private LineChart<String, Number> chart;
private Series<String, Number> series = new Series<>();
public Task(LineChart<CategoryAxis, NumberAxis> chart) {
this.chart.getData().add(series);
}
#Override
public void run() {
while (isActive) {
try {
// Simulate heavy processing stuff
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
FutureTask<Object> task = new FutureTask<Object>(new Callable<Object>() {
#Override
public Object call() throws Exception {
// Add a new number to the linechart
series.getData().add(new Data<>("",0));
// Remove first number of linechart
series.getData().remove(0);
return true;
}
});
Platform.runLater(task);
try {
System.out.println(task.get());
//TODO after insertion
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
public void kill(){
isActive = false;
}
}
I want get the cell(node,not data) from list by index.I need cast it to my custom cell and call its function.
I found a lot of question ask how to get value,but i find nothing about how to get cell itself.I try to use listView.getChildrenUnmodifiable().get(index),but it's not work.
The function i want to achieve is right clicking on the cell of list,then popup a context menu which have a 'rename' command,when i clicking the commond, startEdit function of cell need to be called.
How can i do that?
You should set the context menu on the individual cells, not on the list view itself. Here is a quick example:
Controller class with list view, and methods to invoke from context menu items:
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
public class Controller {
#FXML
private ListView<String> listView ;
public void initialize() {
for (int i = 1 ; i <= 20 ; i++) {
listView.getItems().add("Item "+i);
}
listView.setCellFactory(lv -> new ListCellWithContextMenu(this));
}
public void edit(int index) {
listView.edit(index);
}
public void delete(int index) {
listView.getItems().remove(index);
}
}
The cell implementation:
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.cell.TextFieldListCell;
public class ListCellWithContextMenu extends TextFieldListCell<String> {
private final ContextMenu contextMenu ;
public ListCellWithContextMenu(Controller controller) {
contextMenu = new ContextMenu();
MenuItem delete = new MenuItem("Delete");
MenuItem edit = new MenuItem("Edit");
contextMenu.getItems().addAll(edit, delete);
edit.setOnAction(e -> controller.edit(getIndex()));
delete.setOnAction(e -> controller.delete(getIndex()));
setConverter(TextFormatter.IDENTITY_STRING_CONVERTER);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setContextMenu(null);
} else {
setContextMenu(contextMenu);
}
}
}
For completeness, an FXML file:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.ListView?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
<left>
<ListView fx:id="listView" editable="true" />
</left>
</BorderPane>
and an application class:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ListViewWithContextMenu extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(FXMLLoader.load(getClass().getResource("ListViewWithContextMenu.fxml")));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
James_D is right,you can register the context menus on the individual cells.
But in the situation which i have a lot of code relative to context menu,rewrite then all may cause other problems.After some try i use a another way.
For simplicity,I write two reusable class to implement the function that get cell by index from list directly.
If you think helpful,you can use the code below.
//let your cell extend this class
public class GetAbleListCell<T> extends ListCell<T>{
private ListViewHelper<T> libraryListHelper;
public GetAbleListCell(ListViewHelper<T> libraryListHelper) {
this.libraryListHelper = libraryListHelper;
}
#Override
public void updateIndex(int i) {
// TODO Auto-generated method stub
super.updateIndex(i);
if(getIndex()>0)
{
libraryListHelper.setCell(getIndex(),this);
}
}
}
//helper class
public class ListViewHelper<T> {
private ObservableList<GetAbleListCell<T>> list=FXCollections.observableArrayList();
public GetAbleListCell<T> getCell(int index) {
return list.get(index);
}
public void setCell(int index, GetAbleListCell<T> getAbleListCell) {
while(list.size()<index+1)
{
list.add(null);
}
list.set(index, getAbleListCell);
}
}
example
this.libraryListHelper=new ListViewHelper();
libraryLV.setCellFactory(param -> new LibraryCell(libraryListHelper));
.........
private void renameCurrentSelectedItems() {
//get cell directly
LibraryCell cell=(LibraryCell) libraryListHelper.getCell(libraryLV.getSelectionModel().getSelectedIndex());
cell.startEdit();
}
may contain some bug but it work in my situation
Edited and removed all unused code
New to Javafx / scene builder using JDK 8 and eclipse.
Sql DB connection working fine and pulls to a recordset which populates a virtual Tableview, system.out prints db records etc. I am using scene builder and trying to populate a FXML defined Tableview in scenebuilder, which is fun to learn.
I just cannot get the data to the tableview.
I added static to private static ObservableList<ObservableList<String>> data; which has stopped my nullPointerException and added public void initialize(URL location, ResourceBundle resources) which tells me the ObservableList data has SOME DATA and watched way to many youtube videos.
I now have no errors but see no data in the defined tableview. When i add a column in to scenebulder without an id, i get different coloured rows, which makes me think it is doing sometihng, controller is attached in scenebuilder.
I just wanted to pull all the table columns for now just to test and then i can go on from there. Apologies for the messy code but may as well leave it in, first week.
I would be grateful for any assistance, really would.
Controller, left out imports
public class SoftwareController extends Application implements Initializable {
private static ObservableList<ObservableList<String>> data;
#FXML
public TableView<ObservableList<String>> tblSoftware;
public Statement st;
public Connection conn;
public static void main(String[] args) {
launch(args);
}
public void buildData() {
data = FXCollections.observableArrayList();
try {
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
String url = "jdbc:sqlserver://IP;databaseName=CMDB";
conn = DriverManager.getConnection(url,"cmdbadmin","cmdbadmin!1");
System.out.print("connection successfulltttt");
String SQL = "SELECT * from Data_CMDB_Main";
ResultSet rs = conn.createStatement().executeQuery(SQL);
for(int i=0 ; i<rs.getMetaData().getColumnCount(); i++) {
final int j = i;
TableColumn col = new TableColumn(rs.getMetaData().getColumnName(i+1));
public ObservableValue<String> call(CellDataFeatures<ObservableList, String> param) {
return new SimpleStringProperty(param.getValue().get(j).toString());
}
});
System.out.println(col);
}
while (rs.next()){
ObservableList<String> row = FXCollections.observableArrayList();
for(int i=1 ; i<=rs.getMetaData().getColumnCount(); i++) {
row.add(rs.getString(i));
}
data.add(row);
System.out.println(row); //shows records from database
}
}catch (Exception e) {
e.printStackTrace();
System.out.println("Error building data");
}
}
#Override
public void start(Stage stage) throws Exception {
buildData();
}
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Software.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
Stage stage1 = new Stage();;
stage1.setScene(new Scene(root1));
stage1.show();
}
public Label lblTest;
public void btnSoftwarePressed(ActionEvent event) {
lblTest.setText("label working");
}
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println(data);
if(data !=null){
System.out.println("data is not null");
tblSoftware.getItems().addAll(data);
}
else System.out.println("data is null");
}
}
FMXL
<?import java.lang.*?>
<?import java .util.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="368.0" prefWidth="433.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.SoftwareController">
<children>
<TableView fx:id="tblSoftware" layoutY="102.0" prefHeight="266.0" prefWidth="433.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="102.0">
<columns>
<TableColumn prefWidth="75.0" text="Column X" />
</columns>
</TableView>
<Button fx:id="btnSoftware" layoutY="63.0" mnemonicParsing="false" onAction="#btnSoftwarePressed" text="Button" />
<Label fx:id="lblTest" layoutX="226.0" layoutY="63.0" prefHeight="26.0" prefWidth="130.0" text="22" />
</children>
</AnchorPane>
I wanted to try and see how it worked without using a separate controller and application file.
Note that I had to call buildData in the initialize method like James_D mentioned.
This expects you have H2 as a library somewhere but I think your connection was ok, so just delete the H2 part and uncomment your connection.
package fxml;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ResourceBundle;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class SoftwareController extends Application implements Initializable {
private static ObservableList<ObservableList<String>> data;
#FXML public TableView<ObservableList<String>> tblSoftware;
public void buildData() throws Exception{
data = FXCollections.observableArrayList();
DriverManager.registerDriver(new org.h2.Driver());
Connection conn = DriverManager.getConnection("jdbc:h2:mem:");
Statement stmt = conn.createStatement();
String sql = "CREATE TABLE Data_CMDB_Main (name VARCHAR(255), address VARCHAR(255))";
stmt.executeUpdate(sql);
for (int i = 0; i < 10; i++) {
sql = "INSERT INTO Data_CMDB_Main VALUES ("
+ "'1st string in row " + i + "','2nd string in row " + i + "')";
stmt.executeUpdate(sql);
}
// Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
// String url = "jdbc:sqlserver://IP;databaseName=CMDB";
// Connection conn = DriverManager.getConnection(url, "cmdbadmin", "cmdbadmin!1");
// System.out.print("connection successfulltttt");
String SQL = "SELECT * from Data_CMDB_Main";
ResultSet rs = conn.createStatement().executeQuery(SQL);
for(int i=0 ; i<rs.getMetaData().getColumnCount(); i++) {
final int j = i;
TableColumn<ObservableList<String>, String> col = new TableColumn(rs.getMetaData().getColumnName(i+1));
col.setCellValueFactory(p -> new ReadOnlyStringWrapper(p.getValue().get(j)));
tblSoftware.getColumns().add(col);
}
while (rs.next()) {
ObservableList<String> row = FXCollections.observableArrayList();
for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
row.add(rs.getString(i));
}
data.add(row);
}
tblSoftware.setItems(data);
}
#Override
public void start(Stage stage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Software.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
stage.setScene(new Scene(root1));
stage.show();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
buildData();
} catch (Exception ex) {
ex.printStackTrace();
}
}
#FXML private void btnSoftwarePressed(){}
public static void main(String[] args) { launch(args);}
}