How can I solve this visual glitch in my JavaFX TableView? - java

Left column: Checkboxes to select or deselect rows, selected by default. Right column: a String representing the amount of selected rows up to and including the row. So deselecting a checkbox in a row changes the values in the rows underneath.
The bug: Scroll down to the bottom of the table. Deselect the checkbox in the row with invite code 74. Select it again. The last three invite codes should read 73, 74 and 75 again. But quite often, they show 73, 73, 74 or 73, 74, 74.
The bug does not always occur! If it does not occur, scrolling up and down a bit with the scrollbar of the table and repeating the procedure above can make it occur.
It seems that the bug is only visual - I made it dump the contents of the ObservableList to the console and it shows the correct values. Other than this visual glitch, my app works correctly. Clicking any other control in the window (e.g. the scrollbar of the table) flips the invite codes in the table to the correct value. Switching to another workspace on my desktop and going back makes it show the right values as well.
Small image, showing the console dump of the ObservableList on the left, the bugged table on the right.
The Question, quite logically:
How can I squash this bug!?
EDIT: threw out more code as advised by Kleopatra. Thanks!
MCV:
FXMLDocumentController.java
package invcodebug;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.Callback;
public class FXMLDocumentController implements Initializable {
#FXML private TableView<Person> personTable;
#FXML private TableColumn<Person, Boolean> invitedCol;
#FXML private TableColumn<Person, String> inviteCodeCol;
private final ObservableList<Person> persons
= FXCollections.observableArrayList();
#Override
public void initialize(URL location, ResourceBundle resources) {
initPersonTable();
populatePersons();
}
private void initPersonTable() {
invitedCol.setCellValueFactory(new PropertyValueFactory<>("invited"));
inviteCodeCol.setCellValueFactory(new PropertyValueFactory<>("inviteCode"));
invitedCol.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer param) {
doInvCode();
// SHOWS: underlying ObservableList has correct values
System.out.println("--------------------------");
for (Person p : persons) {
System.out.println(p.isInvited() + " " + p.getInviteCode()
);
}
return persons.get(param).invitedProperty();
}
}));
personTable.setItems(persons);
}
private void doInvCode() {
int invCounter = 1;
for (Person p : persons) {
if (p.isInvited()) {
p.setInviteCode(((Integer) invCounter).toString());
invCounter++;
} else p.setInviteCode("");
}
}
private void populatePersons() {
for (int i = 0; i < 75; i++) {
persons.add(new Person(true, ""));
}
}
}
Person.java
package invcodebug;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
public class Person {
private final SimpleBooleanProperty invited;
private final SimpleStringProperty inviteCode;
public Person(boolean invited, String inviteCode) {
this.invited = new SimpleBooleanProperty(invited);
this.inviteCode = new SimpleStringProperty(inviteCode);
}
public boolean isInvited() {
return invited.get();
}
public SimpleBooleanProperty invitedProperty() {
return invited;
}
public String getInviteCode(){
return inviteCode.get();
}
public void setInviteCode(String invCode) {
this.inviteCode.set(invCode);
}
public SimpleStringProperty inviteCodeProperty() {
return inviteCode;
}
}
FXMLDocument.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="464.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="invcodebug.FXMLDocumentController">
<children>
<TableView fx:id="personTable" editable="true" layoutX="26.0" layoutY="28.0" prefHeight="347.0" prefWidth="572.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="20.0">
<columns>
<TableColumn fx:id="invitedCol" prefWidth="27.0" sortable="false" />
<TableColumn fx:id="inviteCodeCol" editable="false" prefWidth="110.0" resizable="false" sortable="false" text="Invite Code" />
</columns>
</TableView>
</children>
</AnchorPane>
InvCodeBug.java
package invcodebug;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class InvCodeBug 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();
}
public static void main(String[] args) {
launch(args);
}
}

Probably not the most technical answer, but by using personTable.requestFocus(); at the end of the doInvCode() method, the table is refreshed visually and seems to fix the problem.

Related

How do I get additions to a database made from one JavaFX tab to be reflected in a different JavaFX tab?

