0

I wanted to program a TableBrowser for a MYSQl Database in JavaFX.

My first problem is: i dont know which types i get back from the Database.

So i decided to wrap those types with a Wrapper-class.

To show these values on the GUI, i used the TableColumns setCellValueFactory-method, which needs a value, that implements ObservableValue.

So i tried to implement the ObservableValue-interface.

But when i run the program it doesnt show the right Values.

TableBrowser after connecting to the Database

Has anyone an idea where i did wrong or knows a more recommended way to implement it ?

Here is the Part of the Code from the TableBrowser

/*
 * this variable is used to iterate over the tableview's columns.
 * It is a class variable, because it is not possible (for some reasons)
 * to use a local variable while working with it in the context of Lambda-expressions
 */
int t = 0;  

// those two variables are defined in the class Body
private final TableView<Entry> tableview = new TableView<>();  
private final ObservableList<Entry> columndata = FXCollections.observableArrayList();    

// the following Code is inside the Button's Actionlistener

for(int i = 1; i <= maxcol; i++) // adds a new TableColum for every colum in the DB
{
   tableview.getColumns().add(new TableColumn<Entry, String>rsmd.getColumnName(i)));
}

// iterates over the ResultSet
while(rs.next())  
{
    // this is the dataset i put in my TableView
    Entry row = new Entry(maxcol); 

    // for each Column i add the columnvalue to the current dataset
    for(int i = 1; i <= maxcol; i++) 
    {
       int type = rsmd.getColumnType(i);
       Object value = rs.getObject(i);
       row.setCellValue(i-1, type, value);
    }       
    // adds a new dataset to the ObservableList<Entry>
    columndata.add(row);
}
// puts all datasets in the TableView
tableview.setItems(columndata);

 // iterates over all Columns
for(t = 0; t < tableview.getColumns().size(); t++)
{
    // should set the CellValueFactory for each Column so it shows the data

    /*
     * I apologise if there a horrible mistake.
     * I never worked with Lamda before and just copied it form an example page :)
     */
    tableview.getColumns().get(t).setCellValueFactory(celldata -> celldata.getValue().getCellValue(t-1));
}

This is my Entry class, which is an inner Class in TableBrowserclass

/* 
 * should represent a Dataset. 
 * Has an array, which holdes every columnvalue as a WrapperType
 */
private class Entry
{
    WrapperType<?>[] columns;

    private Entry(int columncount) 
    {
        columns = new WrapperType[columncount];
    }

    private WrapperType<?> getCellValue(int col)
    {
        return columns[col];
    }

    private void setCellValue(int col, int type, Object value)
    {
        columns[col] = MySQLTypeWrapper.getInstance().wrapType(type, value);
    }
}

Here is the MySQLTypeWrapper class, which holds the WrapperType as an inner class

public class MySQLTypeWrapper 
{
    public WrapperType<?> wrapType(int type, Object Value)
    {
       Class<?> typeclass = toClass(type);
       return new WrapperType<>(typeclass.cast(Value));
    }

   /*
    *  returns the appropriate class def for every database type
    *  Expl: VARCHAR returns String.class
    */
    private static Class<?> toClass(int type) {...}

    /*
     * I copied the content of the of the overridden Methods from StringPropertyBase
     * as i have clue how to implement ObservableValue
     */
    class WrapperType<T> implements ObservableValue<WrapperType<T>>
    {
        private T value;
        private ExpressionHelper<WrapperType<T>> helper = null;

        private WrapperType(T value)
        {
            this.value = value;
        }

        @Override
        public void addListener(InvalidationListener listener) 
        {
           helper = ExpressionHelper.addListener(helper, this, listener);
        }

        @Override
        public void removeListener(InvalidationListener listener) 
        {
           helper = ExpressionHelper.removeListener(helper, listener);
        }

        @Override
        public void addListener(ChangeListener<? super WrapperType<T>> listener) 
        {
            helper = ExpressionHelper.addListener(helper, this, listener);
        }

        @Override
        public void removeListener(ChangeListener<? super WrapperType<T>> listener) 
        {
           helper = ExpressionHelper.removeListener(helper, listener);
        }

        @Override
        public WrapperType<T> getValue() 
        {
           return this;
        }

        public String toString()
        {
           return value.toString();
        }
    }
}

Thanks for your help in advance :)

  • Adding items to the table is done either by `table.getItems().addAll(...)` or by `table.setItems(...)` if you already have an `ObservableList`. The `setUserData` is used to store an object used by you, for later retrieval, and the `TableView` never actually uses it. – Itai Dec 25 '15 at 16:19
  • Thanks for the answer. I changed this in my code and now the table shows something. But I think there is still something wrong with this part: `for(t = 0; t < tableview.getColumns().size(); t++) { tableview.getColumns().get(t).setCellValueFactory(celldata -> celldata.getValue().getCellValue(t)); }` Because now the table looks like this: http://fs5.directupload.net/images/151225/ndxdx3xh.jpg – aubing0815 Dec 25 '15 at 17:36
  • Now I believe your problem is in the lambda function. See this - http://stackoverflow.com/questions/29029849/why-do-java-8-lambdas-allow-access-to-non-final-class-variables Basically, what is happening is the lambda gets a reference to the instance variable `t`, but at the time of the lambda execution this value is the maximum the loop came to. You can overcome this with a helper generator method. – Itai Dec 25 '15 at 17:59

1 Answers1

0

As mentioned in the comments, your first problem was not using the TableView's Items property.

For the second part - one solution would be to create a helper method along the lines of

private <T> Callback<TableColumn.CellDataFeatures<Entry,T>,ObservableValue<T>> createCellFactory(int columnIndex) {
    return celldata -> celldata.getValue().getCellValue(columnIndex);
}

and then change the loop to

// Now t can be a local variable, as it is not directly passed to the lambda.
for(int t = 0; t < tableview.getColumns().size(); t++)
{
    // should set the CellValueFactory for each Column so it shows the data

    tableview.getColumns().get(t).setCellValueFactory(createCellFactory(t));
}

Note that this time the variable passed to the lambda is a local effectively-final variable and not an instance variable, so the lambda is created with the correct value every time.

One last word of advice - are you sure you need this amount of generality? What I mean is - it is usually better to create a class to directly represent your DB structure with proper getters and setters, then you can use PropertyValueFactory.

Itai
  • 6,641
  • 6
  • 27
  • 51
  • Thank you so much ! I just had to add a cast `return celldata -> (ObservableValue) (celldata.getValue().getCellValue(columnIndex));` Because otherwise it says: _Type mismatch: cannot convert from ObservableValue to ObservableValue_ But now everything is working. And i guess i have to inform myself a bit about Lamda :) – aubing0815 Dec 25 '15 at 20:04