Why is my TableColumn.setSortable() showing the sort graphic on the table header when I double-click on it, but it is not actually doing any sort at all? I would imagine it naturally knows how to sort Numbers? Do I have to set an explicit comparator even for types that have a natural sort behavior?
public class PenaltyDashboardManager {
private final TableView<Penalty> penaltyTable = new TableView<Penalty>();
/* ... */
private void initializeTable() {
penaltyTable.setItems(Penalty.getPenaltyManager().getPenalties());
penaltyTable.setEditable(true);
TableColumn<Penalty,Number> penaltyId = new TableColumn<>("ID");
penaltyId.setCellValueFactory(c -> c.getValue().getPenaltyIdProperty());
penaltyId.setEditable(true);
penaltyId.setSortable(true);
/* ... */
penaltyTable.getColumns.add(penaltyId);
}
}
UPDATE
Very odd. I tried to create a simple example to demonstate the sorting not working. But this simple column of integers is sorting just fine :/
public final class TableSortTest extends Application {
private static final ObservableList<NumericCombo> values = FXCollections.observableList(
IntStream.range(1, 100).mapToObj(i -> new NumericCombo()).collect(Collectors.toList()));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Collections.shuffle(values);
TableView<NumericCombo> tableView = new TableView<>();
tableView.setItems(values);
TableColumn<NumericCombo,Number> combo1 = new TableColumn<>("COMBO 1");
combo1.setCellValueFactory(c -> new ReadOnlyObjectWrapper<>(c.getValue().combo1));
TableColumn<NumericCombo,Number> combo2 = new TableColumn<>("COMBO 2");
combo2.setCellValueFactory(c -> c.getValue().combo2);
TableColumn<NumericCombo,Number> combo3 = new TableColumn<>("COMBO 3");
combo3.setCellValueFactory(c -> c.getValue().combo3);
tableView.getColumns().addAll(combo1,combo2,combo3);
Group root = new Group(tableView);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private static final class NumericCombo {
private static final Random rand = new Random();
private final int combo1;
private final IntegerProperty combo2;
private final IntegerProperty combo3;
private NumericCombo() {
combo1 = rand.nextInt((10000 - 0) + 1);
combo2 = new SimpleIntegerProperty(rand.nextInt((10000 - 0) + 1));
combo3 = new SimpleIntegerProperty(rand.nextInt((10000 - 0) + 1));
}
}
}
I found the issue! I was using my own implementation of ObservableList, called ObservableImmutableList. It wraps the ObservableList interface around a Guava ImmutableList. Since the ImmutableList is not modifiable, it cannot be sorted... even in a TableView.
This transitions to another issue I'm struggling to figure out. How do I sort my ObservableImmutableList? So I posted another question.
Related
I need to get two specific fields of every object inside an ObservableList, multiply them and get the sum of all their products. I also need to keep the sum updated
For now I'm trying to get the sum of only one of those fields first (just because its easier and might help me to understand what is happening).
I have discovered:
How to bind an ObjectBinding<BigDecimal> to a Label with a Formatter?
which is almost what I'm trying to do. However, applying the same code will throw a NullPointerException. Which I assume it's because there was more behind the scene.
Also, according to:
Javafx How to make a Binding to a list of Properties
the observableList needs to have and extractor so it can track the updates of the required field on each element.
Here is what I tried, only ONE field first!!:
// Graphical stuff
void createExempleMenu(){
ObservableList<Itens> orc = FXCollections.observableArrayList(o -> new Observable[] {o.valueProperty()});
Label valueFinalLabel;
valueFinalLabel.textProperty().bind( // this gets NullPointerException
Bindings.createObjectBinding(() ->{
if(orc==null || orc.isEmpty())
return BigDecimal.ZERO; // case orc is empty
else
return orc.stream().map(i->i.getValue()).reduce(BigDecimal.ZERO,BigDecimal::add);},
orc).asString()
);
}
// item
class Itens {
private ObjectProperty<BigDecimal> value;
public BigDecimal getValue() {
return value.get();
}
public void setValue(BigDecimal b){
value.set(b);
}
public void valueProperty(BigDecimal b){
return value;
}
}
You have a few issues with the Itens class:
You never initialize value.
The method valueProperty(BigDecimal b) must return
ObjectProperty<BigDecimal> instead of void.
You have a typo in method getValue(), instead of returning value.get() you are returning valor.get().
class Itens {
private final ObjectProperty<BigDecimal> value =
new SimpleObjectProperty<>(this, "value");
Itens(BigDecimal value) {
this.value.set(value);
}
public BigDecimal getValue() {
return valueProperty().get();
}
public void setValue(BigDecimal value){
valueProperty().set(value);
}
public ObjectProperty<BigDecimal> valueProperty(){
return value;
}
}
Also, you never initialize the Label.
Finally, you can improve your Bindings:
You can create a StringBinding directly instead of creating an
ObjectBinding and then creating StringBinding.
orc is never null, so you don't need to
check for null.
You don't need to check for orc.isEmpty() because when you reduce
the stream you are supplying an identity `BigDecimal.ZERO
Label valueFinalLabel = new Label();
ObservableList<Itens> orc = FXCollections.observableArrayList(
obs -> new Observable[] {obs.valueProperty()});
valueFinalLabel.textProperty().bind(
Bindings.createStringBinding(() -> orc.stream()
.filter(Objects::nonNull)
.map(Itens::getValue)
.reduce(BigDecimal.ZERO, BigDecimal::add).toString(), orc));
Test:
valueFinalLabel.textProperty().addListener((obs, oldVal, newVal) ->
System.out.println(String.format("Label change from %s to %s",
oldVal, newVal)));
Itens i1 = new Itens(BigDecimal.valueOf(1));
Itens i2 = new Itens(BigDecimal.valueOf(2));
Itens i3 = new Itens(BigDecimal.valueOf(3));
orc.add(i1);
orc.add(i2);
orc.add(i3);
i1.setValue(BigDecimal.valueOf(4));
Output:
Label change from 0 to 1
Label change from 1 to 3
Label change from 3 to 6
Label change from 6 to 9
Always remember to initialize your variable folks:
Label valueFinalLabel = new Label("some text");
I may write another question to ask about the product of two fields, which was my initial objective
Where is a complete sample for anyone who stumble here:
public class Main extends Application{
#Override
public void start(Stage primaryStage) {
ObservableList<Itens> orc = FXCollections.observableArrayList();
Label label = new Label("0.00");
label.textProperty().bind( // this gets NullPointerException
Bindings.createObjectBinding(() ->{
if(orc==null || orc.isEmpty())
return BigDecimal.ZERO; // case orc is empty
else
return orc.stream().map(i->i.getValue()).reduce(BigDecimal.ZERO,BigDecimal::add);},
orc).asString()
);
Button btn = new Button("add stuff");
btn.setOnAction(e->{
orc.addAll(new Itens("1"),new Itens("2"),new Itens("3"),new Itens("4"),new Itens("5"));
});
VBox vbox = new VBox();
vbox.getChildren().addAll(label,btn);
Scene scene = new Scene(vbox);
primaryStage = new Stage();
primaryStage.setScene(scene);
primaryStage.setWidth(200);
primaryStage.setHeight(200);
primaryStage.show();
}
}
public static void main(String[] args) {
launch(args);
}
}
public class Itens {
private SimpleObjectProperty<BigDecimal> value;
Itens(String v){
value = new SimpleObjectProperty<BigDecimal>(new BigDecimal(v));
}
public BigDecimal getValue() {
return value.get();
}
public void setValue(BigDecimal b){
value.set(b);
}
public SimpleObjectProperty<BigDecimal> valueProperty(){
return value;
}
}
I want the title of my notes-program to change whenever my list (notes) changes. To achieve this I wanted to bind an IntegerProperty to the size of my list, but it says:
The method bind(ObservableValue<? extends Number>) in the type Property is not applicable for the arguments (int)
Does this mean I should cast the size from int to a Number (tried it but there was another problem) or is there an even easier solution?
public class Notes extends Stage {
ObservableList<String> notes = FXCollections.observableArrayList();
public Notes() {
this.setup();
}
private void setup() {
IntegerProperty size = new SimpleIntegerProperty();
size.bind(this.notes.size());
this.setTitle(String.format("Notes (%d)", size.getValue()));
final Scene scene = new Scene(this.createRootPane());
this.setScene(scene);
}
}
Just bind your title property to the list size’s asString binding:
titleProperty().bind(Bindings.size(notes).asString("Notes (%d)"));
I'm trying to populate a TableView in JavaFX, but only one of the columns is being populated with Data. I've been following the oracle documentation and think that my name conventions are correct.
Data Model:
import javafx.beans.property.SimpleIntegerProperty;
public class PeakClassification {
private final SimpleIntegerProperty peakStart;
private final SimpleIntegerProperty peakEnd;
private final SimpleIntegerProperty peakMaxima;
private final SimpleIntegerProperty peakHeight;
private final SimpleIntegerProperty peakWidth;
public PeakClassification(int peakStart, int peakEnd, int peakMaxima, int peakHeight) {
this.peakStart = new SimpleIntegerProperty(peakStart);
this.peakEnd = new SimpleIntegerProperty(peakEnd);
this.peakMaxima = new SimpleIntegerProperty(peakMaxima);
this.peakHeight = new SimpleIntegerProperty(peakHeight);
this.peakWidth = new SimpleIntegerProperty(peakEnd - peakStart);
}
public int getPeakWidth() {
return peakWidth.get();
}
public int getPeakHeight() {
return peakHeight.get();
}
public int getPeakStart() {
return peakStart.get();
}
public int getPeakEnd() {
return peakEnd.get();
}
public int getPeakMaxima() {
return peakMaxima.get();
}
}
Code for creating the table:
//instantiate the table
TableView tableView = new TableView();
//start values
TableColumn startValue = new TableColumn("Start pos");
startValue.setMinWidth(100);
startValue.setCellValueFactory(new PropertyValueFactory<PeakClassification, Integer>("peakStart"));
TableColumn maximumValue = new TableColumn("Max pos");
startValue.setCellValueFactory(new PropertyValueFactory<PeakClassification, Integer>("peakMaxima"));
tableView.setItems(peakClassifications);
tableView.getColumns().addAll(startValue, maximumValue);
I've printed out the peakClassifications list to console to verify that the maximumValue isn't null.
The getter for the peakMaxima field is getPeakMaxima() so it should be able to find it. I've looked at other stackoverflow entries and that seems to be the issue in most of the cases.
Here's a snippet of the result:
Its probably an obvious mistake. Any ideas?
Thanks.
Error is here, you mistankenly used startValue variable again instead of maximumValue
TableColumn maximumValue = new TableColumn("Max pos");
startValue.setCellValueFactory(new PropertyValueFactory<PeakClassification, Integer>("peakMaxima"));
Hi StackOverflow people,
First question here, I'm stuck on this code and cannot move forward, tried different approaches but cannot figure out why this is happening.
The code is intended to be a few lists each one represents a day of the week, and each of the list has all the possible time. Now, everytime I ran the code each list, even when not update, is using the last date available. For the sake of the example, remove almost all the lists and leave only 2.
The update on the date is being done on this line, t1.setFecha(lunesDate.plusDays(i));, but if for instance, I remove this line on one of the lists, the list is getting the date updated, even if this is happening on another list, with another variable!! It is like the JVM is considering all the lists to be the same... Makes no sense for me...
Can anyone point where is the issue on the code?
Class Turno.class
import java.time.LocalDate;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
public class Turno {
private static final int LIBRE = 1;
private static final int RESERVADO = 2;
private static final int CUMPLIDO = 3;
private static final int CERRADO = 4;
private SimpleIntegerProperty id = new SimpleIntegerProperty();
private SimpleObjectProperty<LocalDate> fecha = new SimpleObjectProperty<LocalDate>();
private SimpleIntegerProperty idhorario = new SimpleIntegerProperty();
private SimpleStringProperty horario = new SimpleStringProperty();
private SimpleIntegerProperty estado = new SimpleIntegerProperty();
private SimpleIntegerProperty idProfesional = new SimpleIntegerProperty();
private SimpleStringProperty profesional = new SimpleStringProperty();;
private SimpleIntegerProperty idPaciente = new SimpleIntegerProperty();
private SimpleStringProperty paciente = new SimpleStringProperty();;
private SimpleStringProperty observaciones = new SimpleStringProperty();;
public Turno(int id, LocalDate d, int idh, String h, int e, int idpro, String pro, int idpac, String pac,
String o) {
this.setId(id);
this.setFecha(d);
this.setIdHorario(idh);
this.setHorario(h);
this.setEstado(e);
this.setIdProfesional(idpro);
this.setProfesional(pro);
this.setIdPaciente(idpac);
this.setPaciente(pac);
this.setObservaciones(o);
}
public Turno() {
}
// ID
public final SimpleIntegerProperty idProperty() {
return this.id;
}
public final int getId() {
return this.idProperty().get();
}
public final void setId(final int i) {
this.idProperty().set(i);
}
/* Bunch of getter and setters for properties, just like the one above */
Class TestTurno.class
public class TestTurnos extends Application {
private static Turno turnoSeleccionado = null;
ScrollPane scrollPane = new ScrollPane();
HBox listas = new HBox();
VBox vBoxL = new VBox();
VBox vBoxM = new VBox();
ListView<Turno> listViewTurnosL = new ListView<>();
ListView<Turno> listViewTurnosM = new ListView<>();
List<Turno> listaHorarios = new ArrayList<>();
List<Turno> listaTurnos = new ArrayList<>();
public static void Main(String[] args) {
launch(args);
}
#Override
public void start(Stage escenario) throws Exception {
// Here we get the current Monday date, in order to prepare for the current week
LocalDate lunesDate = null;
LocalDate diaSemana = null;
LocalDate diaHoy = LocalDate.now();
int d = diaHoy.getDayOfWeek().getValue();
lunesDate = diaHoy.minusDays(d - 1);
// Give the schedules to each day list
listViewTurnosL.setItems(FXCollections.observableList(listaHorarios));
listViewTurnosM.setItems(FXCollections.observableList(listaHorarios));
// Then we look for more data on the DB,
for (int i = 0; i < 2; i++) {
// Database magic happens here, we filled the listaTurnos, not relevant
// We make the lists
//
//!!! Here is where the glitch appears, debugging shows that it only gets into the switch on the right conditions,
// but it keeps on updating the date on any of the lists, even when it is updating another list
switch (i) {
case 0: {
for (Turno t1 : listViewTurnosL.getItems()) {
t1.setFecha(lunesDate.plusDays(i));
}
// Value of t1.getFecha() is 1
break;
}
case 1: {
for (Turno t2 : listViewTurnosM.getItems()) {
t2.setFecha(lunesDate.plusDays(i));
}
// Value of t1.getFecha() is 2 !!!!!
// Value of t2.getFecha() is 2
break;
}
}
}
vBoxL.getChildren().addAll(listViewTurnosL);
vBoxM.getChildren().addAll(listViewTurnosM);
listas.getChildren().addAll(vBoxL, vBoxM);
scrollPane.setContent(listas);
Scene escena = new Scene(scrollPane, 800, 800);
escenario.setScene(escena);
escenario.show();
}
}
Consider how you are creating your lists:
listViewTurnosL.setItems(FXCollections.observableList(listaHorarios));
listViewTurnosM.setItems(FXCollections.observableList(listaHorarios));
The documentation for the factory method you are using reads:
Constructs an ObservableList that is backed by the specified list.
That is - the base list provided is kept as the backing (storing) list. Since both ObservableList instances share the same original ArrayList, it is no wonder they share the content.
You may want to use the factory method FXCollections.ObservableArrayList which creates a new ObservableList (the backing list is created internally, or is the list itself).
If you really need the non-observable list instances, you should probably use two different ones if the lists are not to be equal.
If I look at your code I see the following things:
listViewTurnosL.setItems(FXCollections.observableList(listaHorarios));
listViewTurnosM.setItems(FXCollections.observableList(listaHorarios));
This means to that both listViewTurnosX contain exactly the same element references.
case 0: {
for (Turno t1 : listViewTurnosL.getItems()) {
t1.setFecha(lunesDate.plusDays(i));
}
// Value of t1.getFecha() is 1
break;
}
case 1: {
for (Turno t2 : listViewTurnosM.getItems()) {
t2.setFecha(lunesDate.plusDays(i));
}
It doesn't matter which list is iterated, both contain the same elements, so in both cases the same properties get updated.
I tried to create a vaadin7 application with the use of Navigator.
I have my own ViewProvider implementation.
here is the relevant part of the UI class:
#Override
protected void init(VaadinRequest request) {
layout = new VerticalLayout();
layout.setSizeFull();
setContent(layout);
navigator = new Navigator(this, layout);
mainView = new MainView(navigator);
navigator.addView("", mainView);
ViewProviderImpl viewProviderImpl = new ViewProviderImpl(mainView);
navigator.addProvider(viewProviderImpl);
}
here is MainView:(this is the one that should be displayed by default. Currently it contains two buttons only. Should one hit the buttons, the navigator should take him to one of the other Views)
public MainView(Navigator navigator) {
this.setSizeFull();
this.addComponent(new Label("This is the main view 1"));
int i = 1;
createSubViewButtons(i++ , Constants.DASHBOARD, new DashboardView());
createSubViewButtons(i++ , Constants.SCHEDULE, new ScheduleView());
}
private void createSubViewButtons(int exNum, String caption, View view) {
navigator.addView(caption, view);
Button button = new Button(caption, new ClickListener() {
private static final long serialVersionUID = 1L;
#Override
public void buttonClick(ClickEvent event) {
navigator.navigateTo(event.getButton().getData().toString());
}
});
button.setData(caption);
button.setStyleName(Reindeer.BUTTON_DEFAULT);
this.addComponent(button);
}
and I have a class that implements ViewProvider.
This basically should map URLs to views. The getViewName() methods removes the unnecessary parts of the url, and the getView() should return the View instance based on the return value of getViewName(). (Anyway, I have the strong feeling that the code execution never gets here, as the exception happens earlier)
public class ViewProviderImpl implements ViewProvider {
private static final long serialVersionUID = 1L;
private static Map<String, View> mapping;
static {
mapping = new HashMap<String, View>();
ScheduleView scheduleView = new ScheduleView();
DashboardView dashboardView = new DashboardView();
mapping.put("CORE/maintain/schedule", scheduleView);
mapping.put("CORE/maintain/dashboard", dashboardView);
mapping.put(Constants.DASHBOARD, dashboardView);
mapping.put(Constants.SCHEDULE, scheduleView);
}
public ViewProviderImpl(MainView mv) {
mapping.put("", mv);
}
#Override
public String getViewName(String viewAndParameters) {
// to do --if it is non empty than take it otherwise use Page
StringBuilder sb = new StringBuilder();
String fullURL = Page.getCurrent().getLocation().toString();
String fullURL = viewAndParameters;
String arr[] = fullURL.split(Constants.ARRANGER_WITH_SLASH);
if (arr.length > 1) {
String shortURL = arr[1];
if (shortURL.contains(Constants.QUESTION_MARK)) {
shortURL = shortURL.split("\\?")[0];
}
if (shortURL.contains(Constants.SLASH)) {
// always remove the two first and keep the rest of it.
String split[] = shortURL.split(Constants.SLASH);
for (int i = 0; i < split.length; i++ ) {
if (i <= 1) {
continue;
}
sb.append(split[i]);
if (i >= 2 && i != split.length - 1) {
sb.append(Constants.SLASH);
}
}
}
}
return sb.toString();
}
For me, it seems logical. In reality, however it throws NPE. Why?
Probably I abuse the way how navigation should be used in Vaadin7, but I can't figure out what should I do...
java.lang.NullPointerException
vaadinacrys.MainView.createSubViewButtons(MainView.java:39)
vaadinacrys.MainView.<init>(MainView.java:33)
vaadinacrys.PoolarrangerUI.init(PoolarrangerUI.java:36)
com.vaadin.ui.UI.doInit(UI.java:641)
com.vaadin.server.communication.UIInitHandler.getBrowserDetailsUI(UIInitHandler.java:222)
com.vaadin.server.communication.UIInitHandler.synchronizedHandleRequest(UIInitHandler.java:74)
com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41)
com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1402)
com.vaadin.server.VaadinServlet.service(VaadinServlet.java:295)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Following up my original comment, you have not posted your MainView class fully but in it's constructor you're not assigning the Navigator navigator variable to a field nor passing it as a parameter to the createSubViewButtons method so you can use it there. If you have indeed a field called navigator, by the time navigator.addView(caption, view); gets executed it will be null, hence you get a NPE. Quick fix, this.navigator = navigator in your constructor & enjoy.