TableView ScrollBar Policy
Asked Answered
T

5

20

Is there a way to change the policy of a ScrollBar in a TableView similar to a ScrollPane? I've only seen that the VirtualFlow of a TableView computes the visibility, but no possibility for manual interference.

I need the vertical scrollbar to be always visible and the horizontal never. Changing the visible state of the bars doesn't work.

Example:

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

Trimolecular answered 11/1, 2015 at 11:44 Comment(3)
What do you mean with "does not work"? Did you try setManaged(false)?Titanomachy
I take the scrollbars in the left table and set the visibility to false. They are still visible. And on the right table (see screenshot) I want the scrollbars to always be visible, so I set the visibility to true. I tried setManaged, that didn't work either. In other words: I don't want the visibility to be computed. I want to specify whether they should be visible or not. I need it for the summary table here: #27884986Trimolecular
@James_D Could you take a look at this question. It has many upvotes and still no answer.Intort
T
3

Add your TableView object to a StackPane object. Scroll bars will be added to the table automatically. They will also resize automatically.

I have found that other container types don't work well for TableView objects. Amongst those are ScrollPane objects and AnchorPane objects. So I wonder if HBox can be added to that list.

Trellas answered 19/6, 2015 at 2:29 Comment(0)
S
2

It also works if TableView is stil not showing. It olny works if there is a bit of data, because of

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
}

in VirtualFlow code. But you can extend this example.

        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;
}
Spear answered 4/8, 2016 at 16:31 Comment(1)
The alwaysShowVerticalScroll method can also be modified to take a TreeTableView instead of just a TableView.Hoopes
I
0

The CSS solution is a bad one, because it messes up the view when you have the TableMenuButton visible (the "+" button that makes toggling the visibility of the columns possible).

My solution (still quite nasty) to getting a reference to the scroll bars:

The TableView class creates a TableViewSkin that holds a VirtualFlow. The VirtualFlow then again holds the horizontal and vertical VirtualScrollBar. I'm overriding TableView and TableViewSkin. Depending on your problem you may need to override and change som more. (Check the source of the files..).

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

}

You can then use your new TableView in FXML or code.

I just wanted to disable scrolling, so I also filtered scrolling on the whole table and consumed the event. My CSS to hide the buttons:

.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;
}
Improvisatory answered 1/7, 2015 at 9:17 Comment(0)
R
-1

CSS

this is the only working solution for me, just hidde it

.scroll-bar:vertical {
    -fx-opacity: 0;
    -fx-padding:-7;
}
Republicanism answered 21/2, 2015 at 20:27 Comment(0)
G
-3

I such cases I putting my object into ScrollPane and work with hbarPolicy/vbarPolicy of it.

In Fxml it looks like:

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

In controller something like:

yourScrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
yourScrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS);
Gunwale answered 8/7, 2016 at 9:53 Comment(2)
<ScrollPane fx:id="yourScrollPane" hbarPolicy="NEVER"> <ScrollPane fx:id="yourScrollPane" vbarPolicy="ALWAYS"> this is not workingWingfooted
I'm not working with JavaFx currently, so can't find my code, where it was used, but at least by me it works fine as I remember. What do you mean with "not working"? Have you got error or what? If you don't see a scroll in your ScrollPane it could mean you have it inside other node, which "blokes" it or something like that. Have you tried to set these values in controller?Gunwale

© 2022 - 2024 — McMap. All rights reserved.