Показать некоторые вкладки вперед от выбранной вкладки в заголовке JavaFX 8 TabPane


Я пытаюсь изменить элемент управления TabPage в JavaFx 8, чтобы он показывал на видовом экране некоторые вкладки впереди (справа) текущего выбранного, или если выбранная вкладка находится в крайнем левом углу заголовка, она показывает соседние вкладки перед текущим.

Как это сейчас:

Поведение по умолчанию

Как я пытаюсь заставить его вести себя так:

Когда пользователь выбирает вкладку индекса X, заголовок панели вкладок показывает еще 2 или 3 соседних вкладки.

Когда пользователь выбирает вкладку индекса X, заголовок панели вкладок показывает еще 2 или 3 соседние вкладки.

Это то, что я пытался до сих пор, но безуспешно, по-видимому, приведенный ниже код слишком быстр, чтобы заставить поток интерфейса синхронизировать выбор вкладок вовремя (идея состояла в том, чтобы выбрать вкладку вперед, а затем вернуться к выбранной пользователем, заставляя заголовок показывать вкладки после выбранной вкладки):

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;

public class TabSelectionListener implements ChangeListener<Tab> {

    protected TabPane owner;
    protected boolean lock;
    protected int nextItems;

    TabSelectionListener(TabPane listenTo){
        owner = listenTo;
        lock = false;
        nextItems = 2;
    }

    TabSelectionListener(TabPane listenTo, int minimalInFront){
        owner = listenTo;
        lock = false;
        nextItems = minimalInFront;
    }

    @Override
    public void changed(ObservableValue<? extends Tab> list, Tab old, Tab newT) {
        int maxTab;
        int curTab;
        int i;

        // Locks this listener, because the selections owner.getSelectionModel().select(X)
        // will call this listener again, and we are calling those from here.
        if(!lock){
            lock = true;
            maxTab = owner.getTabs().size() - 1;
            curTab = owner.getSelectionModel().getSelectedIndex();

            for(i = 0; i < nextItems && curTab + i < maxTab; i++);
            owner.getSelectionModel().select(i); // int overload
            owner.getSelectionModel().select(newT);

            lock = false;
        }
    }
}

TabPane вызывает его для каждого выбора вкладки:

tabPane.getSelectionModel().selectedItemProperty().addListener(new TabSelectionListener(tabPane,3));

Я читал здесь некоторые темы, и мне кажется, что заголовок на самом деле является Стекпаном, и может быть получен путем выполнения:

StackPane region = (StackPane) tabPane.lookup(".headers-region");

Это работает, но после этого я понятия не имею, как получить доступ к свойствам, реализующим поведение по умолчанию.

Есть предложения?

Спасибо за чтение.

1 5

1 ответ:

Наконец - то я это сделал.

Оказалось, что класс, который я искал, был com.солнце.в JavaFX.сцена.контроль.кожа.TabPaneSkin @ jfxrt.jar, у него есть метод, чтобы сделать выбранную вкладку видимой, он запускается каждый раз, когда выбранная вкладка на панели табуляции не полностью видна, я переписал ее.

TabPaneSkin-это кожа TabPane по умолчанию, она применяет некоторые модели поведения к элементу управления TabPane.