I have a simple two tab application build around JavaFXML, scenebuilder and a Derby database. There is a MainController class declared as:
package tabpane.view;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
public class MainController {
#FXML public TabPane tabPane;
#FXML public Tab inputTab;
#FXML public Tab accountTab;
#FXML private Button exitBtn;
#FXML private void handleExitBtn() {
System.exit(0);
}
}
An InputController class defines the first tab called Input. It marshals some basic data, consisting of a new account name and a checkbox to indicate if the account is active. When the input is collected, it is written to a table in a derby database via a DAO controller class:
package tabpane.view;
import java.net.URL;
import java.sql.SQLException;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import tabpane.db.NdDao;
import tabpane.model.AccountObject;
public class InputController implements Initializable
{
#FXML private TextField accName;
#FXML private CheckBox isEnabled;
#FXML private Button saveBtn;
#Override
public void initialize(URL location, ResourceBundle resources) {}
#FXML private void handleSaveBtn() throws SQLException {
AccountObject obj = new AccountObject(accName.getText(), isEnabled.isSelected());
NdDao.connect();
NdDao.insertAccount(obj);
NdDao.disconnect();
}
}
This is the code in the DAO controller class:
package tabpane.db;
import tabpane.model.AccountObject;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.TreeMap;
public class NdDao
{
private static final String strAccountInsert = "INSERT INTO ACCOUNTS " +
" (ACCNAME, ISENABLED) " + "VALUES (?, ?)";
private static final String strAccountSelectAll = "SELECT * from ACCOUNTS ORDER BY ACCNAME ASC";
private static PreparedStatement pstmtAccountInsert;
private static PreparedStatement pstmtAccountSelectAll;
private static Connection conn = null;
public static void connect() throws SQLException {
conn = DriverManager.getConnection("jdbc:derby:C:/Users/" + System.getenv("USERNAME") + "/AppData/Local/TabPaneEx/db" + ";create=true");
prepareStatements();
}
public static void disconnect() throws SQLException {
if (conn != null) {
conn.close();
conn = null;
}
}
private static void prepareStatements() throws SQLException{
pstmtAccountInsert = conn.prepareStatement(strAccountInsert);
pstmtAccountSelectAll = conn.prepareStatement(strAccountSelectAll);
}
public static void insertAccount(AccountObject obj) throws SQLException
{
pstmtAccountInsert.clearParameters();
pstmtAccountInsert.setString(1, obj.getAccName());
pstmtAccountInsert.setBoolean(2, obj.getIsEnabled());
pstmtAccountInsert.executeUpdate();
}
public static TreeMap<Integer, AccountObject> selectAccounts() throws SQLException {
TreeMap<Integer, AccountObject> map = new TreeMap<>();
ResultSet results;
AccountObject obj;
results = pstmtAccountSelectAll.executeQuery();
while(results.next()) {
obj = new AccountObject(
results.getString(1),
results.getBoolean(2)
);
map.put( (obj.hashCode() ), obj);
}
return map;
}
}
A DetailController class defines the second tab. In this class a combobox of account names is initialized from the database via the DAO:
package tabpane.view;
import java.net.URL;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.ResourceBundle;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.AnchorPane;
import tabpane.db.NdDao;
import tabpane.model.AccountObject;
public class DetailController implements Initializable
{
#FXML public ComboBox<String> accNames;
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
getAccCBoxView();
} catch (SQLException ex) {
Logger.getLogger(DetailController.class.getName()).log(Level.SEVERE, null, ex);
}
accNames.getSelectionModel().select(0);
}
public void getAccCBoxView() throws SQLException
{
ObservableList<String> list = FXCollections.observableArrayList();
NdDao.connect();//
TreeMap<Integer, AccountObject> map = NdDao.selectAccounts() ;
NdDao.disconnect();//
AccountObject obj ;
Iterator<AccountObject> li = map.values().iterator();
while (li.hasNext()){
obj = li.next();
list.add(obj.getAccName());
}
accNames.setItems(list);
}
}
To begin with, there is one entry In the databse table “L Enqvist”. In the detail tab combo box, this looks like this:
[![Fig 1][1]][1]
Fig 1.
I then enter a new name via the Input tab and press Save:
[![Fig 2][2]][2]
Fig 2
A dump of the ACCOUNTS table shows that the addition has been successful:
ACCNAME ISENABLED
L Enqvist false
K Berg false
If I then click the Account tab, I would expect to find this in the combo box:
[![Fig 3][3]][3]
Fig 3.
However, the combobox is not updated and all I see is the view in Fig 1. I really don’t have a clue how to proceed and would greatly appreciate some pointers, maybe the code required to get this working as expected or some specific reference information. I have found nothing so far in extensive searches on the internet and wonder if this kind of implementation is in fact possible.
In first approach I get the following error dump:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at tabpane.view.MainController.reloadAccounts(MainController.java:31)
at tabpane.view.MainController.lambda$initialize$0(MainController.java:26)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.control.Tab$1.invalidated(Tab.java:209)
at javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:109)
at javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:144)
at javafx.scene.control.Tab.setSelected(Tab.java:185)
at javafx.scene.control.TabPane$TabPaneSelectionModel.select(TabPane.java:722)
at javafx.scene.control.TabPane$TabPaneSelectionModel.select(TabPane.java:735)
at javafx.scene.control.TabPane$TabPaneSelectionModel.select(TabPane.java:656)
at com.sun.javafx.scene.control.behavior.TabPaneBehavior.selectTab(TabPaneBehavior.java:122)
at com.sun.javafx.scene.control.skin.TabPaneSkin$TabHeaderSkin$5.handle(TabPaneSkin.java:1332)
at com.sun.javafx.scene.control.skin.TabPaneSkin$TabHeaderSkin$5.handle(TabPaneSkin.java:1317)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:394)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$353(GlassViewEventHandler.java:432)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:431)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
I experimented with a number of different paths without success:
detailController = new FXMLLoader(getClass().getResource("DetailView.fxml")).getController();
detailController = new FXMLLoader(getClass().getResource("/DetailView.fxml")).getController();
detailController = new FXMLLoader(getClass().getResource("./DetailView.fxml")).getController();
detailController = new FXMLLoader(getClass().getResource("view/DetailView.fxml")).getController();
detailController = new FXMLLoader(getClass().getResource("/view/DetailView.fxml")).getController();
detailController = new FXMLLoader(getClass().getResource("src/view/DetailView.fxml")).getController();
detailController = new FXMLLoader(getClass().getResource("/src/view/DetailView.fxml")).getController();
My project heirarchy looks like above:
[![Fig 4][4]][4]
EDIT 3 Possible Solution
Move the Account combo box from the Detail pane to the MainController.
With SceneBuilder move the combo box to coordinates that make it appear it is part of the detailPane tab. Also in SB create a reference to accNames and a reference to a method to control it, ie, handleAccNamesCBox().
Set the combo box to visible when the accountTab is clicked in MainController.
public class MainController implements Initializable {
#FXML public TabPane tabPane;
#FXML public Tab inputTab;
#FXML public Tab accountTab;
#FXML public ComboBox accNames;
private static String selectedAccount;
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
accNames.setVisible(false);
loadAccounts();
} catch (SQLException ex) {
Logger.getLogger(MainController.class.getName()).log(Level.SEVERE, null, ex);
}
accountTab.setOnSelectionChanged(event -> {
try {
loadAccounts();
accNames.setVisible(true);
} catch (SQLException ex) {
Logger.getLogger(MainController.class.getName()).log(Level.SEVERE, null, ex);
}
});
inputTab.setOnSelectionChanged(event -> {
accNames.setVisible(false);
});
accNames.getSelectionModel().select(0);
}
#FXML private void handleAccNamesCBox () {
try {
selectedAccount = accNames.getSelectionModel().getSelectedItem().toString();
} catch (NullPointerException e) {}
}
public static String setSelectedAccount() {
return selectedAccount;
}
public void loadAccounts() throws SQLException
{
ObservableList<String> list = FXCollections.observableArrayList();
NdDao.connect();//
TreeMap<Integer, AccountObject> map = NdDao.selectAccounts() ;
NdDao.disconnect();//
AccountObject obj ;
Iterator<AccountObject> li = map.values().iterator();
while (li.hasNext()){
obj = li.next();
list.add(obj.getAccName());
}
accNames.setItems(list);
}
#FXML private void handleExitBtn() {
System.exit(0);
}
}
The input controller and inputPane are unchanged and most of the handling logic stripped out of the detailPane:
public class DetailController implements Initializable{
#FXML private AnchorPane detailPane;
#Override
public void initialize(URL location, ResourceBundle resources) {}
}
A solution is still required to retrieve the selected value from the MainController and do something with it in the DetailController, but at least the combo box update is now working as expected. I am now convinced after days of wasted effort trying to resolve the persistent nullpointer exception when trying to load the tabs in some way, that there is no other solution and that the autoupdate feature on JavaFX via object properties does not work in the way I had originally hoped for. Its fine when updating objects in the same view but breals down when one tries to update objects from one view like one tab and get the updated result in another view, ie a different tab.
The solution appears to be to access the controller view of the relevant tab from the main controller by embedding a reference to the tab’s controller in the main controller and then perform whatever operations you need via the controller on the embedded tab’s GUI. So the main controller now looks like this:
package tabpane.view;
import java.net.URL;
import java.sql.SQLException;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
public class MainController implements Initializable{
#FXML private TabPane tabPane;
// Inject input tab content.
#FXML private Tab inputTab;
// Inject input tab controller
#FXML private InputController includedInputViewController;
// Inject account detail tab.
#FXML private Tab detailTab;
// Inject account detail tab controller
#FXML private DetailController includedDetailViewController;
#FXML private Button exitBtn;
#Override
public void initialize(URL location, ResourceBundle resources) {
detailTab.setOnSelectionChanged(event -> {
reloadAccounts();
});
}
private void reloadAccounts() {
try {
includedDetailViewController.getAccCBoxView();
} catch (SQLException ex) {
ex.printStackTrace();
}
includedDetailViewController.accNames.getSelectionModel().select(0);
}
#FXML private void handleExitBtn() {
System.exit(0);
}
}
The reference mapping from the main controller to the fxml view can be seen in the fx:include statements in the main fxml. Its important to be aware that the embedded view id in the fxml must be appended with Controller in the main controller. That is an fxml naming convention for this kind of embedded view.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tabpane.view.MainController">
<children>
<TabPane fx:id="tabPane" prefHeight="345.0" prefWidth="600.0" style="-fx-background-color: blue;" tabClosingPolicy="UNAVAILABLE">
<tabs>
<Tab fx:id="inputTab" closable="false" text="Input">
<content>
<fx:include fx:id="includedInputView" source="InputView.fxml" />
</content>
</Tab>
<Tab fx:id="detailTab" closable="false" text="Detail">
<content>
<fx:include fx:id="includedDetailView" source="DetailView.fxml" />
</content>
</Tab>
</tabs>
</TabPane>
<Button fx:id="exitBtn" layoutX="529.0" layoutY="345.0" mnemonicParsing="false" onAction="#handleExitBtn" text="Exit" />
</children>
</AnchorPane>
In this application, an updated view of the combo box is always performed when the user clicks the Account tab from the MainController view. So if one updates the database via the Input tab and then clicks the Account tab, the updated view of the database is always presented in the Account tab combo box accNames. The Detail controller now looks like this:
package tabpane.view;
import java.net.URL;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.ResourceBundle;
import java.util.TreeMap;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import tabpane.db.NdDao;
import tabpane.model.AccountObject;
public class DetailController implements Initializable
{
#FXML protected ComboBox<String> accNames;
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
getAccCBoxView();
} catch (SQLException ex) {}
accNames.getSelectionModel().select(0);
}
public void getAccCBoxView() throws SQLException
{
ObservableList<String> list = FXCollections.observableArrayList();
NdDao.connect();//
TreeMap<Integer, AccountObject> map = NdDao.selectAccounts() ;
NdDao.disconnect();//
AccountObject obj ;
Iterator<AccountObject> li = map.values().iterator();
while (li.hasNext()){
obj = li.next();
list.add(obj.getAccName());
}
accNames.setItems(list);
}
}
For the record the input view and controller look much the same as before:
package tabpane.view;
import java.net.URL;
import java.sql.SQLException;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import tabpane.db.NdDao;
import tabpane.model.AccountObject;
public class InputController implements Initializable {
#FXML private TextField accName;
#FXML private CheckBox isEnabled;
#Override
public void initialize(URL location, ResourceBundle resources) {}
#FXML private void handleSaveBtn() throws SQLException {
AccountObject obj = new AccountObject(accName.getText(), isEnabled.isSelected());
NdDao.connect();
NdDao.insertAccount(obj);
NdDao.disconnect();
}
}
Input View:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tabpane.view.InputController">
<children>
<Label ellipsisString="" graphicTextGap="0.0" layoutX="134.0" layoutY="92.0" text="Account holder:" textFill="MAGENTA" />
<CheckBox fx:id="isEnabled" ellipsisString="" graphicTextGap="0.0" layoutX="247.0" layoutY="137.0" mnemonicParsing="false" prefHeight="21.0" prefWidth="132.0" text="Enable" textFill="MAGENTA" />
<TextField fx:id="accName" layoutX="247.0" layoutY="87.0" prefHeight="31.0" prefWidth="245.0" />
<Button fx:id="saveBtn" layoutX="239.0" layoutY="200.0" mnemonicParsing="false" onAction="#handleSaveBtn" style="-fx-background-color: green;" text="Save" textFill="#eeecec" />
</children>
</AnchorPane>
The reference in one of my comments above to the FXML composition document is especially useful if one finds this a bit confusing to start with.

