I am using javafx tableview with active sorting and insertion of a new row every millisecond...
I want this functionality:
If I have selected a row then it should remain visible (that is should not go up or down from current visible portion of my table) when new rows are inserted.
This may be the long away around it and it's kind of hackish, but it worked for me when I needed to do something similar.
The gist of the answer is that you need to get access to the VirtualFlow member of the TableViewSkin. That's not as simple as is sounds because the skin isn't loaded until the CSS is parsed. I added a Listener to the skinProperty of the TableView and was able to get the VirtualFlow that way.
tableView.skinProperty().addListener(new ChangeListener<Skin>()
{
#Override
public void changed(ObservableValue<? extends Skin> ov, Skin t, Skin t1)
{
if (t1 == null) { return; }
TableViewSkin tvs = (TableViewSkin)t1;
ObservableList<Node> kids = tvs.getChildrenUnmodifiable();
if (kids == null || kids.isEmpty()) { return; }
flow = (VirtualFlow)kids.get(1);
}
});
After you have the VirtualFlow, you can listen to the table's ObservableList for changes and check to make sure the selected item is still in the viewport.
countries.addListener(new ListChangeListener<Country>()
{
#Override
public void onChanged(ListChangeListener.Change<? extends Country> change)
{
while (change.next())
{
if (change.wasAdded())
{
if (flow == null) { return; }
int first = flow.getFirstVisibleCell().getIndex();
int last = flow.getLastVisibleCell().getIndex();
int selected = tableView.getSelectionModel().getSelectedIndex();
if (selected < first || selected > last)
{
flow.show(selected);
}
}
}
}
});
There will still be a bit of bookkeeping to manage. For instance, if you wanted to place focus back on the table. Also, it's worth noting that the VirtualFlow isn't strictly bound by the visible rectangle of the table, so an item may be considered visible even if it is just outside the viewport. You can study the VirtualFlow for more details.
Here's a SSCCE.
JavaFXApplication21.java:
package javafxapplication21;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class JavaFXApplication21 extends Application
{
#Override
public void start(Stage stage) throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("Sample.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
Sample.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="200.0" prefWidth="200.0" xmlns:fx="http://javafx.com/fxml" fx:controller="javafxapplication21.SampleController">
<children>
<ToolBar AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<Button fx:id="insertBtn" mnemonicParsing="false" text="Insert" />
</items>
</ToolBar>
<TableView fx:id="tableView" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="31.0">
<columns>
<TableColumn prefWidth="100.0" text="Country" fx:id="countryColumn" />
<TableColumn prefWidth="100.0" text="Capital" fx:id="capitalColumn" />
</columns>
</TableView>
</children>
</AnchorPane>
SampleController.java:
package javafxapplication21;
import com.sun.javafx.scene.control.skin.TableViewSkin;
import com.sun.javafx.scene.control.skin.VirtualFlow;
import java.net.URL;
import java.util.*;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
public class SampleController implements Initializable
{
#FXML private Button insertBtn;
#FXML private TableView<Country> tableView;
#FXML private TableColumn<Country, String> countryColumn;
#FXML private TableColumn<Country, String> capitalColumn;
private VirtualFlow flow;
private ObservableList<Country> countries =
FXCollections.observableArrayList();
private List<Country> insertList = new ArrayList<>();
public SampleController()
{
countries.addAll(new Country("AG", "Buenos Aires"),
new Country("AU", "Vienna"),
new Country("BY", "Minsk"),
new Country("CO", "Bogota"),
new Country("EG", "Cairo"));
insertList.add(new Country("ZI", "Harare"));
insertList.add(new Country("UK", "London"));
insertList.add(new Country("TW", "Taipei"));
}
#Override
public void initialize(URL url, ResourceBundle rb)
{
countryColumn.setCellValueFactory(
new PropertyValueFactory<Country, String>("name"));
capitalColumn.setCellValueFactory(
new PropertyValueFactory<Country, String>("capital"));
tableView.setItems(countries);
tableView.skinProperty().addListener(new ChangeListener<Skin>()
{
#Override
public void changed(ObservableValue<? extends Skin> ov,
Skin t, Skin t1)
{
if (t1 == null) { return; }
TableViewSkin tvs = (TableViewSkin)t1;
ObservableList<Node> kids = tvs.getChildrenUnmodifiable();
if (kids == null || kids.isEmpty()) { return; }
flow = (VirtualFlow)kids.get(1);
}
});
insertBtn.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent t)
{
if (!insertList.isEmpty())
{
countries.add(2, insertList.get(0));
insertList.remove(0);
}
}
});
countries.addListener(new ListChangeListener<Country>()
{
#Override
public void onChanged(ListChangeListener.Change<? extends Country> change)
{
while (change.next())
{
if (change.wasAdded())
{
if (flow == null) { return; }
int first = flow.getFirstVisibleCell().getIndex();
int last = flow.getLastVisibleCell().getIndex();
int selected = tableView.getSelectionModel().getSelectedIndex();
if (selected < first || selected > last)
{
flow.show(selected);
}
}
}
}
});
}
public class Country
{
private SimpleStringProperty name;
private SimpleStringProperty capital;
public Country(String name, String capital)
{
this.name = new SimpleStringProperty(name);
this.capital = new SimpleStringProperty(capital);
}
public SimpleStringProperty nameProperty() { return name; }
public SimpleStringProperty capitalProperty() { return capital; }
}
}
I also had the same problem .You can use cellEventHandler class to which implemets EventHandler interface to fired when a cell is selcted in table and set last selected row index to a variable. The class is as followed.
public class CellEventHandler implements EventHandler<MouseEvent>
{
CellEventHandler()
{
}
#Override
public void handle(MouseEvent t)
{
TableCell c;
c = (TableCell) t.getSource();
c.getIndex();
PanelController.selectedItem = c.getIndex();
}
}`
Then your table class render of the table in PanelController Class should be as followed.
typeCol.setCellFactory(new Callback<TableColumn<tableModel, String>, TableCell<tableModel, String>>()
{
#Override
public TableCell<tableModel, String> call(TableColumn<tableModel, String> tableColumn)
{
LableCell lableCell = new LableCell(Pos.CENTER, true);
lableCell.addEventFilter(MouseEvent.MOUSE_CLICKED, new CellEventHandler());
return lableCell;
}
});
The "selectedItem" field should be declared in the panelController class as static field and then that "selectedItem " should set as selected in the table when you refresh the table row data.
myTable.getSelectionModel().select(selectedItem);
This works with me:
tableView.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, t1) -> {
TableViewSkin tvs = (TableViewSkin)tableView.getSkin();
ObservableList<Node> kids = tvs.getChildren();
if (kids == null || kids.isEmpty()) { return; }
VirtualFlow flow = (VirtualFlow)kids.get(1);
int first =-1;
if (flow.getFirstVisibleCell() != null) first = flow.getFirstVisibleCell().getIndex();
int last = -1;
if (flow.getFirstVisibleCell() != null) last = flow.getLastVisibleCell().getIndex();
if(first !=-1 && t1.intValue()<first)
flow.scrollTo(t1.intValue());
if(last != -1 && t1.intValue()>last)
flow.scrollTo(t1.intValue()+1);
});
Related
I have a JavaFX table view with custom editable cells.
It contains fields for item name, quantity, price, discount, tax and amount.
Every field is editable using the setCellFactory method and an implementation of a class extended by TableCell class. And I also have an extractor on the observable list to detect changes in quantity and amount properties of the list to calculate total values for them. My question is a duplicate of this question, but it wasn't answered there, so here I am asking it again.
So when I press on a field it starts editing but as I shift focus using the tab it navigates to the last column and after that, If I click on the cell again it starts editing for a millisecond and then again navigates to the last cell.
This problem is resolved when I remove the extractor but then I cannot detect changes in observable list properties.
It behaves weirdly because for some time it works OK and other times it doesn't.
GIF Showing the problem
Main Application
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloApplication extends Application {
#Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("EditableTableView.fxml"));
Scene scene = new Scene(fxmlLoader.load());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
View FXML Code
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<TableView fx:id="transactionsTable" editable="true" maxHeight="1.7976931348623157E308" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="500.0" prefWidth="1200.0" tableMenuButtonVisible="true"
xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.example.testingpurposejavafx.EditableTableViewController">
<columns>
<TableColumn fx:id="idCol" editable="false" prefWidth="50.66670632362366"/>
<TableColumn fx:id="qtyCol" minWidth="0.0" prefWidth="50.0" text="quantity"/>
<TableColumn fx:id="priceUnitCol" prefWidth="50.0" text="price unit"/>
<TableColumn prefWidth="200.0" text="discount">
<columns>
<TableColumn fx:id="discountRateCol" prefWidth="150.0" text="rate"/>
<TableColumn fx:id="discountAmountCol" prefWidth="150.0" text="amount"/>
</columns>
</TableColumn>
<TableColumn prefWidth="200.0" text="tax">
<columns>
<TableColumn fx:id="taxRateCol" prefWidth="150.0" text="rate"/>
<TableColumn fx:id="taxAmountCol" minWidth="0.0" prefWidth="150.0" text="amount"/>
</columns>
</TableColumn>
<TableColumn fx:id="amountCol" prefWidth="50.0" text="amount"/>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
</TableView>
Table Model Class
package com.example.testingpurposejavafx;
import javafx.beans.property.*;
public class TransactionTable {
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty item = new SimpleStringProperty();
private final StringProperty quantity = new SimpleStringProperty();
private final StringProperty priceUnit = new SimpleStringProperty();
private final StringProperty discountRate = new SimpleStringProperty();
private final StringProperty discountAmount = new SimpleStringProperty();
private final StringProperty taxRate = new SimpleStringProperty();
private final StringProperty taxAmount = new SimpleStringProperty();
private final StringProperty amount = new SimpleStringProperty();
//Getter Setter and property fields skipped
}
Main Controller Class
package com.example.testingpurposejavafx;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import java.net.URL;
import java.util.ResourceBundle;
public class EditableTableViewController implements Initializable {
public TableView<TransactionTable> transactionsTable;
public TableColumn<TransactionTable, Number> idCol;
public TableColumn<TransactionTable, String> qtyCol;
public TableColumn<TransactionTable, String> priceUnitCol;
public TableColumn<TransactionTable, String> discountRateCol;
public TableColumn<TransactionTable, String> discountAmountCol;
public TableColumn<TransactionTable, String> taxRateCol;
public TableColumn<TransactionTable, String> taxAmountCol;
public TableColumn<TransactionTable, String> amountCol;
public final ObservableList<TransactionTable> transactionList = FXCollections.observableArrayList(transaction -> new Observable[]{transaction.quantityProperty(), transaction.amountProperty()});
#Override
public void initialize(URL location, ResourceBundle resources) {
transactionsTable.setItems(transactionList);
transactionsTable.getSelectionModel().setCellSelectionEnabled(true);
setCellValueFactory();
setCellFactory();
addRow();
}
private void addRow() {
var transactionTable = new TransactionTable();
transactionTable.setId(transactionsTable.getItems().isEmpty() ? 1 : transactionsTable.getItems().size() + 1);
transactionList.add(transactionTable);
}
private void setCellFactory() {
qtyCol.setCellFactory(param -> new TextEditingCell<>());
priceUnitCol.setCellFactory(param -> new TextEditingCell<>());
discountRateCol.setCellFactory(param -> new TextEditingCell<>());
discountAmountCol.setCellFactory(param -> new TextEditingCell<>());
taxRateCol.setCellFactory(param -> new TextEditingCell<>());
amountCol.setCellFactory(param -> new TextEditingCell<>());
}
private void setCellValueFactory() {
idCol.setCellValueFactory(cellData -> cellData.getValue().idProperty());
qtyCol.setCellValueFactory(cellData -> cellData.getValue().quantityProperty());
priceUnitCol.setCellValueFactory(cellData -> cellData.getValue().priceUnitProperty());
discountRateCol.setCellValueFactory(cellData -> cellData.getValue().discountRateProperty());
discountAmountCol.setCellValueFactory(cellData -> cellData.getValue().discountAmountProperty());
taxRateCol.setCellValueFactory(cellData -> cellData.getValue().taxRateProperty());
taxAmountCol.setCellValueFactory(cellData -> cellData.getValue().taxAmountProperty());
amountCol.setCellValueFactory(cellData -> cellData.getValue().amountProperty());
}
}
Text Editing Cell
package com.example.testingpurposejavafx;
import javafx.event.Event;
import javafx.scene.control.*;
public class TextEditingCell<S> extends TableCell<S, String> {
protected TextField textField;
#Override
public void startEdit() {
if (isEmpty()) return;
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.requestFocus();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(getString());
setGraphic(null);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(item(item));
setGraphic(null);
return;
}
if (isEditing()) {
if (textField != null) {
textField.setText(item(item));
}
setText(null);
setGraphic(textField);
return;
}
setText(item(item));
setGraphic(null);
}
#Override
public void commitEdit(String item) {
if (!isEditing()) {
TableView<S> table = getTableView();
if (table != null) {
TableColumn<S, String> column = getTableColumn();
TableColumn.CellEditEvent<S, String> event = new TableColumn.CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
}
public void createTextField() {
this.textField = new TextField();
textField.setOnAction(e -> commitEdit(textField.getText()));
textField.focusedProperty().addListener((oV, oldV, newV) -> {
if (Boolean.FALSE.equals(newV)) commitEdit(textField.getText());
});
textField.setText(getString());
}
private String getString() {
return getItem() == null ? "" : getItem() + "";
}
public String item(String item) {
return item == null ? "" : item + "";
}
}
Is there a way to bind an object's field to a TreeItem so that when the field is changed, the TreeItem is automatically updated in the TreeView?
I have objects with several fields that are set in TreeItems in a TreeView. The objects fields are updated sometimes at 5 to 10 times per second via their setters and I would like the TreeItems to update on the fly as the fields change. Any help on this would be greatly appreciated. I can't seem to find a way to do this other than reloading the entire TreeView.
Edit: Here is the existing code...
The list listener sets up the treeview for the player list. If the number of objects in the list changes, the tree is redrawn. That is all fine. What I want is, if you were to place code in the handle button click method that continuously changes player attributes(in a loop, maybe 2-10 times per second), that should be reflected in the treeview as those attributes change.(Just a simulation, I cant get into how those attributes are really being changed at that rate). An example of how to do this would be great.
package application;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import viewcontroller.PlayerViewController;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
public class Main extends Application {
private Stage mainStage;
#FXML
private AnchorPane mainView;
#Override
public void start(Stage mainStage) {
this.mainStage = mainStage;
PlayerList.playerListListener();
this.showMainLayout();
}
/**
* shows the main screen
*/
public void showMainLayout() {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("/viewcontroller/playerView.fxml"));
try {
mainView = (AnchorPane) loader.load();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Scene scene = new Scene(mainView);
mainStage.sizeToScene();
mainStage.setScene(scene);
mainStage.show();
PlayerViewController controller = loader.getController();
controller.setMainApp(this);
}
public static void main(String[] args) {
launch(args);
}
}
package model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Player {
private StringProperty str1 = new SimpleStringProperty();
private StringProperty str2 = new SimpleStringProperty();
private StringProperty str3 = new SimpleStringProperty();
public Player() {
}
public Player(String str1, String str2, String str3) {
super();
this.str1 = new SimpleStringProperty(str1);
this.str2 = new SimpleStringProperty(str2);
this.str3 = new SimpleStringProperty(str3);
}
public StringProperty str1() {
return str1;
}
public String getStr1() {
return str1.get();
}
public void setStr1(String str1) {
this.str1.set(str1);
}
public StringProperty str2() {
return str2;
}
public String getStr2() {
return str2.get();
}
public void setStr2(String str2) {
this.str2.set(str2);
}
public StringProperty str3() {
return str3;
}
public String getStr3() {
return str3.get();
}
public void setStr3(String str3) {
this.str3.set(str3);
}
}
package application;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.TreeItem;
import model.Player;
public class PlayerList {
private final ObservableList<Player> playerList = FXCollections.observableArrayList();
private static PlayerList instance = new PlayerList();
public static TreeItem<String> rootNode = new TreeItem<String>("Root");
private PlayerList() {}
public ObservableList<Player> getPlayerList() {
return playerList;
}
public static PlayerList getInstance() {
return instance;
}
public static void playerListListener() {
//adds a list listener which reloads tree if objects are added or removed
PlayerList.getInstance().getPlayerList().addListener((ListChangeListener<? super Player>) new ListChangeListener<Player>() {
#SuppressWarnings("unchecked")
public void onChanged(Change<? extends Player> change) {
//re-sort list
//System.out.println("List changed");
rootNode.getChildren().clear();//clear tree before reloading on list change
for(Player p: PlayerList.getInstance().getPlayerList()) {
//Parent Node
TreeItem<String> playerName = new TreeItem<String>(p.getStr1());
//Items
TreeItem<String> attr1 = new TreeItem<String>(p.getStr2());
TreeItem<String> attr2 = new TreeItem<String>(p.getStr3());
playerName.getChildren().addAll(attr1, attr2);
playerName.setExpanded(true);
rootNode.getChildren().add(playerName);
rootNode.setExpanded(true);
}
}
});
}
}
package viewcontroller;
import java.net.URL;
import java.util.ResourceBundle;
import application.Main;
import application.PlayerList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TreeView;
import javafx.scene.layout.VBox;
import model.Player;
public class PlayerViewController implements Initializable {
#FXML
private VBox vbox;
private TreeView<String> treeView;
public void setMainApp(Main main) {
// TODO Auto-generated method stub
}
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
Player p1 = new Player("Name1", "Attr1", "Attr2");
Player p2 = new Player("Name2", "Attr1", "Attr2");
Player p3 = new Player("Name3", "Attr1", "Attr2");
PlayerList.getInstance().getPlayerList().addAll(p1, p2, p3);//add players to list
treeView = new TreeView<String>(PlayerList.rootNode);
treeView.setShowRoot(false);
vbox.getChildren().add(treeView);//add tree view to GUI
}
#FXML
private void handleButtonClick() {
/*this method can hold code that would run in a loop, continuously setting new values
on p1, p2, and p3 attributes and would show the tree items updating automatically*/
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="mainView" prefHeight="400.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="viewcontroller.PlayerViewController">
<children>
<VBox fx:id="vbox" layoutX="14.0" layoutY="14.0" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button mnemonicParsing="false" onAction="#handleButtonClick" text="Button" />
</children>
</VBox>
</children>
</AnchorPane>
I'm following the MVP pattern so the Presenter has access to the view and to the model and the view to the presenter.
View <-> Presenter -> Model.
My problem is, I can't populate the ComboBox. I am an amateur and this one here breaks me. I think I have a fundamental issue.
The Main class:
package test;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class Main extends Application
{
private Presenter presenter;
private Model model;
private ViewController view;
#Override
public void start(Stage primaryStage) throws Exception
{
presenter = initData();
testSampleData();
model.showList(); // prints out: [Susi 22, Peter 30, Tom 54]
AnchorPane paneMain = (AnchorPane) FXMLLoader.load(getClass().getResource("View.fxml"));
Scene scene = new Scene(paneMain);
primaryStage.setScene(scene);
primaryStage.show();
}
public void testSampleData()
{
model.getList().add(new Person("Susi", 22));
model.getList().add(new Person("Peter", 30));
model.getList().add(new Person("Tom", 54));
}
private Presenter initData()
{
presenter = new Presenter();
model = new Model();
view = new ViewController();
presenter.setModel(model);
presenter.setView(view);
view.setPresenter(presenter);
return presenter;
}
public static void main(String[] args)
{
launch(args);
}
}
Im trying to create an ComboBox with Objects.
The Person Class:
package test;
public class Person
{
private String name;
private int age;
public Person(String name, int alter)
{
this.name = name;
this.age = alter;
}
public String toString()
{
return name + " " + age;
}
}
The data is stored in the model class:
package test;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Model
{
private ObservableList<Person> list = FXCollections.observableArrayList();
public void showList()
{
System.out.println(this.list.toString());
}
public ObservableList<Person> getList()
{
return list;
}
public void initTest()
{
list.add(new Person("Susi", 22));
list.add(new Person("Peter", 30));
list.add(new Person("Tom", 54));
}
}
The presenter is the "bridge" between view and model
package test;
import javafx.collections.ObservableList;
public class Presenter
{
private Model myModel;
private ViewController myViewController;
public void setModel(Model model)
{
this.myModel = model;
}
public void setView(ViewController view)
{
this.myViewController = view;
}
public ObservableList<Person> getList()
{
return myModel.getList();
}
}
And here is the view:
package test;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
public class ViewController
{
Presenter presenter;
#FXML
private Label myLabel;
#FXML
private ComboBox<Person> myComboBox;
#FXML
private TextArea myTextArea;
private ObservableList<Person> list;
#FXML
private void handleComboBoxAction() {
Person selectedPerson = myComboBox.getSelectionModel().getSelectedItem();
myTextArea.appendText("Selected Person: " + selectedPerson + "\n");
}
public ViewController()
{
// myComboBox.setItems(presenter.getList()); // -> nullpointer
}
public void initialize()
{
list = FXCollections.observableArrayList();
// list = presenter.getList(); // -> this is my goal
list.add(new Person("Tom", 54)); // this here works, but I want to get the data from the model class :(
// myComboBox.setItems(list);
}
public void setPresenter(Presenter presenter)
{
this.presenter = presenter;
}
}
Last but not least, the fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane minHeight="300.0" minWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.ViewController">
<children>
<BorderPane prefHeight="300.0" prefWidth="300.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<center>
<ComboBox fx:id="myComboBox" onAction="#handleComboBoxAction" prefWidth="150.0" BorderPane.alignment="CENTER" />
</center>
<right>
<TextArea fx:id="myTextArea" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</right>
</BorderPane>
</children>
</AnchorPane>
Perhaps I spammed to much code in here but I thought it would be necessary.
EDIT:
I edited the FXML part in my main like this:
loader = new FXMLLoader(getClass().getResource("View.fxml"));
AnchorPane paneMain = (AnchorPane)loader.load();
view = (ViewController)loader.getController();
And now the advice from NAMS and James_D works!
Scenario: Im passing in comma separated values to a table. 2 columns, one with the original value, one with a text field with the values populated inside. They are lined up so I can add/change values, and copy the changes to a string.
I cannot figure out how to capture the TextField data after they are put in the table. Code below:
Main:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/application/MainFxml.fxml")); //this is the file that
Scene scene = new Scene(root,800,800); ////100,100 is width and height of window
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
}
Controller:
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.HBox;
public class MainFxmlController implements Initializable {
public static int count=-1;
#FXML public TableView<tableClass> table = new TableView<tableClass>();
#FXML private TableColumn<tableClass, String>col1;
#SuppressWarnings({ "unchecked", "rawtypes" })
#FXML public TableColumn<tableClass, Row> col2 = new TableColumn("Row");
#FXML public TextField txt;
#FXML public Button btn, btn2;
#FXML public ListView<String> listView = new ListView<String>();
public static ArrayList<String> input = new ArrayList<String>();
final HBox hb = new HBox();
public ObservableList<tableClass> obList = FXCollections.observableArrayList(); // each column contains an observable list object
public ObservableList<tableClass> loadTable(){
return obList; //return data object
}////END loadData
#Override
public void initialize(URL url, ResourceBundle rb) {
table.setEditable(true);
col1.setCellValueFactory(cellData -> cellData.getValue().getCol1());
col2.setCellFactory((param) -> new TextFieldCell<tableClass, Row>(EnumSet.allOf(Row.class)));
col2.setCellValueFactory(new PropertyValueFactory<tableClass, Row>("Row"));
col2.setOnEditCommit(
new EventHandler<CellEditEvent<tableClass, Row>>() {
#Override
public void handle(CellEditEvent<tableClass, Row> t) {
((tableClass) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setCol2(t.getNewValue());
}
}
);
tableClass Entry = new tableClass(" ", Row.Row1); //create the table using getters/setters from Table Class
table.getItems().addAll(Entry);
table.setItems(loadTable());
col1.setCellFactory(TextFieldTableCell.<tableClass>forTableColumn()); //Makes the columns themselves editable
col1.setOnEditCommit(
new EventHandler<CellEditEvent<tableClass, String>>() {
#Override
public void handle(CellEditEvent<tableClass, String> t) {
((tableClass) t.getTableView().getItems().get( t.getTablePosition().getRow())).setCol1(t.getNewValue());
}
}
);
col1.setStyle( "-fx-alignment: BOTTOM-RIGHT;"); //to alight text next to textArea
txt.setText("fsad,0,0,gfds,43,4,4,fdsg,rtewrtwe,0,67,3,4,4,,4,44,,4"); //TO BE ROMOVED UPON COMPLETION
}//end initialize
public void buttonAction(ActionEvent e){
if(txt.getText() != ""){
System.out.println(txt.getText());
ArrayList<String> myList = new ArrayList<String>(Arrays.asList(txt.getText().split(",")));
input = myList;
}
for(int i =0; i< input.size(); i++){
Row.Row1.equals(input.get(i).toString());
obList.add(new tableClass(input.get(i).toString(), Row.Row1));
}//end for
}//end buttonAction
public void captureText(ActionEvent e){
/*
* HERE I NEED TO CAPTURE VALUES FROM THE TEXT FIELDS
* IN COL2
*/
}
public static enum Row { //enum for the dxTable radio button if you want any other options, add another value and another radio buttton will be populated
Row1;
}
public static class TextFieldCell<S,T extends Enum<T>> extends TableCell<S,T>{
private EnumSet<T> enumeration;
public TextFieldCell(EnumSet<T> enumeration) {
this.enumeration = enumeration;
}
#Override
protected void updateItem(T item, boolean empty)
{
super.updateItem(item, empty);
if (!empty)
{
// gui setup
HBox hb = new HBox(7);
hb.setAlignment(Pos.CENTER);
// create a radio button for each 'element' of the enumeration
for (Enum<T> enumElement : enumeration) {
try{
TextField textField = new TextField(input.get(count));
textField.setUserData(enumElement);
hb.getChildren().add(textField);
}catch(IndexOutOfBoundsException e){}
}
// issue events on change of the selected radio button
setGraphic(hb);
count++;
} //end if
else
setGraphic(null);
}//updateItem
}//END TextFieldCell class
public static class tableClass{ ///table object with getters and setters.
public final SimpleStringProperty col1;
public final SimpleObjectProperty<Row> col2 = new SimpleObjectProperty<Row>();
public tableClass(String col1, Row t) { //uses an enum for the second type
this.col1 = new SimpleStringProperty(col1);
this.col2.setValue(t);
}
public StringProperty getCol1() {
return col1;
}
public void setCol1(String i) {
col1.set(i);
}
public void setCol2(Row t) {
col2.set(t);
}
public Row getCol2() {
return col2.get();
}
public String getCol2(int index) {
return "";
}
}//end table class
}//end controller
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="800.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainFxmlController">
<children>
<Button fx:id="btn" layoutX="723.0" layoutY="26.0" mnemonicParsing="false" onAction="#buttonAction" text="btn" />
<TextField fx:id="txt" layoutX="41.0" layoutY="26.0" prefHeight="25.0" prefWidth="647.0" />
<TableView fx:id="table" editable="true" layoutX="41.0" layoutY="106.0" prefHeight="588.0" prefWidth="451.0">
<columns>
<TableColumn fx:id="col1" prefWidth="75.0" text="C1" />
<TableColumn fx:id="col2" prefWidth="114.0" text="C2" />
</columns>
</TableView>
<Button fx:id="btn2" layoutX="507.0" layoutY="669.0" mnemonicParsing="false" onAction="#captureText" text="Button" />
</children>
</AnchorPane>
Any help would be greatly appreciated.
I'm not sure exactly what are trying to do, but I have found a way to get the TextField when they press enter.
In your TextFieldCell class, I added a setOnAction to grab what the user input in the TextField.
TextField textField = new TextField(input.get(count));
textField.setUserData(enumElement);
textField.setOnAction(event -> {
System.out.println("Gotcha");
});
You can use that, instead of the button, to do whatever you want to the text entered.
I have no idea how to use your button to programmatically grab all of the TextField in col2 and grab their text.
But, with the setOnAction you can add whatever you like in there and hopefully do what you need to do.
Edit 1
With some heavy editing of your work, I have done it!
I will leave in the old answer, since it applies directly to your source code.
First, I made col2 Look like this.
#FXML public TableColumn<tableClass, TextField> col2;
Then, I used that to my advantage and setCellValueFactory like so:
col2.setCellValueFactory(new PropertyValueFactory<>("col2"));
Everything to do with col2 I had to update/remove to get it to work with the TextField, and I ended up with a much shorter source code, that can do what you want. Example is in the captureText method.
I edited the tableClass to make use of the TextField, I removed your cellFactory class altogether. Hope this helps.
#FXML public TableView<tableClass> table;
#FXML private TableColumn<tableClass, String>col1;
#SuppressWarnings({ "unchecked", "rawtypes" })
#FXML public TableColumn<tableClass, TextField> col2;
#FXML public TextField txt;
#FXML public Button btn, btn2;
public static ArrayList<String> input = new ArrayList<String>();
public static Group hb = new Group();
public ObservableList<tableClass> obList = FXCollections.observableArrayList(); // each column contains an observable list object
public ObservableList<tableClass> loadTable(){
return obList; //return data object
}////END loadData
#Override
public void initialize(URL url, ResourceBundle rb) {
table.setEditable(true);
col1.setCellValueFactory(cellData -> cellData.getValue().col1Property());
col2.setCellValueFactory(new PropertyValueFactory<>("col2"));
table.setItems(loadTable());
col1.setCellFactory(TextFieldTableCell.<tableClass>forTableColumn()); //Makes the columns themselves editable
col1.setOnEditCommit(
new EventHandler<CellEditEvent<tableClass, String>>() {
#Override
public void handle(CellEditEvent<tableClass, String> t) {
((tableClass) t.getTableView().getItems().get( t.getTablePosition().getRow())).setCol1(t.getNewValue());
}
}
);
col1.setStyle( "-fx-alignment: BOTTOM-RIGHT;"); //to alight text next to textArea
txt.setText("fsad,0,0,gfds,43,4,4,fdsg,rtewrtwe,0,67,3,4,4,,4,44,,4"); //TO BE ROMOVED UPON COMPLETION
}//end initialize
public void buttonAction(ActionEvent e){
if(txt.getText() != ""){
System.out.println(txt.getText());
ArrayList<String> myList = new ArrayList<String>(Arrays.asList(txt.getText().split(",")));
input = myList;
}
for(int i =0; i< input.size(); i++){
obList.add(new tableClass(input.get(i),input.get(i)));
}//end for
}//end buttonAction
public void captureText(ActionEvent e) {
obList.forEach(event -> {
event.setCol1(event.getCol2().getText());
});
/*
* HERE I NEED TO CAPTURE VALUES FROM THE TEXT FIELDS
* IN COL2
*/
}
public static class tableClass{ ///table object with getters and setters.
public final SimpleStringProperty col1;
public final TextField col2;
public tableClass(String col1, String col2) { //uses an enum for the second type
this.col1 = new SimpleStringProperty(col1);
this.col2 = new TextField(col2);
}
public StringProperty col1Property() {
return col1;
}
public String getCol1(){
return col1.get();
}
public void setCol1(String i) {
col1.set(i);
}
public void setCol2(String tx) {
col2.setText(tx);
}
public TextField getCol2() {
return col2;
}
}//end table class
What is an expected method of connecting view and model in JavaFX?
Binding?
Suppose I want to make positioning in database with the following controls:
I have data (recordset) object in memory and it's properties are bindable. I.e. they are notifying when current record changes and when the number of records changes.
I want user to be able position inside recordset both with slider and text field.
How to accomplish that? There is no numeric spin in JavaFX, so how to bind text, slider and recordset object (three ends) together? Is it possible?
I cannot give an authoritative answer since I don't work for Oracle nor am I some JavaFX-pert but I usually construct the controller with an instance of the model and in the initialize method of the controller I create the bindings between the controls' properties and the model's properties.
To your concrete problem I have a lame but working solution. Lame because I can't figure out how to use the low-level binding API and I use two properties on the model. Here's how :
FXML :
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.control.TextField?>
<GridPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<Slider GridPane.columnIndex="0" fx:id="slider" min="0" max="99"/>
<Label GridPane.columnIndex="1" text="Record" />
<TextField fx:id="textfield" GridPane.columnIndex="2"/>
</GridPane>
Main.java :
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import sample.Models.Model;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
final Model model = new Model();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("sample.fxml"));
loader.setControllerFactory(new Callback<Class<?>, Object>() {
#Override
public Object call(Class<?> aClass) {
return new Controller(model);
}
});
GridPane root = (GridPane) loader.load();
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
}
Controller.java :
package sample;
import javafx.beans.binding.DoubleBinding;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import sample.Models.Model;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable {
private final Model model;
public Controller(Model model) {
this.model = model;
}
#FXML
public Slider slider;
#FXML
public TextField textfield;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
slider.valueProperty().bindBidirectional(model.pageProperty());
textfield.textProperty().bindBidirectional(model.pageTextProperty());
}
}
Model.java :
package sample.Models;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
public class Model {
private IntegerProperty pageProperty;
private StringProperty pageTextProperty;
public Model() {
pageProperty = new SimpleIntegerProperty(2);
pageProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observableValue, Number number, Number number2) {
int value = pageProperty.get();
//System.out.println("Page changed to " + value);
if (Integer.toString(value).equals(pageTextProperty.get())) return;
pageTextProperty.set(Integer.toString(value));
}
});
pageTextProperty = new SimpleStringProperty("2");
pageTextProperty.addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observableValue, String s, String s2) {
try {
int parsedValue = Integer.parseInt(observableValue.getValue());
if (parsedValue == pageProperty.get()) return;
pageProperty().set(parsedValue);
} catch (NumberFormatException e) {
// too bad
}
}
});
}
public int getPage() {
return pageProperty.get();
}
public void setPage(int page) {
pageProperty.set(page);
}
public IntegerProperty pageProperty() {
return pageProperty;
}
public String getPageText() {
return pageTextProperty.get();
}
public void setPageText(String pageText) {
pageTextProperty.set(pageText);
}
public StringProperty pageTextProperty() {
return pageTextProperty;
}
}