/*
* Copyright (c) 2011, 2014, Oracle и/или ее филиалы. Все права защищены.
* ORACLE PROPRIETARY / CONFIDENTIAL. Использование осуществляется в соответствии с условиями лицензии.
*

Надеюсь, Оракул не будет возражать...

Выберите свой TabPane, и сделать...

tabPane.setSkin(new TabPaneNewSkin(tabPane));

... чтобы перезаписать TabPaneSkin Oracle по умолчанию с этим, я написал, что показывает соседние вкладки.

Исходный код Oracle для изменения положения вкладок, когда одна из них выбрана, чтобы сделать ее видимой:

    private void ensureSelectedTabIsVisible() {
            // work out the visible width of the tab header
            double tabPaneWidth = snapSize(isHorizontal() ? getSkinnable().getWidth() : getSkinnable().getHeight());
            double controlTabWidth = snapSize(controlButtons.getWidth());
            double visibleWidth = tabPaneWidth - controlTabWidth - firstTabIndent() - SPACER;

            // and get where the selected tab is in the header area
            double offset = 0.0;
            double selectedTabOffset = 0.0;
            double selectedTabWidth = 0.0;
            for (Node node : headersRegion.getChildren()) {
                TabHeaderSkin tabHeader = (TabHeaderSkin)node;

                double tabHeaderPrefWidth = snapSize(tabHeader.prefWidth(-1));

                if (selectedTab != null && selectedTab.equals(tabHeader.getTab())) {
                    selectedTabOffset = offset;
                    selectedTabWidth = tabHeaderPrefWidth;
                }
                offset += tabHeaderPrefWidth;
            }

            final double scrollOffset = getScrollOffset();
            final double selectedTabStartX = selectedTabOffset;
            final double selectedTabEndX = selectedTabOffset + selectedTabWidth;

            final double visibleAreaEndX = visibleWidth;

            if (selectedTabStartX < -scrollOffset) {
                setScrollOffset(-selectedTabStartX);
            } else if (selectedTabEndX > (visibleAreaEndX - scrollOffset)) {
                setScrollOffset(visibleAreaEndX - selectedTabEndX);
            }
        }

Код, который я записал в свой пользовательский скин TabPane:

    // This function was overwritten
    private void ensureSelectedTabIsVisible() {
        // work out the visible width of the tab header
        double tabPaneWidth = snapSize(isHorizontal() ? getSkinnable().getWidth() : getSkinnable().getHeight());
        double controlTabWidth = snapSize(controlButtons.getWidth());
        double visibleWidth = tabPaneWidth - controlTabWidth - firstTabIndent() - SPACER;


        // and get where the selected tab is in the header area
        double offset = 0.0;
        double selectedTabOffset = 0.0;
        double selectedTabWidth = 0.0;

        // OVERWRITE
        // Makes the nearby 3 tabs for each side of the selected tab visible.
        ObservableList<Node> headersRegionChildren = headersRegion.getChildren();
        boolean nextTabs = false;
        int nextTabsCount = 0;
        int current = 0;
        int numOfTabsToShowNext = 3;
        int numOfTabsToShowBefore = 3;
        double tabHeaderPrefWidth;       
        TabHeaderSkin tabHeader;

        for (Node node : headersRegionChildren) {
            tabHeader = (TabHeaderSkin)node;

            tabHeaderPrefWidth = snapSize(tabHeader.prefWidth(-1));

           if (selectedTab != null && selectedTab.equals(tabHeader.getTab())) {
                    selectedTabWidth = tabHeaderPrefWidth;

                // OVERWRITE: Finds the offset of the first tab in the limit numOfTabsToShowBefore before the selected one to be shown
                for(int i = current - 1; i >= 0 && numOfTabsToShowBefore > 1; i--, numOfTabsToShowBefore--){
                    tabHeader = (TabHeaderSkin)headersRegionChildren.get(i);
                    tabHeaderPrefWidth = snapSize(tabHeader.prefWidth(-1));
                    offset -= tabHeaderPrefWidth;
                    selectedTabWidth += tabHeaderPrefWidth;
                }

                selectedTabOffset = offset;
                // OVERWRITE: Sets the flag to start counting in the next 3 nearby tabs.  
                nextTabs = true;
            }
            // OVERWRITE: Sums the width of the next nearby tabs with the
            // width of the selected tab, so it will scroll enough to show
            // them too.
            if(nextTabs && nextTabsCount < numOfTabsToShowNext){
                selectedTabWidth += tabHeaderPrefWidth;
                nextTabsCount++;
            }else if(nextTabsCount == numOfTabsToShowNext){
                break;
            }

            offset += tabHeaderPrefWidth;
            current++;
        }
        // END OVERWRITE

        final double scrollOffset = getScrollOffset();
        final double selectedTabStartX = selectedTabOffset;
        final double selectedTabEndX = selectedTabOffset + selectedTabWidth;

        final double visibleAreaEndX = visibleWidth;

        if (selectedTabStartX < -scrollOffset) {
            setScrollOffset(-selectedTabStartX);
        } else if (selectedTabEndX > (visibleAreaEndX - scrollOffset)) {
            setScrollOffset(visibleAreaEndX - selectedTabEndX);
        }
    }

Код выше показывает 3 ближайших вкладки на каждая сторона от выбранной вкладки (если одна из них находится вне экрана и существует), для каждого выбора вкладки.

Так оно и было. ком.солнце.в JavaFX.сцена.контроль.кожа.TabPaneSkin не предполагалось расширять, почти каждый метод является частным, поэтому я сделал его копию и изменил только функцию, упомянутую выше, и переименовал ее в TabPaneNewSkin, и она находится в моем пакете.