TableView ScrollBar Policy

Есть ли способ изменить политику ScrollBar в TableView, подобном ScrollPane? Я только видел, что VirtualFlow TableView вычисляет видимость, но нет возможности ручного вмешательства.

Мне нужна вертикальная полоса прокрутки, которая всегда будет видна, а горизонтальная никогда. Изменение видимого состояния баров не работает.

Пример:

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);
        }


    }
} 

enter image description here

Ответ 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;
}

Ответ 3

Решение CSS плохое, потому что оно искажает представление, когда у вас видна кнопка TableMenuButton (кнопка "+", которая позволяет переключать видимость столбцов).

Мое решение (все еще довольно неприятное) для получения ссылки на полосы прокрутки:

Класс TableView создает TableViewSkin, который содержит VirtualFlow. Затем VirtualFlow снова удерживает горизонтальный и вертикальный VirtualScrollBar. Я переопределяю TableView и TableViewSkin. В зависимости от вашей проблемы вам может потребоваться переопределить и изменить som more. (Проверьте источник файлов..).

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();
            }
        });
    }
}

}

Затем вы можете использовать свой новый TableView в FXML или коде.

Я просто хотел отключить прокрутку, поэтому я также прокрутил прокрутку по всей таблице и использовал событие. Мой 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);