【问题标题】:TableView ScrollBar PolicyTableView 滚动条策略
【发布时间】:2015-03-09 07:32:03
【问题描述】:

有没有办法像 ScrollPane 一样更改 TableView 中 ScrollBar 的策略?我只看到 TableView 的 VirtualFlow 计算可见性,但没有人工干预的可能性。

我需要垂直滚动条始终可见,而水平滚动条永远不可见。更改栏的可见状态不起作用。

例子:

import java.time.LocalDate;
import java.time.Month;
import java.util.Set;

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import com.sun.javafx.scene.control.skin.VirtualFlow;

public class ScrollBarInTableViewDemo extends Application {

    private TableView<Data> table1 = new TableView<>(); // table with scrollbars
    private TableView<Data> table2 = new TableView<>(); // table without scrollbars

    private final ObservableList<Data> data =
            FXCollections.observableArrayList( 
                    new Data( LocalDate.of(2015, Month.JANUARY, 10), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 11), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 12), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 13), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 14), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 15), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 16), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 17), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 18), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 19), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 20), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 21), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 22), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 23), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 24), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 25), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 26), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 27), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 28), 10.0, 20.0, 30.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 29), 40.0, 50.0, 60.0),
                    new Data( LocalDate.of(2015, Month.JANUARY, 30), 10.0, 20.0, 30.0)

            );

    final HBox hb = new HBox();

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {

        Scene scene = new Scene(new Group());

        stage.setTitle("Table View Sample");
        stage.setWidth(800);
        stage.setHeight(800);

        // setup table columns
        setupTableColumns( table1);
        setupTableColumns( table2);

        // fill tables with data
        table1.setItems(data);
        table1.setTableMenuButtonVisible(true);

        // create container
        HBox hBox = new HBox();
        hBox.getChildren().addAll( table1, table2);

        ((Group) scene.getRoot()).getChildren().addAll( hBox);

        stage.setScene(scene);
        stage.show();

        ScrollBar table1HorizontalScrollBar = findScrollBar( table1, Orientation.HORIZONTAL);
        ScrollBar table1VerticalScrollBar = findScrollBar( table1, Orientation.VERTICAL);

        // this doesn't work:
        table1HorizontalScrollBar.setVisible(false);
        table1VerticalScrollBar.setVisible(false);

        ScrollBar table2HorizontalScrollBar = findScrollBar( table2, Orientation.HORIZONTAL);
        ScrollBar table2VerticalScrollBar = findScrollBar( table2, Orientation.VERTICAL);

        // this doesn't work:
        table2HorizontalScrollBar.setVisible(true);
        table2VerticalScrollBar.setVisible(true);

        // enforce layout to see if anything has an effect
        VirtualFlow flow1 = (VirtualFlow) table1.lookup(".virtual-flow");
        flow1.requestLayout();

        VirtualFlow flow2 = (VirtualFlow) table2.lookup(".virtual-flow");
        flow2.requestLayout();

    }

    /**
     * Primary table column mapping.
     */
    private void setupTableColumns( TableView table) {


        TableColumn<Data, LocalDate> dateCol = new TableColumn<>("Date");
        dateCol.setPrefWidth(120);
        dateCol.setCellValueFactory(new PropertyValueFactory<>("date"));

        TableColumn<Data, Double> value1Col = new TableColumn<>("Value 1");
        value1Col.setPrefWidth(90);
        value1Col.setCellValueFactory(new PropertyValueFactory<>("value1"));

        TableColumn<Data, Double> value2Col = new TableColumn<>("Value 2");
        value2Col.setPrefWidth(90);
        value2Col.setCellValueFactory(new PropertyValueFactory<>("value2"));

        TableColumn<Data, Double> value3Col = new TableColumn<>("Value 3");
        value3Col.setPrefWidth(90);
        value3Col.setCellValueFactory(new PropertyValueFactory<>("value3"));

        table.getColumns().addAll( dateCol, value1Col, value2Col, value3Col);


    }

    /**
     * Find the horizontal scrollbar of the given table.
     * @param table
     * @return
     */
    private ScrollBar findScrollBar(TableView<?> table, Orientation orientation) {

        // this would be the preferred solution, but it doesn't work. it always gives back the vertical scrollbar
        //      return (ScrollBar) table.lookup(".scroll-bar:horizontal");
        //      
        // => we have to search all scrollbars and return the one with the proper orientation

        Set<Node> set = table.lookupAll(".scroll-bar");
        for( Node node: set) {
            ScrollBar bar = (ScrollBar) node;
            if( bar.getOrientation() == orientation) {
                return bar;
            }
        }

        return null;

    }

    /**
     * Data for primary table rows.
     */
    public static class Data {

        private final ObjectProperty<LocalDate> date;
        private final SimpleDoubleProperty value1;
        private final SimpleDoubleProperty value2;
        private final SimpleDoubleProperty value3;

        public Data( LocalDate date, double value1, double value2, double value3) {

            this.date = new SimpleObjectProperty<LocalDate>( date);

            this.value1 = new SimpleDoubleProperty( value1);
            this.value2 = new SimpleDoubleProperty( value2);
            this.value3 = new SimpleDoubleProperty( value3);
        }

        public final ObjectProperty<LocalDate> dateProperty() {
            return this.date;
        }
        public final LocalDate getDate() {
            return this.dateProperty().get();
        }
        public final void setDate(final LocalDate date) {
            this.dateProperty().set(date);
        }
        public final SimpleDoubleProperty value1Property() {
            return this.value1;
        }
        public final double getValue1() {
            return this.value1Property().get();
        }
        public final void setValue1(final double value1) {
            this.value1Property().set(value1);
        }
        public final SimpleDoubleProperty value2Property() {
            return this.value2;
        }
        public final double getValue2() {
            return this.value2Property().get();
        }
        public final void setValue2(final double value2) {
            this.value2Property().set(value2);
        }
        public final SimpleDoubleProperty value3Property() {
            return this.value3;
        }
        public final double getValue3() {
            return this.value3Property().get();
        }
        public final void setValue3(final double value3) {
            this.value3Property().set(value3);
        }


    }
} 

