如何在JavaFX9中查找TableView中可见行的索引

如何在JavaFX9中查找TableView中可见行的索引,java,java-9,javafx-9,Java,Java 9,Javafx 9,如何在JavaFX9中获取TableView中可见行的索引?在JavaFX8中,我可以执行以下操作: // --- The offending imports in Java 9 // import com.sun.javafx.scene.control.skin.TableViewSkin; // import com.sun.javafx.scene.control.skin.VirtualFlow; /** * This is a total hack. We n

如何在JavaFX9中获取TableView中可见行的索引?在JavaFX8中,我可以执行以下操作:

  // --- The offending imports in Java 9
  // import com.sun.javafx.scene.control.skin.TableViewSkin;
  // import com.sun.javafx.scene.control.skin.VirtualFlow;

  /**
   * This is a total hack. We need it as scrollTo jumps the selected
   * row to the top of the table. Jarring if the row is already 
   * visible. As a workaround, we only scroll if the row isn't already
   * visible
   *
   * @return A 2 element ray with the start and end index of visible rows
   */
  public int[] getVisibleRows() {
      TableView<?> tableView = getTableView();
      TableViewSkin<?> skin = (TableViewSkin<?>) tableView.getSkin();
      if (skin == null) return new int[] {0, 0};
      VirtualFlow<?> flow = (VirtualFlow<?>) skin.getChildren().get(1);
      int idxFirst;
      int idxLast;
      if (flow != null &&
              flow.getFirstVisibleCellWithinViewPort() != null &&
              flow.getLastVisibleCellWithinViewPort() != null) {
          idxFirst = flow.getFirstVisibleCellWithinViewPort().getIndex();
          if (idxFirst > tableView.getItems().size()) {
              idxFirst = tableView.getItems().size() - 1;
          }
          idxLast = flow.getLastVisibleCellWithinViewPort().getIndex();
          if (idxLast > tableView.getItems().size()) {
              idxLast = tableView.getItems().size() - 1;
          }
      }
      else {
          idxFirst = 0;
          idxLast = 0;
      }
      return new int[]{idxFirst, idxLast};
  }
是否有任何官方方法可以获取TableView中的可见行?有没有其他更好的解决办法

更新:基于@user4712734解决方案的解决方案

TableViewExt<?> tableViewExt = new TableViewExt<>(tableView);

public int[] getVisibleRows() {
    return new int[]{ tableViewExt.getFirstVisibleIndex(), 
                      tableViewExt.getLastVisibleIndex()};
}
TableViewExt TableViewExt=新的TableViewExt(tableView);
public int[]getVisibleRows(){
返回新的int[]{tableViewExt.getFirstVisibleIndex(),
tableViewExt.getLastVisibleIndex()};
}

我在使用JavaFX 8特定的TableViewSkin/VirtualFlow代码时遇到了相同的问题,但现在使用此解决方法,它将一些建议总结在关于此问题的另一篇文章中-请参阅

此解决方案在Java8/9中运行和编译,不使用参考包com.sun.javafx。要使用该类,需要在构造TableView和向其中添加行之间创建TableViewExtra:

TableView<YourRowType> tableView = new TableView<YourRowType>();
TableViewExtra<YourRowType> tvX = new TableViewExtra(tableView);
// Add some rows etc
您可以看到我曾经在评论中使用过的旧com.sun.javafx依赖项:

import java.util.LinkedHashSet;

//com.sun.javafx: START
//import com.sun.javafx.scene.control.skin.TableViewSkin;
//import com.sun.javafx.scene.control.skin.VirtualFlow;
//import javafx.beans.value.ChangeListener;
//import javafx.beans.value.ObservableValue;
//import javafx.scene.Node;
//import javafx.scene.control.IndexedCell;
//import javafx.scene.control.Skin;
// com.sun.javafx: END 

import javafx.collections.ObservableList;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.util.Callback;

/**
 * Extra calls for TableView which would have been nice to see in JavaFx TableView
 */
public class TableViewExtra<T> // com.sun.javafx: implements ChangeListener<Skin<?>>
{
    private final TableView<T> tableView;

