Reputation: 2503
My goal is to simply edit the cell of a treeview (to do so, I've read the doc of JavaFX).
My problem is that my treeview doesn't contains strings
, but my own type of data, that is call : Database
. I got the error : Database$Variable cannot be cast to core.Database
.
Is it possible to implement a treeview with my own type ?
Here's the code that I use, with the help of James_D :
public class DatabaseStructureView extends TreeView<Object> {
private Database db;
@FXML
private TreeView<Database> databaseStructureView;
public DatabaseStructureView() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("DatabaseStructureView.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
databaseStructureView.setEditable(true);
databaseStructureView.setCellFactory(new Callback<TreeView<Database>,TreeCell<Database>>(){
@Override
public TreeCell<Database> call(TreeView<Database> param) {
return new TextFieldTreeCellImpl();
}
});
}
private final class TextFieldTreeCellImpl extends TreeCell<Database> {
private TextField textField;
public TextFieldTreeCellImpl() {
}
@Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
setText(null);
setGraphic(textField);
textField.selectAll();
}
@Override
public void cancelEdit() {
super.cancelEdit();
setText(getString());
setGraphic(getTreeItem().getGraphic());
}
@Override
public void updateItem(Database item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(getTreeItem().getGraphic());
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(getDatabase(textField.getText()));
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
private Database getDatabase(String text) {
return new Database(text);
}
private String getString(){
return getItem() == null ? "" : getItem().toString();
}
}
public void setDatabase(Database db) {
this.db = db;
TreeItem<Object> dbRoot = new TreeItem<>(db);
dbRoot.setExpanded(true);
for (Variable var : db.getVariables()) {
dbRoot.getChildren().add(new TreeItem<Object>(var));
}
setRoot(dbRoot);
}
}
Here's my DataBase class :
public class Database {
public static class Variable {
public static enum VariableType {
NUMERIC, INTEGER, LOGIC, ORDONEE, NOMINAL, DATE
}
private String name;
private VariableType type;
private List<String> values;
public Variable(String name) {
this.name = name;
values = new ArrayList<>();
}
private void addValue(String value) {
values.add(value);
}
public String getValue(int index) {
return values.get(index);
}
public void addValue(int index, String value) {
values.add(index, value);
}
public VariableType getType() {
return type;
}
public String getName() {
return name;
}
public List<String> getValues() {
return values;
}
public String calculerMinimum (){
if(this.getType().equals(VariableType.INTEGER)){
// Je convertit en entier les strings
List<String> maColonneString = this.getValues();
List<Integer> maColonneInt = new ArrayList<Integer>();
for(int i = 0; i < maColonneString.size(); i++){
if(!maColonneString.get(i).equals("NULL")){
maColonneInt.set(i, Integer.parseInt(maColonneString.get(i)));
}
}
return(Integer.toString(Collections.min(maColonneInt)));
}
else {
return null;
}
}
public String calculerMaximum (){
if(this.getType().equals(VariableType.INTEGER)){
// Je convertit en entier les strings
List<String> maColonneString = this.getValues();
List<Integer> maColonneInt = new ArrayList<Integer>();
for(int i = 0; i < maColonneString.size(); i++){
if(!maColonneString.get(i).equals("NULL")){
maColonneInt.set(i, Integer.parseInt(maColonneString.get(i)));
}
}
return(Integer.toString(Collections.max(maColonneInt)));
}
else {
return null;
}
}
public String calculerFrequence (String maValeurTemoin){
List<String> maColonneString = this.getValues();
int compteur = 0;
for(int i = 0; i < maColonneString.size(); i++){
if(maColonneString.get(i).equals(maValeurTemoin)){
compteur ++;
}
}
return Integer.toString(compteur);
}
public String calculerMoyenne (){
if(this.getType().equals(VariableType.INTEGER)){
List<String> maColonneString = this.getValues();
int somme = 0;
int valeurNull=0;
for(int i = 0; i < maColonneString.size(); i++){
if(!maColonneString.get(i).equals("NULL")){
somme += Integer.parseInt(maColonneString.get(i));
}
else{
valeurNull ++;
}
}
return (Integer.toString(somme/(maColonneString.size()-valeurNull)));
}
else {
return null;
}
}
public String calculerVariance (){
if(this.getType().equals(VariableType.INTEGER)){
List<String> maColonneString = this.getValues();
int sommeMul=0;
int variance=0;
int valeurNull=0;
for(int i = 0; i < maColonneString.size(); i++){
if(!maColonneString.get(i).equals("NULL")){
sommeMul += Integer.parseInt(maColonneString.get(i))*Integer.parseInt(maColonneString.get(i));
}
else{
valeurNull ++;
}
}
variance = sommeMul/(maColonneString.size()-valeurNull)-Integer.parseInt(calculerMoyenne())*Integer.parseInt(calculerMoyenne());
return Integer.toString(variance);
}
else {
return null;
}
}
public String calculerEcartType (){
if(this.getType().equals(VariableType.INTEGER)){
return (Double.toString( Math.sqrt(Integer.parseInt(calculerVariance()))));
}
else {
return null;
}
}
public List<String> calculerAnalyseUnivarieRestreinte (){
if(this.getType().equals(VariableType.INTEGER)){
List<String> analyseUniList = new ArrayList<String>();
analyseUniList.add(calculerMinimum());
analyseUniList.add(calculerMaximum());
analyseUniList.add(calculerMoyenne());
return analyseUniList;
}
else{
return null;
}
}
public List<String> calculerAnalyseUnivarieComplete (){
if(this.getType().equals(VariableType.INTEGER)){
List<String> analyseUniList = new ArrayList<String>();
analyseUniList.add(calculerMinimum());
analyseUniList.add(calculerMaximum());
analyseUniList.add(calculerMoyenne());
analyseUniList.add(calculerVariance());
analyseUniList.add(calculerEcartType());
return analyseUniList;
}
else{
return null;
}
}
public boolean rechercherValeur (String maValeurTemoin){
List<String> maColonneString = this.getValues();
return(maColonneString.contains(maValeurTemoin));
}
public void setTypeVariable (VariableType monNouveauType){
this.type = monNouveauType;
}
public void setNomVariable (String monNouveauNom){
this.name = monNouveauNom;
}
public void modifierValeur (String ancienneValeur, String nouvelValeur){
List<String> maColonneString = this.getValues();
for(int i = 0; i < maColonneString.size(); i++){
if(maColonneString.get(i).equals(ancienneValeur)){
this.values.set(i, nouvelValeur);
}
}
}
@Override
public String toString() {
return name;
}
}
private List<Variable> variables;
private String name;
public Database(String name) {
this.name = name;
variables = new ArrayList<>();
}
public void addVariable(String name) {
variables.add(new Variable(name));
}
public String print() {
StringBuilder sb = new StringBuilder();
int i = 0;
while (i != variables.get(0).values.size()) {
for (Variable variable : variables) {
sb.append(variable.values.get(i)).append(" ");
}
sb.append(System.getProperty("line.separator"));
i++;
}
return sb.toString();
}
@Override
public String toString() {
return name;
}
public Variable getVariable(Variable maVariable) {
for (Variable variable : variables) {
if (variable.getName().equals(maVariable.getName())) {
return variable;
}
}
return null;
}
public List<Variable> getVariables() {
return new ArrayList<>(variables);
}
public Variable getVariable(int index) {
return variables.get(index);
}
public boolean addVariable(Variable variableAdd) {
return (this.variables.add(variableAdd));
}
public boolean removeVariable(Variable variableRemove) {
return (this.variables.remove(variableRemove));
}
}
And here's the errors that I get :
java.lang.ClassCastException: core.Database$Variable cannot be cast to core.Database
at components.databaseStructureView.DatabaseStructureView$TextFieldTreeCellImpl.updateItem(DatabaseStructureView.java:53)
at javafx.scene.control.TreeCell.updateItem(TreeCell.java:515)
at javafx.scene.control.TreeCell.indexChanged(TreeCell.java:473)
at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116)
Upvotes: 0
Views: 396
Reputation: 209319
The code you posted has a strange structure: you have two TreeView
references, the instance of the TreeView<Object>
subclass (DatabaseStructureView
), which is set as the dynamic root of the FXML, and then the TreeView<Database>
reference you declare: databaseStructureView
.
From what you describe, I can only assume (since you didn't create an MCVE) you have assigned both of these references to the same object, by setting the fx:id
attribute on the root, something as follows:
<fx:root type="TreeView" fx:id="databaseStructureView" ... >
So what happens here is that both this
(in the context of DatabaseStructureView
) and databaseStructureView
refer to the same object.
This is a highly confusing setup, and you should not do this. (Specifically, don't set an fx:id
on a <fx:root ... >
when the root is also the controller.)
What's happening is approximately the following:
Somewhere you do something like
TreeView<Object> treeView = new DatabaseStructureView<>();
This causes the fxml loader to load. Because you do setRoot(this)
, it effectively does
Object root = treeView ;
(the type is Object
because FXML is effectively untyped). Then because you have setController(this)
the FXMLLoader
's controller is also set to treeView
. And then when it parses the FXML, it sees the fx:id
and also sets databaseStructureView
to treeView
. So you actually have treeView.databaseStructureView == treeView
. You can verify this by adding
System.out.println(this == databaseStructureView);
after the FXMLLoader
's load()
method has successfully completed.
Because of type erasure, and because all the assignments from the FXMLLoader
are made by reflection, the generic types are invisible, so the FXML root, which has a compile time type of TreeView<Object>
and databaseStructureView
, which has a compile time type of TreeView<Database>
, both have runtime types of a raw TreeView
. So these reflective assignments are all successful.
But what has happened is that by using reflection in the FXML loader, you now have made a non-typesafe assignment, so all bets on the generic types are off. Because databaseStructureView
has a compile-time type of TreeView<Database>
, the compiler thinks it is safe to assume that all tree items in that tree must be TreeItem<Database>
. So your updateItem(...)
method takes a Database
as a parameter. However, since the current object is a TreeView<Object>
, the compiler will allow you to add any TreeView<Object>
to the tree structure. But since they refer to the same thing, when you do
dbRoot.getChildren().add(new TreeItem<Object>(var));
you are adding a TreeView<Object>
to the tree structure of databaseStructureView
. When the tree view later retrieves the wrapped object (really a DatabaseStructure.Variable
) and passes it to the updateItem(...)
method, you get a class cast exception because it is the wrong type.
This is obviously quite confusing: the confusion though comes from having the unnecessary reference databaseStructure
which is really identical to the current object. If you remove that reference entirely, and the fx:id
attribute, then you can just replace any references to databaseStructureView
with this
. What will then happen is that the type errors you have will be detected at compile time, not at run time (it won't let you set a Callback<TableView<Database>, TableCell<DataBase>>
as the cell factory, for example, as it now (correctly) expects a Callback<TableView<Object>, TableCell<Object>>
).
The underlying problem that is preventing you from doing what you want is that all items in a TreeView
have to be the same type. You are trying to mix types: some TreeItem
s contain Database
objects, others contain Database.Variable
objects. You need to either design your object model so that everything has a common type, or write the cell implementation to be a TreeCell<Object>
and somehow figure out the correct objects to create when you commit an edit.
Upvotes: 1