修复javafx TableView中的列数

时间:2014-03-02 00:49:11

标签: javafx tableview

我在javafx中动态生成的tableView中有4列。但是在UI中,我得到了一个没有文本集的附加列。我想移动它,只保留四列。我怎样才能做到这一点? 该表也没有响应。我已将其设置为在所有方面增长,但只有额外的列增长。所有其他列都达到了最初在Scene Builder中给出的宽度。我希望桌子能够快速响应。我怎样才能做到这一点?

2 个答案:

答案 0 :(得分:5)

这不是一个空列,只是未使用的空间。你可以使用

tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

使列占用所有可用宽度。

我发现更好的解决方案是使用https://stackoverflow.com/a/10152992/2855515

等绑定的宽度百分比

答案 1 :(得分:0)

CONSTRAINED_RESIZE_POLICY将删除额外的列,但您必须强制所有列的宽度相同。您可以使用百分比宽度绑定作为brian建议,但是当您尝试手动调整列时,它具有非常奇怪的行为。 CONSTRAINED_RESIZE_POLICY不尊重列宽的事实是一个已知错误,JIRA就在这里:https://bugs.openjdk.java.net/browse/JDK-8157687

在javafx源代码中进行黑客攻击之后,我发现以下解决方案最适合我的项目。它并不完美,但它是我能找到的最好的解决方法。逻辑在第一个块中,其余部分主要是TableUtil类的copypasta,以使其具有必要的访问控制。只是实例化这个类而不是TableView,它应该可以工作。

