JavaFX: How to focus and select to the next cell w

2019-05-11 22:17发布

问题:

I have a ListView filled with cells and I'd like to scroll to the next cell to have it focused and selected.

My code works to scroll down but the scroll bar is going to far!

How can I reduce the scroll size to each cell, so I can see the focused cell? Is there a better way to implements this?

Here is my code:

documentListView.scrollTo(0);
documentListView.getSelectionModel().select(0);

int indexSize = documentListView.getItems().size();

for (Node node : documentListView.lookupAll(".scroll-bar")) {
    if (node instanceof ScrollBar) {
        final ScrollBar bar = (ScrollBar) node;
        bar.valueProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> value, Number oldValue, Number newValue) {
                int selectedIndex = documentListView.getSelectionModel().getSelectedIndex();

                if(selectedIndex <= indexSize && listScrollDown(oldValue.floatValue(), newValue.floatValue())) {
                    selectedIndex++;
                    documentListView.scrollTo(selectedIndex);
                    documentListView.getFocusModel().focus(selectedIndex);
                    documentListView.getSelectionModel().select(selectedIndex);
                } 
            }
        });
    }
}

Thanks for your help

回答1:

I've had to deal with this same requirement in some custom hardware. I ended up using the TableView VirtualFlow to handle the job. You have to control the scrolling as well as the selection when you do it but it ultimately gave me the granular control I needed for selection. Here's a bare bones custom TableView that should do what you're asking.

package custom.javafx.controls;

import com.sun.javafx.scene.control.skin.TableViewSkin;
import com.sun.javafx.scene.control.skin.VirtualFlow;
import javafx.beans.value.*;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.TableView;
import javafx.scene.input.ScrollEvent;

public class CustomTableView<T> extends TableView<T>
{
    private VirtualFlow flow = null;

    public CustomTableView()
    {
        super();

        /* Navigation listeners */
        skinProperty().addListener(skinPropertyChangeListener);
        getSelectionModel().selectedIndexProperty().addListener(selectedIndexChangeListener);
        addEventFilter(ScrollEvent.SCROLL, scrollEventFilter);  
    }

    /* You need this to prevent NPE's when trying to use the skin */
    private ChangeListener<Skin> skinPropertyChangeListener =
            new ChangeListener<Skin>()
    {
        @Override
        public void changed(ObservableValue<? extends Skin> ov,
            Skin t, Skin t1)
        {
            if (t1 == null) { return; }
            TableViewSkin skin = (TableViewSkin)t1;
            ObservableList<Node> kids = skin.getChildrenUnmodifiable();

            if (kids != null && !kids.isEmpty())
            {
                flow = (VirtualFlow)kids.get(1);
            }
        }
    };

    /* This handles the scrolling to ensure that a selection is visible */
    private ChangeListener<Number> selectedIndexChangeListener =
            new ChangeListener<Number>()
    {
        @Override
        public void changed(ObservableValue<? extends Number> ov,
            Number t, Number t1)
        {
            if (t1 == null || flow == null || t1.intValue() < 0) { return; }
            if (flow.getFirstVisibleCell() == null) { return; }

            int selected = t1.intValue();
            int first = flow.getFirstVisibleCell().getIndex();
            int last = flow.getLastVisibleCell().getIndex();

            if (selected <= first || selected >= last)
            {
                flow.show(selected);
            }
        }
    };

    /* Overrides the ScrollEvent handler to force selection rather than scrolling */
    private EventHandler<ScrollEvent> scrollEventFilter =
            new EventHandler<ScrollEvent>()
    {
        @Override
        public void handle(ScrollEvent t)
        {
            if (t.getDeltaY() < 0)
            {
                getSelectionModel().selectNext();
            }
            else
            {
                getSelectionModel().selectPrevious();
            }

            t.consume();
        }
    };
}

One caveat, I haven't tried this in testing or production but it's the same base implementation as my custom TableView with fewer bells and whistles.