    // com.sun.javafx: private VirtualFlow<?> flow;

    LinkedHashSet<TableRow<T>> rows = new LinkedHashSet<>();
    private int firstIndex;
    private int lastIndex;

    public TableViewExtra(TableView<T> tableView)
    {
        this.tableView = tableView;

        // com.sun.javafx: tableView.skinProperty().addListener(this);

        // Callback to monitor row creation and to identify visible screen rows
        final Callback<TableView<T>, TableRow<T>> rf = tableView.getRowFactory();

        final Callback<TableView<T>, TableRow<T>> modifiedRowFactory = new Callback<TableView<T>, TableRow<T>>() {

            @Override
            public TableRow<T> call(TableView<T> param)
            {
                TableRow<T> r = rf != null ? rf.call(param) : new TableRow<T>();
                // Save row, this implementation relies on JaxaFX re-using TableRow efficiently
                rows.add(r);
                return r;
            }
        };
        tableView.setRowFactory(modifiedRowFactory);
    }

    // com.sun.javafx BEGIN
//    public void changed(ObservableValue<? extends Skin<?>> ov, Skin<?> t, Skin<?> t1)
//    {
//        if (t1 == null) {
//            return;
//        }
//
//        TableViewSkin<?> tvs = (TableViewSkin<?>) t1;
//        ObservableList<Node> kids = tvs.getChildren();
//
//        if (kids == null || kids.isEmpty() || kids.size() < 2) {
//            return;
//        }
//        flow = (VirtualFlow<?>) kids.get(1);
//    }
    // com.sun.javafx END

    /**
     * Changes the current view to ensure that one of the passed index positions
     * is visible on screen. The view is not changed if any of the passed index positions is already visible.
     * The table scroll position is moved so that the closest index to the current position is visible. 
     * @param indices Assumed to be in ascending order.
     * 
     */
    public void
    scrollToIndex(int ... indices)
    {
        int first = getFirstVisibleIndex();
        int last = getLastVisibleIndex();
        int where = first;

        boolean changeScrollPos = true;

        // No point moving current scroll position if one of the index items is visible already:
        if (first >= 0 && last >= first)
            for (int idx : indices)
            {
                if (first <= idx && idx <= last)
                {
                    changeScrollPos = false;
                    break;
                }
            }


        if (indices.length > 0 && changeScrollPos)
        {
            where = indices[0];
            if (first >= 0)
            {
                int x = closestTo(indices, first);
                int abs = Math.abs(x - first);
                if (abs < Math.abs(where - first))
                {
                    where = x;
                }
            }
            if (last >= 0)
            {
                int x = closestTo(indices, last);
                int abs = Math.abs(x - last);
                if (abs < Math.abs(where - last))
                {
                    where = x;
                }
            }
            tableView.scrollTo(where);
        }
    }

    private static int closestTo(int[] indices, int value)
    {
        int x    = indices[0];
        int diff = Math.abs(value - x);
        int newDiff = diff;
        for (int v : indices)
        {
            newDiff = Math.abs(value - v);
            if (newDiff < diff)
            {
                x    = v;
                diff = newDiff;
            }
        }
        return x;
    }

    private void recomputeVisibleIndexes()
    {
        firstIndex = -1;
        lastIndex = -1;

        // Work out which of the rows are visible
        double tblViewHeight = tableView.getHeight();
        double headerHeight = tableView.lookup(".column-header-background").getBoundsInLocal().getHeight();
        double viewPortHeight = tblViewHeight - headerHeight;
        for(TableRow<T> r : rows)
        {
            if (!r.isVisible()) continue; // tingyik90

            double minY = r.getBoundsInParent().getMinY();
            double maxY = r.getBoundsInParent().getMaxY();

            boolean hidden  = (maxY < 0) || (minY > viewPortHeight);
            // boolean fullyVisible = !hidden && (maxY <= viewPortHeight) && (minY >= 0);
            if (!hidden)
            {
                if (firstIndex < 0 || r.getIndex() < firstIndex)
                {
                    firstIndex = r.getIndex();
                }
                if (lastIndex < 0 || r.getIndex() > lastIndex)
                {
                    lastIndex = r.getIndex();
                }
            }
        }

    }
    /**
     * Find the first row in the tableView which is visible on the display
     * @return -1 if none visible or the index of the first visible row (wholly or fully)
     */
    public int getFirstVisibleIndex()
    {
        // com.sun.javafx: START
//        int jre8Index = -1;
//        if (flow != null)
//        {
//            IndexedCell<?> cell = flow.getFirstVisibleCell();
//            if (cell != null)
//            {
//                jre8Index = cell.getIndex();
//            }
//        }
        // com.sun.javafx: END

        recomputeVisibleIndexes();

        System.out.println("getFirstVisibleIndex "+firstIndex+" rows="+rows.size()/*com.sun.javafx:+" jre8Index="+jre8Index*/);
        return firstIndex;
    }

