Столбец javafx в таблице размеров

afaik TableView в javafx имеет 2 политики изменения размера столбцов: CONSTRAINED_RESIZE_POLICY и UNCONSTRAINED_RESIZE_POLICY, но я хочу, чтобы размер столбцов был изменен в соответствии с содержимым их ячеек. Я думаю, что это простая проблема в другой платформе (например, представление данных в C#), но не может решить

16 ответов

Решение

Через 3 года я снова возвращаюсь к этой проблеме, некоторые предложения подсчитывают размер текста данных в каждой ячейке (это сложно в зависимости от размера шрифта, семейства шрифтов, отступов...)

Но я понимаю, что когда я нажимаю на разделитель в заголовке таблицы, он изменяется по размеру, как я хочу. Итак, я копаюсь в исходном коде JavaFX. Наконец, я нашел метод resizeColumnToFitContent в TableViewSkin, но это защищенный метод, который мы можем решить с помощью отражения:

import com.sun.javafx.scene.control.skin.TableViewSkin;
import javafx.scene.control.Skin;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class GUIUtils {
    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 autoFitTable(TableView tableView) {
        tableView.getItems().addListener(new ListChangeListener<Object>() {
            @Override
            public void onChanged(Change<?> c) {
                for (Object column : tableView.getColumns()) {
                    try {
                        columnToFitMethod.invoke(tableView.getSkin(), column, -1);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}

Обратите внимание, что мы вызываем "tableView.getItems ()", поэтому мы должны вызывать эту функцию после setItems()

После тестирования предыдущих решений я наконец нашел то, что мне помогло. Итак, вот мое (вызов метода после вставки данных в таблицу):

public static void autoResizeColumns( TableView<?> table )
{
    //Set the right policy
    table.setColumnResizePolicy( TableView.UNCONSTRAINED_RESIZE_POLICY);
    table.getColumns().stream().forEach( (column) ->
    {
        //Minimal width = columnheader
        Text t = new Text( column.getText() );
        double max = t.getLayoutBounds().getWidth();
        for ( int i = 0; i < table.getItems().size(); i++ )
        {
            //cell must not be empty
            if ( column.getCellData( i ) != null )
            {
                t = new Text( column.getCellData( i ).toString() );
                double calcwidth = t.getLayoutBounds().getWidth();
                //remember new max-width
                if ( calcwidth > max )
                {
                    max = calcwidth;
                }
            }
        }
        //set the new max-widht with some extra space
        column.setPrefWidth( max + 10.0d );
    } );
}

Я думаю, что просто переопределив функцию обратного вызова, которая возвращает true, решит вашу проблему, он отключит изменение размера столбцов, и все столбцы будут изменены в соответствии с содержимым их ячеек.

Пример:

TableView<String[]> table = new TableView<>();
table.setColumnResizePolicy(new Callback<TableView.ResizeFeatures, Boolean>() {
  @Override
  public Boolean call(ResizeFeatures p) {
     return true;
  }
});

Если вы хотите, чтобы только один столбец заполнил оставшуюся ширину таблицы, я нашел довольно прямолинейное решение, которое является коротким и не требует решения хакерского отражения, описанного выше:

DoubleBinding usedWidth = columnA.widthProperty().add(columnB.widthProperty()).add(columnC.widthProperty());

fillingColumn.prefWidthProperty().bind(tableView.widthProperty().subtract(usedWidth));

Или, чтобы сделать это коротким:

// automatically adjust width of columns depending on their content
configAttributeTreeTable.setColumnResizePolicy((param) -> true );

Я использовал другие решения по этому вопросу, и он работает довольно хорошо. Однако недостатком этого является то, когда ширина TableView больше, чем требуемая ширина TableColumns вместе. Я создал хак для решения этой проблемы, и он работает нормально:

orderOverview.setColumnResizePolicy((param) -> true );
Platform.runLater(() -> FXUtils.customResize(orderOverview));

где FXUtils.customResize() создается следующим образом:

public static void customResize(TableView<?> view) {

    AtomicDouble width = new AtomicDouble();
    view.getColumns().forEach(col -> {
        width.addAndGet(col.getWidth());
    });
    double tableWidth = view.getWidth();

    if (tableWidth > width.get()) {
        TableColumn<?, ?> col = view.getColumns().get(view.getColumns().size()-1);
        col.setPrefWidth(col.getWidth()+(tableWidth-width.get()));
    }

}

Я надеюсь, что это может быть полезно и для других людей!

Вот как я нашел:

      tableview.setColumnResizePolicy( TableView.CONSTRAINED_RESIZE_POLICY );
idCol.setMaxWidth( 1f * Integer.MAX_VALUE * 50 ); // 50% width
nameCol.setMaxWidth( 1f * Integer.MAX_VALUE * 30 ); // 30% width
ageCol.setMaxWidth( 1f * Integer.MAX_VALUE * 20 ); // 20% width

Ответ @HarleyDavidson в котлине

      val String.fxWidth: Double
    get() = Text(this).layoutBounds.width

//  call the method after inserting the data into table
fun <T> TableView<T>.autoResizeColumns() {
    columnResizePolicy = TableView.UNCONSTRAINED_RESIZE_POLICY
    columns.forEach { column ->
        column.setPrefWidth(
            (((0 until items.size).mapNotNull {
                column.getCellData(it)
            }.map {
                it.toString().fxWidth
            }.toMutableList() + listOf(
                column.text.fxWidth
            )).maxOrNull() ?: 0.0) + 10.0
        )
    }
}

Это установит минимальную ширину столбцов на основе шрифта и текста, так что имена столбцов не будут обрезаться.

      public static void setDataTableMinColumnWidth(TableView<?> dataTable)
    {
        for (Node columnHeader : dataTable.lookupAll(".column-header"))
        {
            var columnString = columnHeader.getId();
            if (columnString != null)
            {
                for (Node columnHeaderLabel : columnHeader.lookupAll(".label"))
                {
                    var tableColumn = dataTable.getColumns()
                                               .stream()
                                               .filter(x -> x.getId()
                                                             .equals(columnString))
                                               .findFirst();
                    if (columnHeaderLabel instanceof Label && tableColumn.isPresent())
                    {
                        var label = (Label) columnHeaderLabel;
                        /* calc text width based on font */
                        var theText = new Text(label.getText());
                        theText.setFont(label.getFont());
                        var width = theText.getBoundsInLocal()
                                           .getWidth();
                        /*
                         * add 10px because of paddings/margins for the button
                         */
                        tableColumn.get()
                                   .setMinWidth(width + 10);
                    }
                }
            }
        }
    }

Как использовать:

      dataTable.needsLayoutProperty()
             .addListener((obs, o, n) -> setDataTableMinColumnWidth(dataTable));

Для столбцов сначала необходимо установить свойство id:

          TableColumn<BundImportTask, String> columnTask = new TableColumn<>("My Column");
    columnTask.setId("MyColumnId");
    columnTask.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()
                                                                        .fileName()));

Этот код автоматически изменяет размер всех столбцов в относительных пропорциях к ширине таблицы,
в то время как он может зафиксировать ширину первого столбца до заданного значения, когда ширина таблицы меньше, чем х

        // To generalize the columns width proportions in relation to the table width,
        // you do not need to put pixel related values, you can use small float numbers if you wish,
        // because it's the relative proportion of each columns width what matters here:

        final float[] widths = { 1.2f, 2f, 0.8f };// define the relational width of each column 

        // whether the first column should be fixed
        final boolean fixFirstColumm = true; 

        // fix the first column width when table width is lower than:
        final float fixOnTableWidth = 360; //pixels 

        // calulates sum of widths
        float sum = 0;
        for (double i : widths) {
            sum += i;
        }

        // calculates the fraction of the first column proportion in relation to the sum of all column proportions
        float firstColumnProportion = widths[0] / sum;

        // calculate the fitting fix width for the first column, you can change it by your needs, but it jumps to this width
        final float firstColumnFixSize = fixOnTableWidth * firstColumnProportion;

        // set the width to the columns
        for (int i = 0; i < widths.length; i++) {
            table.getColumns().get(i).prefWidthProperty().bind(table.widthProperty().multiply((widths[i] / sum)));
            // ---------The exact width-------------^-------------^
    if (fixFirstColumm)
            if (i == 0) {
                table.widthProperty().addListener(new ChangeListener<Number>() {
                    @Override
                    public void changed(ObservableValue<? extends Number> arg0, Number oldTableWidth, Number newTableWidth) {

                        if (newTableWidth.intValue() <= fixOnTableWidth) {

                            // before you can set new value to column width property, need to unbind the autoresize binding
                            table.getColumns().get(0).prefWidthProperty().unbind();
                            table.getColumns().get(0).prefWidthProperty().setValue(firstColumnFixSize);

                        } else if (!table.getColumns().get(0).prefWidthProperty().isBound()) {

                            // than readd the autoresize binding if condition table.width > x
                            table.getColumns().get(0).prefWidthProperty()
                                    .bind(table.widthProperty().multiply(firstColumnProportion));
                        }

                    }
                });
            }
        }

совет, чтобы поместить код в отдельный класс TableAutoresizeModel, там вы можете обрабатывать дальнейшие вычисления, например, для скрытия столбцов добавить слушателя...

Я реализовал решение, которое довольно сложнее, чем те, которые я нашел здесь, но которое позволяет изменять размер определенного столбца двойным щелчком по заголовку, при этом позволяя пользователю изменять размер столбцов вручную.

Это достигается путем прослушивания событий щелчка по заголовку таблицы (TableHeaderRow). Когда происходит двойной щелчок, конкретный заголовок столбца определяется путем сопоставления событий мыши X и Y.

Примечание: чтобы это работало, необходимо, чтобы у каждого столбца был установлен идентификатор.

// when skin is loaded (hence css), setup click listener on header to make column fit to content max width on double click
tableView.skinProperty().addListener((a, b, newSkin) -> {
    TableHeaderRow headerRow = (TableHeaderRow) tableView.lookup("TableHeaderRow");
    NestedTableColumnHeader headers = (NestedTableColumnHeader) (headerRow.getChildren().get(1));

    headerRow.setOnMouseClicked(evt -> {
        if (evt.getClickCount() != 2 || evt.getButton() != MouseButton.PRIMARY) return;

        // find the header column that contains the click
        for (TableColumnHeader header : headers.getColumnHeaders()) {
            if (header.contains(header.parentToLocal(evt.getX(), evt.getY()))) {
                fitColumnWidthToContent(header.getId());
            }
        }
        evt.consume();
    });
});

Метод изменения размера следующий:

 private void fitColumnWidthToContent (String colId) {
    // find column matching id
    TableColumn column = null;

    for (TableColumn tempCol : tableView.getColumns()) {
        if (tempCol.getId().equals(colId)) {
            column = tempCol;
            break;
        }
    }

    if (column == null) {
        throw new IllegalStateException("Column ID doesn't match any actual column");
    }

    // set default width to column header width
    Text text = new Text(column.getText());
    double max = text.getLayoutBounds().getWidth();

    for (int i = 0; i < tableView.getItems().size(); i++ ) {
        if (column.getCellData(i) == null) continue;

        text = new Text(column.getCellData(i).toString());
        double textWidth = text.getLayoutBounds().getWidth();

        if (textWidth > max) {
            max = textWidth;
        }
    }

    column.setPrefWidth(max + 12);
}

Надеюсь, это может быть полезно кому угодно.

Чтобы разрешить также ручное изменение размера, необходимо добавить немного больше кода для инициализации таблицы:

// listen to width changes in columns and set to pref width (otherwise if for example width changes because of
// user resizing the column, applying the old pref width won't work because it stayed the same)
for (TableColumn col : tableView.getColumns()) {
    col.widthProperty().addListener((obs, oldVal, newVal) -> {
        col.setPrefWidth(newVal.doubleValue());
    });
}

Я реализовал решение для TreeTableView. Он все еще находится в стадии эволюции, но уже дает многообещающие результаты. Далее описание решения.

В классе оболочки элемента управления я добавил к дочерним элементам управления TreeTableView и невидимый VBox. Фабрика ячеек предоставляет производные ячейки для целевого TreeTableColumn. Производные ячейки охватывают узел Label, который добавляется или удаляется из невидимого VBox в соответствии с пустым свойством, и для которого его prefWidth устанавливается в соответствии с шириной ячейки. В ячейках используются:

getProperties().put(Properties.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE)

Я переопределяю метод computePrefWidth() ячейки следующим образом:

      @Override
protected double computePrefWidth(double height) {

    return Double.max(_box.prefWidth(-1.0), super.computePrefWidth(height) + 24.0);

}

Свойство ширины Vbox привязано к prefWidth TreeTableColumn. Это необходимо также для изменения размера заголовка столбца.

Стоит отметить, что в настоящее время для упрощения разработки решения этот подход хорошо работает с отключенными встроенными функциями сортировки, упорядочивания и изменения размера. Т.е.

      _nameColumn = new TreeTableColumn<>("Name");
_nameColumn.setResizable(false);
_nameColumn.setReorderable(false);
_nameColumn.setSortable(false);

Удачного кодирования

myTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
for(TableColumn column: myTable.getColumns())
{           
    column.setMinWidth(200);                                        
}
<TableView fx:id="datalist" layoutX="30.0" layoutY="65.0" prefHeight="400.0" AnchorPane.bottomAnchor="100.0" AnchorPane.leftAnchor="30.0" AnchorPane.rightAnchor="30.0" AnchorPane.topAnchor="100.0">
        <columns>
            <TableColumn fx:id="number" minWidth="-1.0" prefWidth="-1.0" style="width: auto;" text="number" />
            <TableColumn fx:id="id" minWidth="-1.0" prefWidth="-1.0" text="id" />
            <TableColumn fx:id="name" minWidth="-1.0" prefWidth="-1.0" text="name" />
            <TableColumn fx:id="action" minWidth="-1.0" prefWidth="-1.0" text="todo" />
        </columns>
         **<columnResizePolicy>
            <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
         </columnResizePolicy>**
      </TableView>

После долгих исследований. Лучшее решение это..

tblPlan.setColumnResizePolicy((param) -> true );
Platform.runLater(() -> customResize(tblPlan));

"Custom Resize"

public void customResize(TableView<?> view) {

        AtomicLong width = new AtomicLong();
        view.getColumns().forEach(col -> {
            width.addAndGet((long) col.getWidth());
        });
        double tableWidth = view.getWidth();

        if (tableWidth > width.get()) {
            view.getColumns().forEach(col -> {
                col.setPrefWidth(col.getWidth()+((tableWidth-width.get())/view.getColumns().size()));
            });
        }
    }
public static void autoFillColumn(TableView<?> table, int col) {
    double width = 0;
    for (int i = 0; i < table.getColumns().size(); i++) {
        if (i != col) {
            width += table.getColumns().get(i).getWidth();
        }
    }
    table.getColumns().get(col).setPrefWidth(table.getWidth() - width);
}
Другие вопросы по тегам