Programmatically resize TableColumns in JavaFX

2019-09-09 17:05发布

I am trying to make a TableView to programmatically resize its columns at startup in order to fit its content, to achieve this I found a method which works great when bound to a listener (for example #onKeyPressed).
But when i try to bind it to the showingProperty() of the primaryStage in order to call it without any user action it throws a NullPointerException on columnToFitMethod.invoke(tableView.getSkin(), column, -1).
In a previous post I exposed a similar problem, the difference was that I wasn't using a Parent layout (RootLayout) to wrap PersonTable. The solution that @DVarga provided me worked until I added RootLayout.

Here is the Exception:

Exception in Application start method
java.lang.reflect.InvocationTargetException
    ...
Caused by: java.lang.RuntimeException: Exception in Application start method
    ...
Caused by: java.lang.NullPointerException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at columnstofit.MainApp$GUIUtil.fitColumns(MainApp.java:135)
    at columnstofit.PersonTableController.setMainApp(PersonTableController.java:30)
    at columnstofit.MainApp.showPersonTable(MainApp.java:98)
    at columnstofit.MainApp.start(MainApp.java:60)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    ... 1 more
Exception running application columnstofit.MainApp

How can I manage to do the resize programatically mantaining the RootLaylout?
Here is my code:

package columnstofit;

import com.sun.javafx.scene.control.skin.TableViewSkin;
import java.awt.AWTException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class MainApp extends Application {

    public  static Stage                 primaryStage;
    private static AnchorPane            personTable;
    private static BorderPane            rootLayout;
                   PersonTableController controller;

    private ObservableList<Person> personData = FXCollections.observableArrayList();

    /**
     * Constructor
     */
    public MainApp() {
        // i am entering this name just to force the resizing of the column
        personData.add(new Person("Hansgggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg", "Muster"));
        personData.add(new Person("Ruth", "Mueller"));
        personData.add(new Person("Heinz", "Kurz"));
        personData.add(new Person("Cornelia", "Meier"));
        personData.add(new Person("Werner", "Meyer"));
        personData.add(new Person("Lydia", "Kunz"));
        personData.add(new Person("Anna", "Best"));
        personData.add(new Person("Stefan", "Meier"));
    }

    /**
     * Returns the data as an observable list of Persons. 
     * @return
     */
    public ObservableList<Person> getPersonData() {
        return personData;
    }

    @Override
    public void start(Stage primaryStage) throws AWTException  {

        MainApp.primaryStage = primaryStage;
        MainApp.primaryStage.setTitle("Names Table");

        initRootLayout();
        showPersonTable();
    }

    /**
     * Initializes the root layout.
     * @throws java.awt.AWTException
     */
    public void initRootLayout() throws AWTException {
        try 
        {
            // Load root layout from fxml file.
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(MainApp.class.getResource("RootLayout.fxml"));
            rootLayout = (BorderPane) loader.load();

            // Show the scene containing the root layout.
            Scene scene = new Scene(rootLayout);

            MainApp.primaryStage.setScene(scene);
            MainApp.primaryStage.show();
        } 
        catch (IOException e) { e.printStackTrace(); }
    }

    public void showPersonTable() throws AWTException {

        try 
        {
            // Load person overview.
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(MainApp.class.getResource("PersonTable.fxml"));
            personTable = (AnchorPane) loader.load();

            // Set person overview into the center of root layout.
            rootLayout.setCenter(personTable);

            // Give the controller access to the main app.
            controller = loader.getController();
            controller.setMainApp(this);
        } 
        catch (IOException e) { e.printStackTrace(); }
    }

    /**
     * Returns the main stage.
     * @return
     */
    public Stage getPrimaryStage() {
        return primaryStage;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }


    public static class GUIUtil {
        private static Method columnToFitMethod;

        static 
        {
            try 
            {
                columnToFitMethod = TableViewSkin.class.getDeclaredMethod("resizeColumnToFitContent", TableColumn.class, int.class);
                columnToFitMethod.setAccessible(true);
            } 
            catch (NoSuchMethodException e) {e.printStackTrace();}
        }

        public static void fitColumns(TableView tableView) {
            for (Object column : tableView.getColumns()) 
            {
                try { columnToFitMethod.invoke(tableView.getSkin(), column, -1); }
                catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }
            }
        }
    }

    public class Person {
        private final StringProperty firstName;
        private final StringProperty lastName;

        public Person() {
            this(null, null);
        }

        public Person(String firstName, String lastName) {
            this.firstName  = new SimpleStringProperty(firstName);
            this.lastName   = new SimpleStringProperty(lastName);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public void setFirstName(String firstName) {
            this.firstName.set(firstName);
        }

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public String getLastName() {
            return lastName.get();
        }

        public void setLastName(String lastName) {
            this.lastName.set(lastName);
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }
    }
}

PersonTable.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" stylesheets="@PersonTableStyle.css" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="columnstofit.PersonTableController">
    <TableView fx:id="tableView" layoutX="-39.0" layoutY="39.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <columns>
            <TableColumn fx:id="firstNameColumn" editable="false" minWidth="-1.0" prefWidth="-1.0" text="First Name" />
            <TableColumn fx:id="lastNameColumn" editable="false" minWidth="-1.0" prefWidth="-1.0" text="Last Name" />
        </columns>
        <columnResizePolicy>
            <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
        </columnResizePolicy>
    </TableView>
</AnchorPane>

PersonTableController:

package columnstofit;

import java.awt.AWTException;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;

    /**
    * FXML Controller class
    */
    public class PersonTableController {

        // Reference to the main application.
        private MainApp mainApp;

        @FXML
        private TableColumn<MainApp.Person, String> firstNameColumn;
        @FXML
        private TableColumn<MainApp.Person, String> lastNameColumn;
        @FXML
        private TableView                    tableView;

        public PersonTableController() throws AWTException {
        }

        public void setMainApp(MainApp mainApp) {
            this.mainApp = mainApp;
            tableView.setItems(mainApp.getPersonData());
            if(mainApp.primaryStage.isShowing())
                MainApp.GUIUtil.fitColumns(tableView);
            else {
                mainApp.primaryStage.showingProperty().addListener((obs, oldVal, newVal) -> {
                    if(newVal)
                        MainApp.GUIUtil.fitColumns(tableView);
                });
            }
        }

        @FXML
        private void initialize() throws AWTException {
            // Initialize the person table with the two columns.
            firstNameColumn.setCellValueFactory(
                    cellData -> cellData.getValue().firstNameProperty());
            lastNameColumn.setCellValueFactory(
                    cellData -> cellData.getValue().lastNameProperty());
        }
    }

And the RootLaylout.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.BorderPane?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" />

0条回答
登录 后发表回答