    /**
     * Find the last row in the tableView which is visible on the display
     * @return -1 if none visible or the index of the last visible row (wholly or fully)
     */
    public int getLastVisibleIndex()
    {
        // com.sun.javafx: END
//        int jre8Index = -1;
//        if (flow != null)
//        {
//            IndexedCell<?> cell = flow.getLastVisibleCell();
//            if (cell != null)
//                jre8Index = cell.getIndex();
//        }
        // com.sun.javafx: END

        recomputeVisibleIndexes();

        System.out.println("getLastVisibleIndex "+lastIndex+" rows="+rows.size()/*com.sun.javafx:+" jre8Index="+jre8Index*/);

        return lastIndex;
    }

    /**
     * Ensure that some part of the current selection is visible in the display view
     */
    public void scrollToSelection()
    {
        ObservableList<Integer> seln = tableView.getSelectionModel().getSelectedIndices();
        int[] indices = new int[seln.size()];
        for (int i = 0; i < indices.length; i++)
        {
            indices[i] = seln.get(i).intValue();
        }
        scrollToIndex(indices);
    }
}
import java.util.LinkedHashSet;
//com.sun.javafx:开始
//导入com.sun.javafx.scene.control.skin.TableViewSkin;
//导入com.sun.javafx.scene.control.skin.VirtualFlow;
//导入javafx.beans.value.ChangeListener;
//导入javafx.beans.value.observeValue;
//导入javafx.scene.Node;
//导入javafx.scene.control.IndexedCell;
//导入javafx.scene.control.Skin;
//com.sun.javafx:结束
导入javafx.collections.ObservableList;
导入javafx.scene.control.TableRow;
导入javafx.scene.control.TableView;
导入javafx.util.Callback;
/**
*对TableView的额外调用,在JavaFXTableView中可以看到这些调用
*/
公共类TableViewExtra//com.sun.javafx:实现ChangeListener流;
LinkedHashSet行=新LinkedHashSet();
私有索引;
私有索引;
公共TableView附加(TableView TableView)
{
this.tableView=tableView;
//com.sun.javafx:tableView.skinProperty().addListener(这个);
//回调以监视行创建并标识可见的屏幕行
最终回调rf=tableView.getRowFactory();
最终回调modifiedRowFactory=新回调(){
@凌驾
公共TableRow调用(TableView参数)
{
TableRow r=rf!=null?rf.call(参数):new TableRow();
//saverow,这个实现依赖于JaxaFX有效地重用TableRow
行。添加(r);
返回r;
}
};
tableView.setRowFactory(modifiedRowFactory);
}
//com.sun.javafx开始
//公共空隙已更改(可观察值>ov、皮肤t、皮肤t1)
//    {
//如果(t1==null){
//返回;
//        }
//
//TableViewSkin TV=(TableViewSkin)t1;
//ObservableList kids=tvs.getChildren();
//
//if(kids==null | | kids.isEmpty()| | kids.size()<2){
//返回;
//        }
//flow=(VirtualFlow)kids.get(1);
//    }
//com.sun.javafx结束
/**
*更改当前视图以确保传递的索引位置之一
*在屏幕上是可见的。如果传递的任何索引位置已可见,则不会更改视图。
*移动表格滚动位置,以便可以看到与当前位置最近的索引。
*@param索引假定为升序。
* 
*/
公共空间
滚动索引(整数…索引)
{
int first=getFirstVisibleIndex();
int last=getLastVisibleIndex();
int其中=第一;
布尔值changeScrollPos=true;
//如果其中一个索引项已可见,则无需移动当前滚动位置:
如果(第一个>=0&&last>=first)
for(int idx:索引)
{
如果(第一个=0)
{
int x=最接近(指数,第一);
int abs=Math.abs(x-第一);
if(abs=0)
{
int x=最接近(索引,最后一个);
int abs=Math.abs(x-最后一个);
如果(绝对值<数学绝对值(其中-最后一个))
{
式中=x;
}
}
tableView.scrollTo(其中);
}
}
私有静态int closestTo(int[]索引,int值)
{
int x=指数[0];
int diff=Math.abs(值-x);
int newDiff=diff;
用于(INTV:索引)
{
newDiff=Math.abs(值-v);
if(newDiff视口高度);
//布尔值fullyVisible=!hidden&&(maxY=0);
如果(!隐藏)
{
if(firstIndex<0 | | r.getIndex()lastIndex)
{
lastIndex=r.getIndex();
}
}
}
}
/**
*找到th
int firstVisRowIndex = tvX.getFirstVisibleIndex();
int lastVisRowIndex  = tvX.getLastVisibleIndex();
txV.scrollToSelection();
import java.util.LinkedHashSet;

//com.sun.javafx: START
//import com.sun.javafx.scene.control.skin.TableViewSkin;
//import com.sun.javafx.scene.control.skin.VirtualFlow;
//import javafx.beans.value.ChangeListener;
//import javafx.beans.value.ObservableValue;
//import javafx.scene.Node;
//import javafx.scene.control.IndexedCell;
//import javafx.scene.control.Skin;
// com.sun.javafx: END 

import javafx.collections.ObservableList;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.util.Callback;

/**
 * Extra calls for TableView which would have been nice to see in JavaFx TableView
 */
public class TableViewExtra<T> // com.sun.javafx: implements ChangeListener<Skin<?>>
{
    private final TableView<T> tableView;

    // com.sun.javafx: private VirtualFlow<?> flow;

    LinkedHashSet<TableRow<T>> rows = new LinkedHashSet<>();
    private int firstIndex;
    private int lastIndex;

    public TableViewExtra(TableView<T> tableView)
    {
        this.tableView = tableView;

        // com.sun.javafx: tableView.skinProperty().addListener(this);

        // Callback to monitor row creation and to identify visible screen rows
        final Callback<TableView<T>, TableRow<T>> rf = tableView.getRowFactory();

        final Callback<TableView<T>, TableRow<T>> modifiedRowFactory = new Callback<TableView<T>, TableRow<T>>() {

            @Override
            public TableRow<T> call(TableView<T> param)
            {
                TableRow<T> r = rf != null ? rf.call(param) : new TableRow<T>();
                // Save row, this implementation relies on JaxaFX re-using TableRow efficiently
                rows.add(r);
                return r;
            }
        };
        tableView.setRowFactory(modifiedRowFactory);
    }

    // com.sun.javafx BEGIN
//    public void changed(ObservableValue<? extends Skin<?>> ov, Skin<?> t, Skin<?> t1)
//    {
//        if (t1 == null) {
//            return;
//        }
//
//        TableViewSkin<?> tvs = (TableViewSkin<?>) t1;
//        ObservableList<Node> kids = tvs.getChildren();
//
//        if (kids == null || kids.isEmpty() || kids.size() < 2) {
//            return;
//        }
//        flow = (VirtualFlow<?>) kids.get(1);
//    }
    // com.sun.javafx END

    /**
     * Changes the current view to ensure that one of the passed index positions
     * is visible on screen. The view is not changed if any of the passed index positions is already visible.
     * The table scroll position is moved so that the closest index to the current position is visible. 
     * @param indices Assumed to be in ascending order.
     * 
     */
    public void
    scrollToIndex(int ... indices)
    {
        int first = getFirstVisibleIndex();
        int last = getLastVisibleIndex();
        int where = first;

        boolean changeScrollPos = true;

        // No point moving current scroll position if one of the index items is visible already:
        if (first >= 0 && last >= first)
            for (int idx : indices)
            {
                if (first <= idx && idx <= last)
                {
                    changeScrollPos = false;
                    break;
                }
            }


        if (indices.length > 0 && changeScrollPos)
        {
            where = indices[0];
            if (first >= 0)
            {
                int x = closestTo(indices, first);
                int abs = Math.abs(x - first);
                if (abs < Math.abs(where - first))
                {
                    where = x;
                }
            }
            if (last >= 0)
            {
                int x = closestTo(indices, last);
                int abs = Math.abs(x - last);
                if (abs < Math.abs(where - last))
                {
                    where = x;
                }
            }
            tableView.scrollTo(where);
        }
    }

    private static int closestTo(int[] indices, int value)
    {
        int x    = indices[0];
        int diff = Math.abs(value - x);
        int newDiff = diff;
        for (int v : indices)
        {
            newDiff = Math.abs(value - v);
            if (newDiff < diff)
            {
                x    = v;
                diff = newDiff;
            }
        }
        return x;
    }

    private void recomputeVisibleIndexes()
    {
        firstIndex = -1;
        lastIndex = -1;

        // Work out which of the rows are visible
        double tblViewHeight = tableView.getHeight();
        double headerHeight = tableView.lookup(".column-header-background").getBoundsInLocal().getHeight();
        double viewPortHeight = tblViewHeight - headerHeight;
        for(TableRow<T> r : rows)
        {
            if (!r.isVisible()) continue; // tingyik90

            double minY = r.getBoundsInParent().getMinY();
            double maxY = r.getBoundsInParent().getMaxY();

            boolean hidden  = (maxY < 0) || (minY > viewPortHeight);
            // boolean fullyVisible = !hidden && (maxY <= viewPortHeight) && (minY >= 0);
            if (!hidden)
            {
                if (firstIndex < 0 || r.getIndex() < firstIndex)
                {
                    firstIndex = r.getIndex();
                }
                if (lastIndex < 0 || r.getIndex() > lastIndex)
                {
                    lastIndex = r.getIndex();
                }
            }
        }

    }
    /**
     * Find the first row in the tableView which is visible on the display
     * @return -1 if none visible or the index of the first visible row (wholly or fully)
     */
    public int getFirstVisibleIndex()
    {
        // com.sun.javafx: START
//        int jre8Index = -1;
//        if (flow != null)
//        {
//            IndexedCell<?> cell = flow.getFirstVisibleCell();
//            if (cell != null)
//            {
//                jre8Index = cell.getIndex();
//            }
//        }
        // com.sun.javafx: END

        recomputeVisibleIndexes();

        System.out.println("getFirstVisibleIndex "+firstIndex+" rows="+rows.size()/*com.sun.javafx:+" jre8Index="+jre8Index*/);
        return firstIndex;
    }

    /**
     * Find the last row in the tableView which is visible on the display
     * @return -1 if none visible or the index of the last visible row (wholly or fully)
     */
    public int getLastVisibleIndex()
    {
        // com.sun.javafx: END
//        int jre8Index = -1;
//        if (flow != null)
//        {
//            IndexedCell<?> cell = flow.getLastVisibleCell();
//            if (cell != null)
//                jre8Index = cell.getIndex();
//        }
        // com.sun.javafx: END

        recomputeVisibleIndexes();

        System.out.println("getLastVisibleIndex "+lastIndex+" rows="+rows.size()/*com.sun.javafx:+" jre8Index="+jre8Index*/);

        return lastIndex;
    }

    /**
     * Ensure that some part of the current selection is visible in the display view
     */
    public void scrollToSelection()
    {
        ObservableList<Integer> seln = tableView.getSelectionModel().getSelectedIndices();
        int[] indices = new int[seln.size()];
        for (int i = 0; i < indices.length; i++)
        {
            indices[i] = seln.get(i).intValue();
        }
        scrollToIndex(indices);
    }
}