import javafx.beans.InvalidationListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.*;
import javafx.util.Callback;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CustomTableView<S> extends TableView {

   static boolean isFirstRun = true;
   static int widthOfVerticalScrollbarOnTheRight = 16;

   public CustomTableView() {
      super(FXCollections.<S>observableArrayList());

      Callback<ResizeFeatures, Boolean> resizeFeaturesBooleanCallback = new Callback<ResizeFeatures, Boolean>() {
         @Override
         public Boolean call(ResizeFeatures prop) {

            TableView<?> table = prop.getTable();
            List<? extends TableColumnBase<?, ?>> visibleLeafColumns = table.getVisibleLeafColumns();
            Boolean result = TableUtil.constrainedResize(prop, isFirstRun, CustomTableView.this.getWidth(), visibleLeafColumns);
            isFirstRun = false;
            return result;
         }
      };

      setColumnResizePolicy(resizeFeaturesBooleanCallback);
   }


   /**
    * A package protected util class used by TableView and TreeTableView to reduce
    * the level of code duplication.
    */
   public static class TableUtil {

      private TableUtil() {
         // no-op
      }

      static void removeTableColumnListener(List<? extends TableColumnBase> list,
                                            final InvalidationListener columnVisibleObserver,
                                            final InvalidationListener columnSortableObserver,
                                            final InvalidationListener columnSortTypeObserver,
                                            final InvalidationListener columnComparatorObserver) {

         if (list == null) return;
         for (TableColumnBase col : list) {
            col.visibleProperty().removeListener(columnVisibleObserver);
            col.sortableProperty().removeListener(columnSortableObserver);
            col.comparatorProperty().removeListener(columnComparatorObserver);

//            col.sortTypeProperty().removeListener(columnSortTypeObserver);
            if (col instanceof TableColumn) {
               ((TableColumn) col).sortTypeProperty().removeListener(columnSortTypeObserver);
            } else if (col instanceof TreeTableColumn) {
               ((TreeTableColumn) col).sortTypeProperty().removeListener(columnSortTypeObserver);
            }

            removeTableColumnListener(col.getColumns(),
                  columnVisibleObserver,
                  columnSortableObserver,
                  columnSortTypeObserver,
                  columnComparatorObserver);
         }
      }

      static void addTableColumnListener(List<? extends TableColumnBase> list,
                                         final InvalidationListener columnVisibleObserver,
                                         final InvalidationListener columnSortableObserver,
                                         final InvalidationListener columnSortTypeObserver,
                                         final InvalidationListener columnComparatorObserver) {

         if (list == null) return;
         for (TableColumnBase col : list) {
            col.visibleProperty().addListener(columnVisibleObserver);
            col.sortableProperty().addListener(columnSortableObserver);
            col.comparatorProperty().addListener(columnComparatorObserver);

            if (col instanceof TableColumn) {
               ((TableColumn) col).sortTypeProperty().addListener(columnSortTypeObserver);
            } else if (col instanceof TreeTableColumn) {
               ((TreeTableColumn) col).sortTypeProperty().addListener(columnSortTypeObserver);
            }

            addTableColumnListener(col.getColumns(),
                  columnVisibleObserver,
                  columnSortableObserver,
                  columnSortTypeObserver,
                  columnComparatorObserver);
         }
      }

      static void removeColumnsListener(List<? extends TableColumnBase> list, ListChangeListener cl) {
         if (list == null) return;

         for (TableColumnBase col : list) {
            col.getColumns().removeListener(cl);
            removeColumnsListener(col.getColumns(), cl);
         }
      }

      static void addColumnsListener(List<? extends TableColumnBase> list, ListChangeListener cl) {
         if (list == null) return;

         for (TableColumnBase col : list) {
            col.getColumns().addListener(cl);
            addColumnsListener(col.getColumns(), cl);
         }
      }

      static void handleSortFailure(ObservableList<? extends TableColumnBase> sortOrder,
                                    SortEventType sortEventType, final Object... supportInfo) {
         // if the sort event is consumed we need to back out the previous
         // action so that the UI is not in an incorrect state
         if (sortEventType == SortEventType.COLUMN_SORT_TYPE_CHANGE) {
            // go back to the previous sort type
            final TableColumnBase changedColumn = (TableColumnBase) supportInfo[0];
            revertSortType(changedColumn);
         } else if (sortEventType == SortEventType.SORT_ORDER_CHANGE) {
            // Revert the sortOrder list to what it was previously
            ListChangeListener.Change change = (ListChangeListener.Change) supportInfo[0];

            final List toRemove = new ArrayList();
            final List toAdd = new ArrayList();
            while (change.next()) {
               if (change.wasAdded()) {
                  toRemove.addAll(change.getAddedSubList());
               }

               if (change.wasRemoved()) {
                  toAdd.addAll(change.getRemoved());
               }
            }

            sortOrder.removeAll(toRemove);
            sortOrder.addAll(toAdd);
         } else if (sortEventType == SortEventType.COLUMN_SORTABLE_CHANGE) {
            // no-op - it is ok for the sortable type to remain as-is
         } else if (sortEventType == SortEventType.COLUMN_COMPARATOR_CHANGE) {
            // no-op - it is ok for the comparator to remain as-is
         }
      }

      private static void revertSortType(TableColumnBase changedColumn) {
         if (changedColumn instanceof TableColumn) {
            TableColumn tableColumn = (TableColumn) changedColumn;
            final TableColumn.SortType sortType = tableColumn.getSortType();
            if (sortType == TableColumn.SortType.ASCENDING) {
               tableColumn.setSortType(null);
            } else if (sortType == TableColumn.SortType.DESCENDING) {
               tableColumn.setSortType(TableColumn.SortType.ASCENDING);
            } else if (sortType == null) {
               tableColumn.setSortType(TableColumn.SortType.DESCENDING);
            }
         } else if (changedColumn instanceof TreeTableColumn) {
            TreeTableColumn tableColumn = (TreeTableColumn) changedColumn;
            final TreeTableColumn.SortType sortType = tableColumn.getSortType();
            if (sortType == TreeTableColumn.SortType.ASCENDING) {
               tableColumn.setSortType(null);
            } else if (sortType == TreeTableColumn.SortType.DESCENDING) {
               tableColumn.setSortType(TreeTableColumn.SortType.ASCENDING);
            } else if (sortType == null) {
               tableColumn.setSortType(TreeTableColumn.SortType.DESCENDING);
            }
         }
      }

      static enum SortEventType {
         SORT_ORDER_CHANGE,
         COLUMN_SORT_TYPE_CHANGE,
         COLUMN_SORTABLE_CHANGE,
         COLUMN_COMPARATOR_CHANGE
      }


      /**
       * The constrained resize algorithm used by TableView and TreeTableView.
       *
       * @param prop
       * @param isFirstRun
       * @param tableWidth
       * @param visibleLeafColumns
       * @return
       */
      public static boolean constrainedResize(ResizeFeaturesBase prop,
                                              boolean isFirstRun,
                                              double tableWidth,
                                              List<? extends TableColumnBase<?, ?>> visibleLeafColumns) {
         TableColumnBase<?, ?> column = prop.getColumn();
         double delta = prop.getDelta();

        /*
         * There are two phases to the constrained resize policy:
         *   1) Ensuring internal consistency (i.e. table width == sum of all visible
         *      columns width). This is often called when the table is resized.
         *   2) Resizing the given column by __up to__ the given delta.
         *
         * It is possible that phase 1 occur and there be no need for phase 2 to
         * occur.
         */

         boolean isShrinking;
         double target;
         double totalLowerBound = 0;
         double totalUpperBound = 0;

         if (tableWidth == 0) return false;

        /*
         * PHASE 1: Check to ensure we have internal consistency. Based on the
         *          Swing JTable implementation.
         */
         // determine the width of all visible columns, and their preferred width
         double colWidth = 0;

         for (int i = 0; i < visibleLeafColumns.size(); i++) {
            if (i < visibleLeafColumns.size() - 1) {
               colWidth += visibleLeafColumns.get(i).getWidth();
            } else {
               colWidth += visibleLeafColumns.get(i).getWidth()+ widthOfVerticalScrollbarOnTheRight;
            }
         }

         if (Math.abs(colWidth - tableWidth) > 1) {
            isShrinking = colWidth > tableWidth;
            target = tableWidth;

            if (isFirstRun) {
               // if we are here we have an inconsistency - these two values should be
               // equal when this resizing policy is being used.
               for (TableColumnBase<?, ?> col : visibleLeafColumns) {
                  totalLowerBound += col.getMinWidth();
                  totalUpperBound += col.getMaxWidth();
               }

               // We run into trouble if the numbers are set to infinity later on
               totalUpperBound = totalUpperBound == Double.POSITIVE_INFINITY ?
                     Double.MAX_VALUE :
                     (totalUpperBound == Double.NEGATIVE_INFINITY ? Double.MIN_VALUE : totalUpperBound);

               for (TableColumnBase col : visibleLeafColumns) {
                  double lowerBound = col.getMinWidth();
                  double upperBound = col.getMaxWidth();

                  // Check for zero. This happens when the distribution of the delta
                  // finishes early due to a series of "fixed" entries at the end.
                  // In this case, lowerBound == upperBound, for all subsequent terms.
                  double newSize;
                  if (Math.abs(totalLowerBound - totalUpperBound) < .0000001) {
                     newSize = lowerBound;
                  } else {
                     double f = (target - totalLowerBound) / (totalUpperBound - totalLowerBound);
                     newSize = Math.round(lowerBound + f * (upperBound - lowerBound));
                  }

                  double remainder = resize(col, newSize - col.getWidth());

                  target -= newSize + remainder;
                  totalLowerBound -= lowerBound;
                  totalUpperBound -= upperBound;
               }

               CustomTableView.isFirstRun = false;
            } else {
               double actualDelta = tableWidth - colWidth;
               List<? extends TableColumnBase<?, ?>> cols = visibleLeafColumns;
               resizeColumns(cols, actualDelta);
            }
         }

         // At this point we can be happy in the knowledge that we have internal
         // consistency, i.e. table width == sum of the width of all visible
         // leaf columns.

        /*
         * Column may be null if we just changed the resize policy, and we
         * just wanted to enforce internal consistency, as mentioned above.
         */
         if (column == null) {
            return false;
         }

        /*
         * PHASE 2: Handling actual column resizing (by the user). Based on my own
         *          implementation (based on the UX spec).
         */

         isShrinking = delta < 0;

         // need to find the last leaf column of the given column - it is this
         // column that we actually resize from. If this column is a leaf, then we
         // use it.
         TableColumnBase<?, ?> leafColumn = column;
         while (leafColumn.getColumns().size() > 0) {
            leafColumn = leafColumn.getColumns().get(leafColumn.getColumns().size() - 1);
         }

         int colPos = visibleLeafColumns.indexOf(leafColumn);
         int endColPos = visibleLeafColumns.size() - 1;

         // we now can split the observableArrayList into two subobservableArrayLists, representing all
         // columns that should grow, and all columns that should shrink
         //    var growingCols = if (isShrinking)
         //        then table.visibleLeafColumns[colPos+1..endColPos]
         //        else table.visibleLeafColumns[0..colPos];
         //    var shrinkingCols = if (isShrinking)
         //        then table.visibleLeafColumns[0..colPos]
         //        else table.visibleLeafColumns[colPos+1..endColPos];


         double remainingDelta = delta;
         while (endColPos > colPos && remainingDelta != 0) {
            TableColumnBase<?, ?> resizingCol = visibleLeafColumns.get(endColPos);
            endColPos--;

            // if the column width is fixed, break out and try the next column
            if (!resizingCol.isResizable()) continue;

            // for convenience we discern between the shrinking and growing columns
            TableColumnBase<?, ?> shrinkingCol = isShrinking ? leafColumn : resizingCol;
            TableColumnBase<?, ?> growingCol = !isShrinking ? leafColumn : resizingCol;

            //        (shrinkingCol.width == shrinkingCol.minWidth) or (growingCol.width == growingCol.maxWidth)

            if (growingCol.getWidth() > growingCol.getPrefWidth()) {
               // growingCol is willing to be generous in this case - it goes
               // off to find a potentially better candidate to grow
               List<? extends TableColumnBase> seq = visibleLeafColumns.subList(colPos + 1, endColPos + 1);
               for (int i = seq.size() - 1; i >= 0; i--) {
                  TableColumnBase<?, ?> c = seq.get(i);
                  if (c.getWidth() < c.getPrefWidth()) {
                     growingCol = c;
                     break;
                  }
               }
            }
            //
            //        if (shrinkingCol.width < shrinkingCol.prefWidth) {
            //            for (c in reverse table.visibleLeafColumns[colPos+1..endColPos]) {
            //                if (c.width > c.prefWidth) {
            //                    shrinkingCol = c;
            //                    break;
            //                }
            //            }
            //        }


            double sdiff = Math.min(Math.abs(remainingDelta), shrinkingCol.getWidth() - shrinkingCol.getMinWidth());

//                System.out.println("\tshrinking " + shrinkingCol.getText() + " and growing " + growingCol.getText());
//                System.out.println("\t\tMath.min(Math.abs("+remainingDelta+"), "+shrinkingCol.getWidth()+" - "+shrinkingCol.getMinWidth()+") = " + sdiff);

            double delta1 = resize(shrinkingCol, -sdiff);
            double delta2 = resize(growingCol, sdiff);
            remainingDelta += isShrinking ? sdiff : -sdiff;
         }
         return remainingDelta == 0;
      }

      // function used to actually perform the resizing of the given column,
      // whilst ensuring it stays within the min and max bounds set on the column.
      // Returns the remaining delta if it could not all be applied.
      static double resize(TableColumnBase column, double delta) {
         if (delta == 0) return 0.0F;
         if (!column.isResizable()) return delta;

         final boolean isShrinking = delta < 0;
         final List<TableColumnBase<?, ?>> resizingChildren = getResizableChildren(column, isShrinking);

         if (resizingChildren.size() > 0) {
            return resizeColumns(resizingChildren, delta);
         } else {
            double newWidth = column.getWidth() + delta;

            if (newWidth > column.getMaxWidth()) {
               column.impl_setWidth(column.getMaxWidth());
               return newWidth - column.getMaxWidth();
            } else if (newWidth < column.getMinWidth()) {
               column.impl_setWidth(column.getMinWidth());
               return newWidth - column.getMinWidth();
            } else {
               column.impl_setWidth(newWidth);
               return 0.0F;
            }
         }
      }

      // Returns all children columns of the given column that are able to be
      // resized. This is based on whether they are visible, resizable, and have
      // not space before they hit the min / max values.
      private static List<TableColumnBase<?, ?>> getResizableChildren(TableColumnBase<?, ?> column, boolean isShrinking) {
         if (column == null || column.getColumns().isEmpty()) {
            return Collections.emptyList();
         }

         List<TableColumnBase<?, ?>> tablecolumns = new ArrayList<TableColumnBase<?, ?>>();
         for (TableColumnBase c : column.getColumns()) {
            if (!c.isVisible()) continue;
            if (!c.isResizable()) continue;

            if (isShrinking && c.getWidth() > c.getMinWidth()) {
               tablecolumns.add(c);
            } else if (!isShrinking && c.getWidth() < c.getMaxWidth()) {
               tablecolumns.add(c);
            }
         }
         return tablecolumns;
      }

      private static double resizeColumns(List<? extends TableColumnBase<?, ?>> columns, double delta) {
         // distribute space between all visible children who can be resized.
         // To do this we need to work out if we're shrinking or growing the
         // children, and then which children can be resized based on their
         // min/pref/max/fixed properties. The results of this are in the
         // resizingChildren observableArrayList above.
         final int columnCount = columns.size();

         // work out how much of the delta we should give to each child. It should
         // be an equal amount (at present), although perhaps we'll allow for
         // functions to calculate this at a later date.
         double colDelta = delta / columnCount;

         // we maintain a count of the amount of delta remaining to ensure that
         // the column resize operation accurately reflects the location of the
         // mouse pointer. Every time this value is not 0, the UI is a teeny bit
         // more inaccurate whilst the user continues to resize.
         double remainingDelta = delta;

         // We maintain a count of the current column that we're on in case we
         // need to redistribute the remainingDelta among remaining sibling.
         int col = 0;

         // This is a bit hacky - often times the leftOverDelta is zero, but
         // remainingDelta doesn't quite get down to 0. In these instances we
         // short-circuit and just return 0.0.
         boolean isClean = true;
         for (TableColumnBase<?, ?> childCol : columns) {
            col++;

            // resize each child column
            double leftOverDelta = resize(childCol, colDelta);

            // calculate the remaining delta if the was anything left over in
            // the last resize operation
            remainingDelta = remainingDelta - colDelta + leftOverDelta;

            //      println("\tResized {childCol.text} with {colDelta}, but {leftOverDelta} was left over. RemainingDelta is now {remainingDelta}");

            if (leftOverDelta != 0) {
               isClean = false;
               // and recalculate the distribution of the remaining delta for
               // the remaining siblings.
               colDelta = remainingDelta / (columnCount - col);
            }
         }

         // see isClean above for why this is done
         return isClean ? 0.0 : remainingDelta;
      }
   }
}