【问题讨论】:

  • “不起作用”是什么意思?你试过setManaged(false)吗?
  • 我使用左侧表格中的滚动条并将可见性设置为 false。它们仍然可见。在右表(见屏幕截图)上,我希望滚动条始终可见,因此我将可见性设置为 true。我试过setManaged,也没有用。换句话说:我不希望计算可见性。我想指定它们是否应该可见。我需要它作为汇总表:stackoverflow.com/questions/27884486/…
  • @James_D 你能看看这个问题吗?它有很多赞成票,但仍然没有答案。

标签: javafx scrollbar tableview


【解决方案1】:

将您的 TableView 对象添加到 StackPane 对象。滚动条将自动添加到表格中。它们也会自动调整大小。

我发现其他容器类型不适用于 TableView 对象。其中包括 ScrollPane 对象和 AnchorPane 对象。所以我想知道 HBox 是否可以添加到该列表中。

【讨论】:

    【解决方案2】:

    如果 TableView 仍然没有显示,它也可以工作。如果有一点数据,它就可以工作,因为

    private boolean computeBarVisiblity() {
        if (cells.isEmpty()) {
            // In case no cells are set yet, we assume no bars are needed
            needLengthBar = false;
            needBreadthBar = false;
            return true;
        }
    
        // ... other method code
    }
    

    在 VirtualFlow 代码中。但是你可以扩展这个例子。

            public static void alwaysShowVerticalScroll(final TableView view) {
        new Thread(() -> {
            while (true) {
                Set<Node> nodes = view.lookupAll(".scroll-bar");
                if (nodes.isEmpty()) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ignore) {
                    }
                    continue;
                }
                Node node = view.lookup(".virtual-flow");
                if (node == null) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ignore) {
                    }
                    continue;
                }
                final VirtualFlow flow = (VirtualFlow) node;
                for (Node n : nodes) {
                    if (n instanceof ScrollBar) {
                        final ScrollBar bar = (ScrollBar) n;
                        if (bar.getOrientation().equals(Orientation.VERTICAL)) {
                            bar.visibleProperty().addListener(l -> {
                                if (bar.isVisible()) {
                                    return;
                                }
                                Field f = getVirtualFlowField("needLengthBar");
                                Method m = getVirtualFlowMethod("updateViewportDimensions");
                                try {
                                    f.setBoolean(flow, true);
                                    m.invoke(flow);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                                bar.setVisible(true);
                            });
                            Platform.runLater(() -> {
                                bar.setVisible(true);
                            });
                            break;
                        }
                    }
                }
                break;
            }
        }).start();
    }
    
    private static Field getVirtualFlowField(String name) {
        Field field = null;
        try {
            field = VirtualFlow.class.getDeclaredField(name);
            field.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return field;
    }
    
    private static Method getVirtualFlowMethod(String name) {
        Method m = null;
        try {
            m = VirtualFlow.class.getDeclaredMethod(name);
            m.setAccessible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return m;
    }
    

    【讨论】:

    • alwaysShowVerticalScroll 方法也可以修改为采用 TreeTableView 而不仅仅是 TableView。
    【解决方案3】:

    CSS 解决方案是一个糟糕的解决方案,因为当您看到 TableMenuButton 可见时(“+”按钮可以切换列的可见性),它会弄乱视图。

    我的解决方案(仍然很讨厌)获取对滚动条的引用:

    TableView 类创建一个包含 VirtualFlow 的 TableViewSkin。 VirtualFlow 然后再次持有水平和垂直的 VirtualScrollBar。我正在覆盖 TableView 和 TableViewSkin。根据您的问题,您可能需要覆盖和更改更多内容。 (检查文件的来源..)。

    public class OneRowTableView<S> extends TableView<S>{
    
    @Override 
    protected Skin<?> createDefaultSkin() {
        return new OneRowTableViewSkin<S>(this);
    }
    public class OneRowTableViewSkin<T> extends TableViewSkin<T> {
        public OneRowTableViewSkin(final TableView<T> tableView) {
            super(tableView);
            VirtualScrollBar vBar = null;
            for(Node child : flow.getChildrenUnmodifiable()){
                if(child instanceof VirtualScrollBar){
                    if(((VirtualScrollBar)child).getOrientation() == Orientation.VERTICAL){
                        Log.d("Found the vertical scroll bar!");
                        vBar = (VirtualScrollBar) child;
                    }
                }
            }
            if(vBar == null) return;
            vBar.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler() {
    
                @Override
                public void handle(Event event) {
                    Log.d("Suppressing mouse pressing on the Vertical Virtual Scroll Bar");
                    event.consume();
                }
            });
        }
    }
    

    }

    然后您可以在 FXML 或代码中使用新的 TableView。

    我只是想禁用滚动,所以我还过滤了整个表格的滚动并消费了该事件。我的 CSS 来隐藏按钮:

    .table-view *.scroll-bar:vertical *.increment-button,
    .table-view *.scroll-bar:vertical *.decrement-button,
    .table-view *.scroll-bar:vertical *.increment-arrow, 
    .table-view *.scroll-bar:vertical *.decrement-arrow {
        -fx-background-color: null;
    }
    

    【讨论】:

      【解决方案4】:

      CSS

      这对我来说是唯一可行的解​​决方案,只是隐藏它

      .scroll-bar:vertical {
          -fx-opacity: 0;
          -fx-padding:-7;
      }
      

      【讨论】:

        【解决方案5】:

        在这种情况下,我将我的对象放入 ScrollPane 并使用其中的 hbarPolicy/vbarPolicy

        在 Fxml 中是这样的:

        <ScrollPane fx:id="yourScrollPane" hbarPolicy="NEVER">
        <ScrollPane fx:id="yourScrollPane" vbarPolicy="ALWAYS">
        

        在控制器中类似于:

        yourScrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
        yourScrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS);
        

        【讨论】:

        • 这不起作用
        • 我目前没有使用 JavaFx,所以找不到我的代码,它在哪里使用,但至少我记得它工作正常。 “不工作”是什么意思?你有错误还是什么?如果您在 ScrollPane 中没有看到滚动,则可能意味着您将它放在其他节点中,这会“阻止”它或类似的东西。您是否尝试在控制器中设置这些值?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-01-15
        • 2020-10-08
        • 1970-01-01
        • 1970-01-01
        • 2014-12-30
        • 2014-02-13
        • 1970-01-01
        相关资源
        最近更新 更多