Binding two tableviews together such that they scroll in sync

前端 未结 3 1216
甜味超标
甜味超标 2021-02-10 06:38

I want to bind two tableviews together such that they scroll in sync. How do I do that? I am unable to find out how to access the scrollbar of a tableview.

相关标签:
3条回答
  • 2021-02-10 07:15

    I've made a CSS hack to bind a Tableview with an external scrollbar. One scrollbar controls both tableviews.

    An overview of my idea:

    • Create two tableviews
    • Make one Vertical scrollbar. Let's call it myScrollbar in this example
    • Set the min and max of myScrollbar to size of min=0, max=TableView.Items.size()
    • When the value of myScrollbar changes then call both tableview's scrollTo(int) function
    • Disable the native vertical scrollbar of the tableview implemented with CSS.

    This will give you two tables, both controlled by one external scrollbar (myScrollbar).

    Here is the code to hide the scrollbar of a tableview using css:

    /* The main scrollbar **track** CSS class  */
    
    .mytableview .scroll-bar:vertical .track{
            -fx-padding:0px;
        -fx-background-color:transparent;
        -fx-border-color:transparent;
        -fx-background-radius: 0em;
        -fx-border-radius:2em;
    
    }
    
    /* The increment and decrement button CSS class of scrollbar */
    
    .mytableview .scroll-bar:vertical .increment-button ,
    .mytableview .scroll-bar:vertical .decrement-button {
    
        -fx-background-color:transparent;
        -fx-background-radius: 0em;
        -fx-padding:0 0 0 0;
    }
    
    .mytableview  .scroll-bar:vertical .increment-arrow,
    .mytableview  .scroll-bar:vertical  .decrement-arrow
    {
        -fx-shape: " "; 
        -fx-padding:0;        
    }
    
    /* The main scrollbar **thumb** CSS class which we drag every time (movable) */
    .mytableview .scroll-bar:vertical .thumb {
        -fx-background-color:transparent;
        -fx-background-insets: 0, 0, 0;
        -fx-background-radius: 2em;
        -fx-padding:0px;
    
    }
    

    Then we need to set how to scroll the tableview by using the scrollbar.

    scroll.setMax(100); //make sure the max is equal to the size of the table row data.
    scroll.setMin(0); 
    scroll.valueProperty().addListener(new ChangeListener(){
    
        @Override
        public void changed(ObservableValue ov, Number t, Number t1) {
            //Scroll your tableview according to the table row index
            table1.scrollTo(t1.intValue()); 
            table2.scrollTo(t1.intValue());
        }
    
    });
    

    http://blog.ngopal.com.np/2012/09/25/how-to-bind-vertical-scroll-in-multi-tableview/

    0 讨论(0)
  • 2021-02-10 07:21

    I don't think this is currently possible. TableViewSkin inherits from VirtualContainerBase which has a VirtualFlow field. The VirtualFlow object has two VirtualScrollBar fields, hbar and vbar which is what you're after. I can't see any way of getting to it though.

    Interestingly, there is also a private contentWidth field in TableView although this is private. I'm sure that the JFX team are being ultra cautious about opening up too much of the API which is understandable. You could ask to get the contentWidth field opened up as an int property as a feature requestion on the JFX JIRA or openjfx-dev mailing list.

    A stop gap measure would be to bind the selected item or index property of the table views selection model.

    0 讨论(0)
  • 2021-02-10 07:26

    The easiest way I've found to solve the problem is to bind the valueProperty of the visible an the hidden scrollbars.

    // Controller 
    @FXML private TableView<MyBean> tableLeft;
    @FXML private TableView<MyBean> tableRight;
    @FXML private ScrollBar scrollBar;
    
    
    @SuppressWarnings("rawtypes")
    private void bindScrollBars(TableView<?> tableView1, TableView<?> tableView2, 
                     ScrollBar scrollBar, Orientation orientation) {
    
        // Get the scrollbar of first table
        VirtualFlow vf = (VirtualFlow)tableView1.getChildrenUnmodifiable().get(1);
        ScrollBar scrollBar1 = null;
        for (final Node subNode: vf.getChildrenUnmodifiable()) {
            if (subNode instanceof ScrollBar && 
                    ((ScrollBar)subNode).getOrientation() == orientation) {
                scrollBar1 = (ScrollBar)subNode;
            }
         }
    
        // Get the scrollbar of second table
        vf = (VirtualFlow)tableView2.getChildrenUnmodifiable().get(1);
        ScrollBar scrollBar2 = null;
        for (final Node subNode: vf.getChildrenUnmodifiable()) {
            if (subNode instanceof ScrollBar && 
                    ((ScrollBar)subNode).getOrientation() == orientation) {
                scrollBar2 = (ScrollBar)subNode;
            }
         }
    
        // Set min/max of visible scrollbar to min/max of a table scrollbar
        scrollBar.setMin(scrollBar1.getMin());
        scrollBar.setMax(scrollBar1.getMax());
    
        // bind the hidden scrollbar valueProterty the visible scrollbar
        scrollBar.valueProperty().bindBidirectional(scrollBar1.valueProperty());
        scrollBar.valueProperty().bindBidirectional(scrollBar2.valueProperty());
    }
    
    /*
     * This method must be called in Application.start() after the stage is shown,
     * because the hidden scrollbars exist only when the tables are rendered
     */
    public void setScrollBarBinding() {
        bindScrollBars(this.tableLeft, this.tableRight, this.scrollBar, Orientation.VERTICAL);
    }
    

    Now you have to call the binding from Application after the stage is shown and the tables are rendered:

    // Application
        private MyController controller;
    
        @Override
        public void start(Stage primaryStage) {
            try {
                FXMLLoader fxmlLoader = new FXMLLoader(SalesApp.class.getResource("scene.fxml"));
                BorderPane root = (BorderPane) fxmlLoader.load();;
                Scene scene = new Scene(root);
                scene.getStylesheets().add(getClass().getResource("app.css").toExternalForm());
                primaryStage.setScene(scene);
                primaryStage.show();            
    controller = (MyController) fxmlLoader.getController();
                controller.setScrollBarBinding();
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    

    Now the tables should scroll synchronously via mouse, key, or scrollbar.

    Have fun, Olaf

    0 讨论(0)
提交回复
热议问题