英文:
Problem connecting TableColumn objects to custom class
问题
I've been trying to figure out why this scene keeps throwing an error when I try to add rows to it's TableView. I've been looking through the tutorial at https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm#sthref109, but I haven't been able to figure out what I'm doing wrong. I am sure I am able to query the data and put it into my ObservableList object, because I can print the values out afterwards.
It seems like there's something wrong with how I'm connecting the TableColumns to the custom CropCrow class I'm using.
How can I get the TableColumns to accept my custom class values?
The types of warnings I get:
WARNING: Can not retrieve property 'speciesName' in PropertyValueFactory: javafx.scene.control.cell.PropertyValueFactory@5e065707 with provided class type: class main.gui.CropsController$CropRow
Commenting out this line removes the warning, but I can't add rows without it.
this.cropTable.getItems().addAll(data);
My Code:
package main.gui;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import main.GardenersApp;
import main.database.Database;
import java.sql.ResultSet;
import java.sql.SQLException;
public final class CropsController extends SuperController {
private static final ObservableList<CropRow> data = FXCollections.observableArrayList();
@FXML
private TableView<CropRow> cropTable;
@FXML
private TableColumn<CropRow, String> speciesCol;
@FXML
private TableColumn<CropRow, String> varietyCol;
@FXML
private TableColumn<CropRow, String> typeCol;
@FXML
private TableColumn<CropRow, String> soilTempCol;
@FXML
private TableColumn<CropRow, String> weeklyWaterCol;
@FXML
private TableColumn<CropRow, String> sunlightCol;
@FXML
private TableColumn<CropRow, String> pHCol;
@FXML
private Button addCrop;
@FXML
private Button deleteCrop;
@FXML
private Button returnToMain;
/**
* Called immediately when this scene is shown.
* Retrieves relevant data from the database and displays it in the
*/
@FXML
private void initialize() throws SQLException {
Database.connect();
ResultSet cropTable = Database.runQuery(
"SELECT SpeciesName, VarietyName, CropTypeName, SoilTemperatureMin, SoilTemperatureMax, SunlightNeededMin, SunlightNeededMax, WaterNeededMin, WaterNeededMax, PHMin, PHMax \n" +
" FROM CropSpecies CROSS JOIN CropVariety CROSS JOIN CropType \n" +
" ON CropSpecies.SpeciesNumber = CropVariety.SpeciesNumber \n" +
" AND CropType.CropTypeNumber = CropVariety.CropTypeNumber \n" +
" GROUP BY SpeciesName, VarietyName, CropTypeName, SoilTemperatureMin, SoilTemperatureMax, SunlightNeededMin, SunlightNeededMax, WaterNeededMin, WaterNeededMax, PHMin, PHMax");
while (cropTable.next()) {
String speciesName = cropTable.getString("SpeciesName");
String varietyName = cropTable.getString("VarietyName");
String cropTypeName = cropTable.getString("CropTypeName");
String soilTemperatureMin = cropTable.getString("SoilTemperatureMin");
String soilTemperatureMax = cropTable.getString("SoilTemperatureMax");
String sunlightNeededMin = cropTable.getString("SunlightNeededMin");
String sunlightNeededMax = cropTable.getString("SunlightNeededMax");
String waterNeededMin = cropTable.getString("WaterNeededMin");
String waterNeededMax = cropTable.getString("WaterNeededMax");
String pHMin = cropTable.getString("PHMin");
String pHMax = cropTable.getString("PHMax");
String soilTemperatureRange = soilTemperatureMin+ " - " + soilTemperatureMax + " F";
String sunlightNeededRange = sunlightNeededMin + " - " + sunlightNeededMax + " Hours";
String waterNeededRange = waterNeededMin + " - " + waterNeededMax + " inches";
String pHRange = pHMin + " - " + pHMax;
CropRow cropRow = new CropRow(speciesName, varietyName, cropTypeName, soilTemperatureRange, sunlightNeededRange, waterNeededRange, pHRange);
data.add(cropRow);
}
this.speciesCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("speciesName"));
this.varietyCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("varietyName"));
this.typeCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("cropTypeName"));
this.soilTempCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("soilTemperature"));
this.weeklyWaterCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("waterNeeded"));
this.sunlightCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("sunlightNeeded"));
this.pHCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("pH"));
for(CropRow cr : data) {
System.out.println(cr.printCrop());
}
this.cropTable.setItems(data);
Database.disconnect();
}
@FXML
private void addCropAction() { }
@FXML
private void deleteCropAction() { }
/**
* Returns to the title screen when the returnToMain Button is clicked.
*/
@FXML
private void returnToMainAction() {
GardenersApp gApp = GetG_app();
gApp.switchScreen(Page.TITLE);
}
/**
* Holds data on a row in the cropTable TableView object.
*/
private static class CropRow {
private final SimpleStringProperty speciesName;
private final SimpleStringProperty varietyName;
private final SimpleStringProperty cropTypeName;
private final SimpleStringProperty soilTemperature;
private final SimpleStringProperty waterNeeded;
private final SimpleStringProperty sunlightNeeded;
private final SimpleStringProperty pH;
private CropRow(String species, String variety, String cropType, String soilTemp, String water, String sunlight, String pH) {
this.speciesName = new SimpleStringProperty(species);
this.varietyName = new SimpleStringProperty(variety);
this.cropTypeName = new SimpleStringProperty(cropType);
this.soilTemperature = new SimpleStringProperty(soilTemp);
this.waterNeeded = new SimpleStringProperty(water);
this.sunlightNeeded = new SimpleStringProperty(sunlight);
this.pH = new SimpleStringProperty(pH);
}
public String getSpeciesName() {
return speciesName.get();
}
public void setSpeciesName(String speciesName) {
this.speciesName.set(speciesName);
英文:
I've been trying to figure out why this scene keeps throwing an error when I try to add rows to it's TableView. I've been looking through the tutorial at https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm#sthref109, but I haven't been able to figure out what I'm doing wrong. I am sure I am able to query the data and put it into my ObservableList object, because I can print the values out afterwards.
It seems like there's something wrong with how I'm connecting the TableColumns to the custom CropCrow class I'm using.
How can I get the TableColumns to accept my custom class values?
The types of warnings I get:
WARNING: Can not retrieve property 'speciesName' in PropertyValueFactory: javafx.scene.control.cell.PropertyValueFactory@5e065707 with provided class type: class main.gui.CropsController$CropRow
Commenting out this line removes the warning, but I can't add rows without it.
this.cropTable.getItems().addAll(data);
My Code:
package main.gui;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import main.GardenersApp;
import main.database.Database;
import java.sql.ResultSet;
import java.sql.SQLException;
public final class CropsController extends SuperController {
private static final ObservableList<CropRow> data = FXCollections.observableArrayList();
@FXML
private TableView<CropRow> cropTable;
@FXML
private TableColumn<CropRow, String> speciesCol;
@FXML
private TableColumn<CropRow, String> varietyCol;
@FXML
private TableColumn<CropRow, String> typeCol;
@FXML
private TableColumn<CropRow, String> soilTempCol;
@FXML
private TableColumn<CropRow, String> weeklyWaterCol;
@FXML
private TableColumn<CropRow, String> sunlightCol;
@FXML
private TableColumn<CropRow, String> pHCol;
@FXML
private Button addCrop;
@FXML
private Button deleteCrop;
@FXML
private Button returnToMain;
/**
* Called immediately when this scene is shown.
* Retrieves relevant data from the database and displays it in the
*/
@FXML
private void initialize() throws SQLException {
Database.connect();
ResultSet cropTable = Database.runQuery(
"SELECT SpeciesName, VarietyName, CropTypeName, SoilTemperatureMin, SoilTemperatureMax, SunlightNeededMin, SunlightNeededMax, WaterNeededMin, WaterNeededMax, PHMin, PHMax \n" +
" FROM CropSpecies CROSS JOIN CropVariety CROSS JOIN CropType \n" +
" ON CropSpecies.SpeciesNumber = CropVariety.SpeciesNumber \n" +
" AND CropType.CropTypeNumber = CropVariety.CropTypeNumber \n" +
" GROUP BY SpeciesName, VarietyName, CropTypeName, SoilTemperatureMin, SoilTemperatureMax, SunlightNeededMin, SunlightNeededMax, WaterNeededMin, WaterNeededMax, PHMin, PHMax");
while (cropTable.next()) {
String speciesName = cropTable.getString("SpeciesName");
String varietyName = cropTable.getString("VarietyName");
String cropTypeName = cropTable.getString("CropTypeName");
String soilTemperatureMin = cropTable.getString("SoilTemperatureMin");
String soilTemperatureMax = cropTable.getString("SoilTemperatureMax");
String sunlightNeededMin = cropTable.getString("SunlightNeededMin");
String sunlightNeededMax = cropTable.getString("SunlightNeededMax");
String waterNeededMin = cropTable.getString("WaterNeededMin");
String waterNeededMax = cropTable.getString("WaterNeededMax");
String pHMin = cropTable.getString("PHMin");
String pHMax = cropTable.getString("PHMax");
String soilTemperatureRange = soilTemperatureMin+ " - " + soilTemperatureMax + " F";
String sunlightNeededRange = sunlightNeededMin + " - " + sunlightNeededMax + " Hours";
String waterNeededRange = waterNeededMin + " - " + waterNeededMax + " inches";
String pHRange = pHMin + " - " + pHMax;
CropRow cropRow = new CropRow(speciesName, varietyName, cropTypeName, soilTemperatureRange, sunlightNeededRange, waterNeededRange, pHRange);
data.add(cropRow);
}
this.speciesCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("speciesName"));
this.varietyCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("varietyName"));
this.typeCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("cropTypeName"));
this.soilTempCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("soilTemperature"));
this.weeklyWaterCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("waterNeeded"));
this.sunlightCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("sunlightNeeded"));
this.pHCol.setCellValueFactory(
new PropertyValueFactory<CropRow, String>("pH"));
for(CropRow cr : data) {
System.out.println(cr.printCrop());
}
this.cropTable.setItems(data);
Database.disconnect();
}
@FXML
private void addCropAction() { }
@FXML
private void deleteCropAction() { }
/**
* Returns to the title screen when the returnToMain Button is clicked.
*/
@FXML
private void returnToMainAction() {
GardenersApp gApp = GetG_app();
gApp.switchScreen(Page.TITLE);
}
/**
* Holds data on a row in the cropTable TableView object.
*/
private static class CropRow {
private final SimpleStringProperty speciesName;
private final SimpleStringProperty varietyName;
private final SimpleStringProperty cropTypeName;
private final SimpleStringProperty soilTemperature;
private final SimpleStringProperty waterNeeded;
private final SimpleStringProperty sunlightNeeded;
private final SimpleStringProperty pH;
private CropRow(String species, String variety, String cropType, String soilTemp, String water, String sunlight, String pH) {
this.speciesName = new SimpleStringProperty(species);
this.varietyName = new SimpleStringProperty(variety);
this.cropTypeName = new SimpleStringProperty(cropType);
this.soilTemperature = new SimpleStringProperty(soilTemp);
this.waterNeeded = new SimpleStringProperty(water);
this.sunlightNeeded = new SimpleStringProperty(sunlight);
this.pH = new SimpleStringProperty(pH);
}
public String getSpeciesName() {
return speciesName.get();
}
public void setSpeciesName(String speciesName) {
this.speciesName.set(speciesName);
}
public String getVarietyName() {
return varietyName.get();
}
public void setVarietyName(String varietyName) {
this.varietyName.set(varietyName);
}
public String getCropTypeName() {
return cropTypeName.get();
}
public void setCropTypeName(String cropTypeName) {
this.cropTypeName.set(cropTypeName);
}
public String getSoilTemperature() {
return soilTemperature.get();
}
public void setSoilTemperature(String soilTemperature) {
this.soilTemperature.set(soilTemperature);
}
public String getWaterNeeded() {
return waterNeeded.get();
}
public void setWaterNeeded(String waterNeeded) {
this.waterNeeded.set(waterNeeded);
}
public String getSunlightNeeded() {
return sunlightNeeded.get();
}
public void setSunlightNeeded(String sunlightNeeded) {
this.sunlightNeeded.set(sunlightNeeded);
}
public String getpH() {
return pH.get();
}
public void setpH(String pH) {
this.pH.set(pH);
}
public String printCrop() {
return this.speciesName.get() + " | " + this.cropTypeName.get() + " | " + this.varietyName.get();
}
}
}
The FXML File:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="720.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="main.gui.CropsController">
<children>
<HBox prefHeight="720.0" prefWidth="1280.0">
<children>
<VBox prefHeight="720.0" prefWidth="200.0" spacing="15.0">
<padding>
<Insets bottom="25.0" left="25.0" top="25.0" />
</padding>
<children>
<Button fx:id="addCrop" mnemonicParsing="false" onAction="#addCropAction" text="Add Crop" />
<Button fx:id="deleteCrop" layoutX="35.0" layoutY="35.0" mnemonicParsing="false" onAction="#deleteCropAction" text="Delete Crop" />
<Button fx:id="returnToMain" layoutX="35.0" layoutY="75.0" mnemonicParsing="false" onAction="#returnToMainAction" text="Return to Main" />
</children>
</VBox>
<TableView fx:id="cropTable" prefHeight="200.0" prefWidth="1080.0">
<columns>
<TableColumn fx:id="speciesCol" prefWidth="102.0" text="Species" />
<TableColumn fx:id="varietyCol" prefWidth="168.0" text="Variety" />
<TableColumn fx:id="typeCol" prefWidth="115.0" text="Type" />
<TableColumn fx:id="soilTempCol" prefWidth="119.0" text="Soil Temperature" />
<TableColumn fx:id="weeklyWaterCol" prefWidth="118.0" text="Weekly Water" />
<TableColumn fx:id="sunlightCol" prefWidth="98.0" text="Sunlight" />
<TableColumn fx:id="pHCol" minWidth="0.0" prefWidth="69.0" text="pH" />
</columns>
<HBox.margin>
<Insets />
</HBox.margin>
</TableView>
</children>
</HBox>
</children>
</AnchorPane>
答案1
得分: 1
从PropertyValueFactory文档中的示例:
使用此类的示例是:
TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name"); firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
在这个示例中,Person
是TableView
项列表的类类型。类Person
必须声明为public。
您的代码无法工作,因为您的模型类CropRow
声明为private
:如果要使用PropertyValueFactory
,您需要将其声明为public
。
请注意,PropertyValueFactory
是一个旧的类,仅为了避免在Java 8之前创建单元格值工厂时需要大量样板代码而引入的。它有一些缺点(最重要的是依赖将字符串映射到方法名称,这在编译时无法检查;但还有其他缺点,正如您已经发现的,需要完全访问模型类)。
随着Java 8及更高版本中编译时类型推断和Lambda表达式的改进,实际上不再需要PropertyValueFactory
。
根据上面链接的文档,将“属性访问器方法”添加到您的模型类(即使您仍然使用PropertyValueFactory
,这也是一个好主意):
private static class CropRow {
private final StringProperty speciesName;
private CropRow(String species /* etc... */) {
this.speciesName = new SimpleStringProperty(species);
// ...
}
public StringProperty speciesNameProperty() {
return speciesName;
}
public final String getSpeciesName() {
return speciesNameProperty().get();
}
public final void setSpeciesName(String speciesName) {
speciesNameProperty().set(speciesName);
}
// 类似地处理其他属性...
}
然后您只需要:
this.speciesCol.setCellValueFactory(cellData ->
cellData.getValue().speciesNameProperty());
英文:
From the documentation for PropertyValueFactory
::
> An example of how to use this class is:
>
> TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
> firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));
>
> In this example,
> Person
is the class type of the TableView
items list. The class Person
> must be declared public.
(my emphasis).
Your code fails to work because your model class CropRow
is declared private
: you need to make it public
if you want to use a PropertyValueFactory
.
Note that PropertyValueFactory
is something of a legacy class that was introduced solely to avoid a lot of boilerplate code that would have been necessary to create a cell value factory prior to Java 8. It has several disadvantages (most importantly, relying on mapping strings to method names, which can't be checked at compile time; but also, as you've discovered, needing full access to the model class).
With the advent of improved compile-time type inference and lambda expressions in Java 8 and later, PropertyValueFactory
is really not needed any more.
Add "property accessor methods" to your model class (according to the documentation linked above, this is a good idea even if you still use PropertyValueFactory
):
private static class CropRow {
private final StringProperty speciesName;
private CropRow(String species /* etc... */) {
this.speciesName = new SimpleStringProperty(species);
// ...
}
public StringProperty speciesNameProperty() {
return speciesName ;
}
public final String getSpeciesName() {
return speciesNameProperty.get();
}
public final void setSpeciesName(String speciesName) {
speciesNameProperty().set(speciesName);
}
// similarly for other properties...
}
and then you just need:
this.speciesCol.setCellValueFactory(cellData ->
cellData.getValue().speciesNameProperty());
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论