JavaFX bind a controller variable to a component property

Say I have a controller with
#FXML private ObservableList<String> myStrings = FXCollections.observableArrayList();
Is it possible to write any FXML which will wire up a ListView with myStrings as its items?
My first try was:
<ListView>
<items fx:id="myStrings"/>
</ListView>
But this complains that fx:id is not valid in that position. I also tried
<ListView items="${controller.myStrings}"/>
...but it couldn't resolve that value.
Please do not post this solution:
<ListView fx:id="myStringsListView"/>
// In controller
#FXML private ListView<String> myStringsListView;
#FXML public void initialize() {
myStringsListView.setItems(myStrings);
}
This is what I am doing now but the amount of indirection and boilerplate here hurts me.
The following works
<?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="ListViewController">
<center>
<ListView items="${controller.myStrings}" />
</center>
</BorderPane>
with the following controller (the main difference, I think, being that you either didn't define an accessor method for the list, or named it incorrectly):
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class ListViewController {
private final ObservableList<String> myStrings = FXCollections.observableArrayList();
public ListViewController() {
myStrings.addAll("One", "Two", "Three");
}
public ObservableList<String> getMyStrings() {
return myStrings ;
}
}
This quick test:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ListViewItemsFromControllerTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setScene(new Scene(FXMLLoader.load(getClass().getResource("ListViewItemsFromController.fxml"))));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
produces

Getting a listener class to change values of a Label in a controller

I'm new to Java and JavaFX -- I'm making a small player application and are finding it a challenge to get the duration timer to display on a label on my display.
My latest attempt was creating a TimeListener.java class which would change the duration values for each new song played and set them on the label in another class but that idea is flawed as I came across a non-static error.
TrackPlayer object class
private MediaPlayer player;
private Media track;
private String filepath;
private Duration duration;
public TrackPlayer(String filepath) {
this.filepath = filepath;
track = new Media(filepath);
player = new MediaPlayer(track);
player.setOnReady(() -> {
duration = track.getDuration();
System.out.println("Duration: " + duration);
});
player.currentTimeProperty().addListener(new TimeListener());
}
TimeListener class
public class TimeListener implements ChangeListener<Duration> {
#Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
TrackPlayerController.setTime(newValue.toString());
}
}
FXML Controller class
#FXML
private Label runTime;
...
public void setTime(String time) {
//runTime.setText(time);
}
How else could I approach this problem? I want a label which would display something like 00:00:00 (elapsed) / 00:00:00 (duration) but I'm sure if I just get the duration working I can also get the elapsed time working.
Example with the same problem but most if not all features removed
TrackPlayer class
package logic;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaPlayer.Status;
import javafx.util.Duration;
public class TrackPlayer {
private MediaPlayer player;
private Media track;
private String filepath;
private Duration duration;
public TrackPlayer(String filepath) {
this.filepath = filepath;
track = new Media(filepath);
player = new MediaPlayer(track);
player.setOnReady(() -> {
duration = track.getDuration();
System.out.println("Duration: " + duration);
});
}
public void playSong() {
System.out.println("Playing song");
player.play();
}
public void pauseSong() {
System.out.println("Pausing song");
player.pause();
}
public void stopSong() {
System.out.println("Stopping song");
player.stop();
}
public Status getStatus() {
return player.getStatus();
}
public Duration getDuration() {
return duration;
}
public Duration getCurrentTime() {
return player.getCurrentTime();
}
public Duration getStartTime() {
return player.getStartTime();
}
public void setSeek(Duration duration) {
player.seek(duration);
}
public Media getMedia() {
return player.getMedia();
}
public ReadOnlyObjectProperty<Duration> currentTimeProperty() {
return player.currentTimeProperty();
}
public Duration getTotalDuration() {
return player.getTotalDuration();
}
}
TrackPlayerController class
package gui;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import logic.TrackPlayer;
import logic.Track;
public class TrackPlayerController implements Initializable {
#FXML
private TableView<Track> playingTable;
#FXML
private TableColumn<Track, String> playingTitleCol;
#FXML
private TableColumn<Track, String> playingArtistCol;
#FXML
private TableColumn<Track, String> playingGenreCol;
#FXML
private TableColumn<Track, String> playingRunTimeCol;
#FXML
private Label runTime;
private TrackPlayer player;
#Override
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
playingTitleCol.setCellValueFactory(new PropertyValueFactory<>("TrackTitle"));
playingArtistCol.setCellValueFactory(new PropertyValueFactory<>("TrackArtist"));
playingGenreCol.setCellValueFactory(new PropertyValueFactory<>("TrackGenre"));
playingRunTimeCol.setCellValueFactory(new PropertyValueFactory<>("RunTime"));
player.currentTimeProperty().addListener(observable -> {
setTime(player.getCurrentTime()
+ " / "
+ player.getTotalDuration());
});
playingTable.setRowFactory(tv -> { // Function for double-click to play (load)
TableRow<Track> row = new TableRow<>();
row.setOnMouseClicked(event -> {
if (event.getClickCount() == 2 && (!row.isEmpty())) {
play();
}
});
return row;
});
}
#FXML
private void play() {
}
#FXML
private void reset(ActionEvent e) {
}
#FXML
private void remove(ActionEvent e) {
}
#FXML
private void removeAll(ActionEvent e) {
}
#FXML
private void search(ActionEvent e) throws IOException {
}
public void setTime(String time) {
runTime.setText(time);
}
}
TrackPlayerMain class
package gui;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;
public class TrackPlayerMain extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
FXMLLoader trackPlayerLoader = new FXMLLoader(getClass().getResource("TrackPlayer.fxml"));
root.setCenter(trackPlayerLoader.load());
TrackPlayerController trackPlayerController = trackPlayerLoader.getController();
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
TrackPlayer FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gui.TrackPlayerController">
<children>
<Slider fx:id="timeSlider" layoutX="9.0" layoutY="333.0" prefHeight="25.0" prefWidth="582.0" />
<Label alignment="BOTTOM_LEFT" layoutX="23.0" layoutY="10.0" prefHeight="17.0" prefWidth="75.0" text="Now Playing" />
<Button fx:id="play" layoutX="250.0" layoutY="361.0" mnemonicParsing="false" onAction="#play" prefHeight="25.0" prefWidth="100.0" text="Play" />
<Button fx:id="ff" layoutX="356.0" layoutY="361.0" mnemonicParsing="false" text=">>" />
<Button fx:id="rw" layoutX="211.0" layoutY="361.0" mnemonicParsing="false" text="<<" />
<Button fx:id="reset" layoutX="22.0" layoutY="361.0" mnemonicParsing="false" onAction="#reset" prefWidth="59.0" text="Reset" />
<Button fx:id="remove" layoutX="498.0" layoutY="305.0" mnemonicParsing="false" onAction="#remove" prefWidth="83.0" text="Remove" />
<Label fx:id="runTime" alignment="TOP_CENTER" layoutX="516.0" layoutY="350.0" prefHeight="17.0" prefWidth="75.0" text="00:00 / 00:00" textFill="#00000065">
<font>
<Font size="11.0" />
</font>
</Label>
<Button fx:id="removeAll" layoutX="401.0" layoutY="305.0" mnemonicParsing="false" onAction="#removeAll" prefHeight="25.0" prefWidth="83.0" text="Remove All" />
<TableView fx:id="playingTable" layoutX="18.0" layoutY="32.0" prefHeight="263.0" prefWidth="563.0">
<columns>
<TableColumn fx:id="playingTitleCol" editable="false" prefWidth="140.75" resizable="false" text="Title" />
<TableColumn fx:id="playingArtistCol" editable="false" prefWidth="140.75" resizable="false" text="Artist" />
<TableColumn fx:id="playingGenreCol" editable="false" prefWidth="140.75" resizable="false" text="Genre" />
<TableColumn fx:id="playingRunTimeCol" prefWidth="140.75" resizable="false" text="Run Time" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
<Button fx:id="search" layoutX="303.0" layoutY="305.0" mnemonicParsing="false" onAction="#search" prefHeight="0.0" prefWidth="83.0" text="Search" />
</children>
</AnchorPane>
From what I assume it is throwing NullPointerException because it's trying to initalize the listener with the current time and duration however the player object has not been created yet (as no song is played right from the start, only when selected and pressed play) -- if that is the case how can I add the listener?
Edit: Okay so I've tested what causes the NullPointerException and it's the player being null, as the program launches when I do this.
if (player != null) {
player.currentTimeProperty().addListener(observable -> {
runTime.setText(player.getCurrentTime()
+ " / "
+ player.getTotalDuration());
});
}
However when I do this the listener doesn't get initialized as the runTime label does not change at all. This is my problem that I'm trying to solve. How can I go about fixing it?
Non-FXML Based Sample
Here is some sample code, it doesn't use FXML, but the principles are the same whether you have FXML involved or not. You just add a listener to the relevant property and take action as it changes. I didn't format the duration as you have in your question, but that is trivial and different from the problem of responding to changes in the listener.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.media.*;
import javafx.stage.Stage;
public class VideoPlayerExample extends Application {
private static final String MEDIA_LOC =
"http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv";
#Override
public void start(final Stage stage) throws Exception {
final MediaPlayer oracleVid = new MediaPlayer(
new Media(MEDIA_LOC)
);
Label progress = new Label();
oracleVid.currentTimeProperty().addListener(observable -> {
progress.setText(
oracleVid.getCurrentTime()
+ " / "
+ oracleVid.getTotalDuration()
);
});
VBox layout = new VBox(10, progress, new MediaView(oracleVid));
stage.setScene(new Scene(layout, 540, 208));
stage.show();
oracleVid.play();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
where should I place the listener?
It's difficult to recommend without seeing full code, so I'll just make some assumptions and provide advice on that to get you started. So, let's assume that:
You are defining and embedding your custom media player control in FXML similar to the mechanism outlined in:
How to create an FXML file for an already created new component in java than add it to scene builder?
The duration tracking label is (for some reason) not part of the custom media control.
Your encompassing FXML therefore includes two components, your custom media player and the duration label.
In this case the listener is situated in the controller for the encompassing FXML file that includes the media player and the duration label and is set up during the initialize() call for that controller.
FXML Based Sample
In general, for something like this for generic reuse you might create a custom control as outlined previously, but for brevity this sample will just use the in-built MediaView and Label controls rather than a custom control.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.media.MediaView?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" spacing="10.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="plot.VideoPlayerController">
<children>
<Label fx:id="progress" text="Label" />
<MediaView fx:id="mediaView" fitHeight="208.0" fitWidth="540.0" />
</children>
</VBox>
App
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class VideoPlayerApp extends Application {
#Override
public void start(final Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("player.fxml"));
Parent root = loader.load();
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
Controller
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
public class VideoPlayerController {
private static final String MEDIA_LOC =
"http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv";
#FXML
MediaView mediaView;
#FXML
Label progress;
public void initialize() {
final MediaPlayer oracleVid = new MediaPlayer(
new Media(MEDIA_LOC)
);
oracleVid.currentTimeProperty().addListener(observable -> {
progress.setText(
oracleVid.getCurrentTime()
+ " / "
+ oracleVid.getTotalDuration()
);
});
mediaView.setMediaPlayer(oracleVid);
oracleVid.play();
}
}

JavaFX - DatePicker always returns null

DatePicker is not updating after change date.
I was looking for an answer and i found this topic:
JavaFX datepicker not updating value
, but it doesn't work.
My listener for DatePicker:
public void datePickerListener() {
this.datePicker = new DatePicker();
this.datePicker.setShowWeekNumbers(false);
this.datePicker.setOnAction(event -> {
LocalDate date = this.datePicker.getValue();
System.out.println("Selected date: " + date);
});
}
Additionally I tried get data without listener like:
public void searchAvailableAppointments() {
LocalDate date;
String date2;
date = this.datePicker.getValue();
date2 = this.datePicker.getEditor().getText();
System.out.println(date);
System.out.println(date2);
}
Maybe anyone had similar problem and found workaround?
EDIT1:
I have created simple project to check behaviour of DatePicker:
Controller class:
package sample;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.DatePicker;
import java.net.URL;
import java.time.LocalDate;
import java.util.ResourceBundle;
public class Controller implements Initializable {
#FXML
DatePicker datePicker;
#Override
public void initialize(URL location, ResourceBundle resources) {
this.datePicker = new DatePicker();
}
public void pressButton() {
LocalDate date;
date = datePicker.getValue();
System.out.println(date);
}
}
FXML file:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="142.0" prefWidth="504.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<DatePicker fx:id="datePicker" layoutX="65.0" layoutY="58.0" prefHeight="25.0" prefWidth="295.0" />
<Button layoutX="387.0" layoutY="59.0" mnemonicParsing="false" onAction="#pressButton" prefHeight="25.0" prefWidth="80.0" text="Print result" />
</children>
</AnchorPane>
Main class:
package sample;
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));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
But I have still no idea how to make it work.
You should not re-initialize the DatePicker inside the initialize(). When FXMLLoader loads the fxml, it instantiates all the fields and injects them into the references which are annotated with #FXML.
If you re-initialize the field, the reference to the original object which is rendered on the view is lost and you are using the reference to the newly created object, therefore the value fetched from this object will always be null.
#Override
public void initialize(URL location, ResourceBundle resources) {
// this.datePicker = new DatePicker(); <-- Should not exist
}

onScroll listener does not working in TableView in JavaFX 2

I'm trying to use the onScroll event listener of a TableView component:
FXML:
<TableView fx:id="table" onScroll="#doSomething" tableMenuButtonVisible="true" VBox.vgrow="ALWAYS">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
Java Controller:
#FXML
void doSomething(ActionEvent event)
{
System.out.println("Object: " + event.getSource());
}
But it does not working! What I'm doing wrong?
I need to capture the vertical scroller to manually control the scroll position and fetch related data according to scroll down or up.
Thanks everybody!
I think what might be happening is that the TableView includes itself is consuming the scroll event and processing it internally, so it never gets to your application handler.
Initially, I thought you might want to use onScrollTo rather than onScroll, but that doesn't seem to really address the issue.
I think the solution which works is to apply a filter on the scroll event.
In addition, you can write code that makes use of with scrollTo calls to "manually control the scroll position".
Here is some sample code you can try (Java 8):
TableScrollerApp.java
package finder;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TableScrollerApp extends Application {
#Override public void start(final Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("tablescroller.fxml")
);
Parent parent = loader.load();
stage.setScene(new Scene(new Group(parent)));
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
TableScrollerController.java
package finder;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.fxml.FXML;
import javafx.scene.control.ScrollToEvent;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.ScrollEvent;
import java.util.Arrays;
import java.util.stream.Collectors;
public class TableScrollerController {
private static final String[] fruitNames = {
"apples", "oranges", "pears", "peaches",
"guavas", "bananas", "jackfruit", "durians"
};
#FXML
private TableView<Fruit> fruitsTable;
#FXML
private TableColumn<Fruit, String> fruitsColumn;
#FXML
protected void initialize() {
fruitsColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
fruitsTable.addEventFilter(ScrollEvent.ANY, event ->
System.out.println("Coded scroll filter: " + event)
);
fruitsTable.getItems().setAll(
Arrays.stream(fruitNames)
.map(Fruit::new)
.collect(Collectors.toList())
);
fruitsTable.scrollTo(5);
}
#FXML
protected void onScrollHandler(ScrollEvent scrollEvent) {
System.out.println("FXML referenced scroll handler: " + scrollEvent);
}
#FXML
protected void onScrollToHandler(ScrollToEvent<Integer> scrollToEvent) {
System.out.println("FXML referenced onScroll handler: " + scrollToEvent);
}
public static class Fruit {
private ReadOnlyStringWrapper name;
public Fruit(String name) {
this.name = new ReadOnlyStringWrapper(name);
}
public String getName() {
return name.get();
}
public ReadOnlyStringProperty nameProperty() {
return name;
}
}
}
tablescroller.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<TableView fx:id="fruitsTable"
maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity"
onScroll="#onScrollHandler"
onScrollTo="#onScrollToHandler"
prefHeight="100.0" prefWidth="250.0"
xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="finder.TableScrollerController">
<columns>
<TableColumn fx:id="fruitsColumn"
maxWidth="800.0" minWidth="200.0" prefWidth="-1.0"
text="Fruits" />
</columns>
</TableView>

Categories

Resources