5

I wrote a custom TableColumn width resize policy. You can see its code under Java 8 / JavaFX 8 here on github. As you can see, in the method distributeSpaceRatio() and other places, it depends on TableColumn.impl_setWidth() to set the width of the columns to the desired value after doing our calculations.

I am migrating my project to Java 11 / JavaFX 11 and the method impl_setWidth() has been removed from public view.

Is there another method to tell a table column to set its width? I'm open to any solution as long as it is reliable and sets the with to (or close to) the requested value.

Here are two ideas I've tried so far that didn't work:


Attempt 1:

This one gives NoMethodFoundException:

private void setColumnWidth ( TableColumn column, double targetWidth ) {

    try {
        Method method = column.getClass().getDeclaredMethod( "setWidth" );
        method.setAccessible( true );
        Object r = method.invoke( targetWidth );
    } catch ( Exception e ) {
        e.printStackTrace();
    }
}

Attempt 2:

This one has no effect:

private void setColumnWidth ( TableColumn column, double targetWidth ) {

    double oldMax = column.getMaxWidth();
    double oldMin = column.getMinWidth();
    double oldPref = column.getPrefWidth();

    column.setMaxWidth( targetWidth );
    column.setMinWidth( targetWidth );
    column.setPrefWidth( targetWidth );

    column.getTableView().refresh();

    column.setPrefWidth( oldPref );
    column.setMinWidth( oldMin );
    column.setMaxWidth( oldMax );
}

Attempt 3:

Looking at the source for JavaFX's TableView.UNCONSTRAINED_RESIZE_POLICY, it seems the method invoked is ultimately TableColumnBase.doSetWidth(), however that also doesn't work for me here:

private void setColumnWidth ( TableColumn column, double targetWidth ) {

    try {
        Method method = column.getClass().getDeclaredMethod( "doSetWidth" );
        method.setAccessible( true );
        Object r = method.invoke( targetWidth );
    } catch ( Exception e ) {
        e.printStackTrace();
    }
}

Attempt 4:

I also tried using the solution provided in this answer to execute a private method, and got the same exception -- "There are no methods found with name doSetWidth and params [class java.lang.Double]".


Attempt 5:

I thought this one was going to work, but no such luck. It threw a "java.lang.NoSuchFieldException: width". However, when I go to the TableColumnBase source code, I clearly see on line 412 a private field called width. Not sure why this fails.

private void setColumnWidth ( TableColumnBase column, double targetWidth ) {
    try {
        Field widthField = column.getClass().getDeclaredField( "width" );
        widthField.setAccessible( true );
        ReadOnlyDoubleWrapper width = (ReadOnlyDoubleWrapper)widthField.get( column );
        width.set( targetWidth );
    } catch ( Exception e ) {
        e.printStackTrace();
    }
}

Attempt 6:

This got us closer, but still failed:

private void setColumnWidth ( TableColumnBase column, double targetWidth ) {
    try {
        Field widthField = column.getClass().getSuperclass().getDeclaredField( "width" );
        widthField.setAccessible( true );
        ReadOnlyDoubleWrapper width = (ReadOnlyDoubleWrapper)widthField.get( column );
        width.set( targetWidth );
    } catch ( Exception e ) {
        e.printStackTrace();
    }
}

java.lang.reflect.InaccessibleObjectException: Unable to make field private javafx.beans.property.ReadOnlyDoubleWrapper javafx.scene.control.TableColumnBase.width accessible: module javafx.controls does not "opens javafx.scene.control" to unnamed module @649dcffc


Attempt 7:

private void setColumnWidth ( TableColumn column, double targetWidth ) {
    try {
        Method method = column.getClass().getSuperclass().getDeclaredMethod( "setWidth", double.class );
        method.setAccessible( true );
        method.invoke ( targetWidth );
        //ReadOnlyDoubleWrapper width = (ReadOnlyDoubleWrapper)widthField.get( column );
        //width.set( targetWidth );
    } catch ( Exception e ) {
        e.printStackTrace();
    }
}

java.lang.reflect.InaccessibleObjectException: Unable to make void javafx.scene.control.TableColumnBase.setWidth(double) accessible: module javafx.controls does not "opens javafx.scene.control" to unnamed module @5063f647

Grumblesaurus
  • 3,021
  • 3
  • 31
  • 61
  • 1
    I don't have the OpenJFX 11 source codes, but I think attempt 5 should be using `TableColumnBase.class.getDeclaredField("width")`. Runtime class of the `column` instance is probably `TableColumn`, which isn't going to match. – Jai Nov 12 '18 at 04:31
  • Yea, Attempt 6 and 7 figure that out. But I run into that nasty "InaccessibleObjectException". – Grumblesaurus Nov 12 '18 at 04:33
  • 1
    Attempts at reflection aren't going to work out of the box because of the module system. Since your code is, according to the error, in "unnamed module" you'll need to use `--add-opens javafx.controls/javafx.scene.control=ALL-UNNAMED` in the command line. – Slaw Nov 12 '18 at 04:34
  • That stopped the exceptions from firing, but the columns do not change size like they did when I called impl_setWidth(). – Grumblesaurus Nov 12 '18 at 04:40
  • 1
    Call `TableColumnBase.doSetWidth(double)` reflectively instead. That is supposed to be the direct replacement of `impl_setWidth(double)`, except that it's now package-private. – Jai Nov 12 '18 at 04:57
  • Yea, tried that, same problem -- the columns don't actually change size. At least not in the same way that impl_setWidth() made them. I'm going to try to strip down to a more simple test framework to make sure. – Grumblesaurus Nov 12 '18 at 05:04
  • Ok, it seems to work in a more simple setting. I'll have to mess with my ResizePolicy code to figure out why it's not working there. Thanks! – Grumblesaurus Nov 12 '18 at 05:09
  • Actually, it seems to be a perfect replacement. I had a typo that was causing the problem in my first test. Thanks @Jai! – Grumblesaurus Nov 12 '18 at 05:15

1 Answers1

3

Based on testing and the help of others, I found this solution:

private void setColumnWidth ( TableColumn column, double targetWidth ) {
    try {
        Method method = TableColumnBase.class.getDeclaredMethod( "doSetWidth", double.class );
        method.setAccessible( true );
        method.invoke( column, targetWidth );
    } catch ( NoSuchMethodException | InvocationTargetException | IllegalAccessException e ) {
        e.printStackTrace();
    }
}

In addition, the JVM needs to be invoked with the following arguments:

--add-opens javafx.controls/javafx.scene.control=ALL-UNNAMED
Grumblesaurus
  • 3,021
  • 3
  • 31
  • 61
  • 1
    By the way, if you ever put your code in a module replace `ALL-UNNAMED` with your module name. – Slaw Nov 12 '18 at 05:23
  • 1
    Just want to point out that it is safer to use `TableColumnBase.class`, since you'd never know when someone extends from `TableColumn`. – Jai Nov 12 '18 at 05:23
  • 2
    Also note that `com.sun.javafx.scene.control.TableColumnBaseHelper.setWidth(TableColumnBase, double)` might work without using reflections, though you still have to use private API. Update: It looks like it was designed for this purpose. Found this by accident though. – Jai Nov 12 '18 at 05:27
  • How would I use that private API without reflection? – Grumblesaurus Nov 12 '18 at 05:32
  • 2
    Well, stuff in `com.sun....` are considered private API. It should generate a warning which you would need to suppress at compile-time. I'm still using Java8/JavaFX8 so I'm not sure if this is still the case for OpenJFX11. – Jai Nov 12 '18 at 05:33