JavaFX - DatePicker always returns null - java

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
}

Related

Issues with setting bounds of NumberAxis based on user input in FXML JavaFX application

SUMMARY: I am trying to design an application where the user inputs into TextFields, and upon clicking a button ONCE:
The values are retrieved
The scene switches to one containing a lineChart
The number axes' bounds are set corresponding to the values entered before.
However I'm finding it very hard to understand how to make the program flow correctly for this to work. My main issues are with switching the scene then accessing x_axis and y_axis, where for some reason "this.x_axis" is null.
Main Class:
public class Main extends Application {
#Override
public void start(Stage stage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("menuScene.fxml"));
Scene scene = new Scene(root);
stage.setTitle("FT_Tool");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Controller: (calculate is called after clicking button)
public class MenuController {
private Stage stage;
private Scene scene;
private Parent root;
#FXML
private TextField funcBox;
#FXML
private TextField sampBox;
#FXML
private TextField limitBox;
Expression func;
double sampFreq;
double limit;
#FXML
public void calculate(ActionEvent event) throws IOException{
func = new ExpressionBuilder(funcBox.getText()).variables("x").build();
sampFreq = Double.parseDouble(sampBox.getText());
limit = Double.parseDouble(limitBox.getText());
//=== SWITCHING SCENES ===//
root = FXMLLoader.load(getClass().getResource("graphScene.fxml"));
stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
scene = new Scene(root);
stage.setScene(scene);
stage.show();
graphing();
}
#FXML
private LineChart sampleGraph;
#FXML
private NumberAxis x_axis;
#FXML
private NumberAxis y_axis;
public void graphing(){
x_axis.setUpperBound(limit);
}
}
2nd Scene:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<children>
<LineChart fx:id="sampleGraph" layoutX="24.0" layoutY="16.0" prefHeight="368.0" prefWidth="552.0" title="Samples taken">
<xAxis>
<NumberAxis fx:id="x_axis" autoRanging="false" label="Time (s)" tickUnit="1.0"/>
</xAxis>
<yAxis>
<NumberAxis fx:id="y_axis" autoRanging="false" label="Amplitude (m)"/>
</yAxis>
</LineChart>
</children>
</AnchorPane>
When I run it, application runs fine and after clicking button the scene switches and linechart is shown but without the right bounds.
This line of code: x_axis.setUpperBound(limit) causes the following error:
Caused by: java.lang.NullPointerException: Cannot invoke "javafx.scene.chart.NumberAxis.setUpperBound(double)" because "this.x_axis" is null
Despite not having the fx:controller attribute defined in your FXML file, it looks like—based on your FXML controller class—you're trying to use the same FXML controller class with multiple FXML files. This is strongly discouraged. By default, a new controller instance is created every time the FXML file is loaded. Consequently, it quickly becomes difficult to reason about which fields should have been injected (i.e., non-null) at any given time. An FXML file should have a one-to-one mapping with an FXML controller class (or possibly no controller).
You have two views, which suggests two FXML files and thus two FXML controller classes. When you load the second view, you should communicate to its controller the needed information. For trivial applications you can do this directly, but for more complex applications it would be better to communicate via a model shared between the two controllers.
Example
Here's an example (based on your code but simplified for demonstration). It only asks for the upper bounds of the X and Y axes and then graphs random data within those bounds. It also makes use of TextFormatter to make it easier to grab the values out of the TextFields, and to ensure the user only enters positive integers.
FXML files are assumed to be located in the com.example package.
Main.java:
package com.example;
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 {
var root = FXMLLoader.<Parent>load(Main.class.getResource("Form.fxml"));
primaryStage.setScene(new Scene(root, 1000, 650));
primaryStage.show();
}
}
Form.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.GridPane?>
<GridPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.FormController" alignment="CENTER" vgap="20" hgap="15">
<Label text="Upper X Bound:" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
<TextField fx:id="xField" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
<Label text="Upper Y Bound:" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
<TextField fx:id="yField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
<Button text="Graph" onAction="#handleOnGraph" defaultButton="true" GridPane.rowIndex="2"
GridPane.columnIndex="2" GridPane.halignment="RIGHT"/>
</GridPane>
FormController.java:
package com.example;
import java.io.IOException;
import java.util.function.UnaryOperator;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.converter.IntegerStringConverter;
public class FormController {
#FXML private TextField xField;
#FXML private TextField yField;
private TextFormatter<Integer> xFieldFormatter;
private TextFormatter<Integer> yFieldFormatter;
#FXML
private void initialize() {
UnaryOperator<TextFormatter.Change> filter =
change -> {
if (change.getControlNewText().matches("\\d*")) {
return change;
}
return null;
};
xFieldFormatter = new TextFormatter<>(new IntegerStringConverter(), 50, filter);
xField.setTextFormatter(xFieldFormatter);
yFieldFormatter = new TextFormatter<>(new IntegerStringConverter(), 50, filter);
yField.setTextFormatter(yFieldFormatter);
}
#FXML
private void handleOnGraph(ActionEvent event) throws IOException {
event.consume();
int upperX = xFieldFormatter.getValue();
int upperY = yFieldFormatter.getValue();
var loader = new FXMLLoader(FormController.class.getResource("Chart.fxml"));
xField.getScene().setRoot(loader.load());
loader.<ChartController>getController().graph(upperX, upperY);
}
}
Chart.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<LineChart fx:id="chart" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.ChartController">
<xAxis>
<NumberAxis fx:id="xAxis" upperBound="50" autoRanging="false"/>
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" upperBound="50" autoRanging="false"/>
</yAxis>
</LineChart>
ChartController.java:
package com.example;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
public class ChartController {
#FXML private LineChart<Number, Number> chart;
#FXML private NumberAxis xAxis;
#FXML private NumberAxis yAxis;
void graph(int upperX, int upperY) {
xAxis.setUpperBound(upperX);
yAxis.setUpperBound(upperY);
generateAndGraphRandomData();
}
private void generateAndGraphRandomData() {
var data = FXCollections.<XYChart.Data<Number, Number>>observableArrayList();
for (int x = 0; x <= xAxis.getUpperBound(); x++) {
int y = (int) (Math.random() * yAxis.getUpperBound());
data.add(new XYChart.Data<>(x, y));
}
var series = new XYChart.Series<>("Random Demo Data", data);
chart.getData().add(series);
}
}

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

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

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.

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

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