JavaFX: как переместить & ldquo; стрелку вниз & rdquo; в TitledPane быть справа

String a = new String("foo");
String b = new String("foo");
System.out.println(a == b); // prints false
System.out.println(a.equals(b)); // prints true

Убедитесь, что вы понимаете, почему. Это потому, что сравнение == сравнивает только ссылки; equals() метод сопоставляет содержимое по символу.

Когда вы вызываете new для a и b, каждый получает новую ссылку, указывающую на "foo" в таблице строк. Ссылки разные, но контент один и тот же.

1
задан Terry Dorsey 11 March 2019 в 03:42
поделиться

2 ответа

Это не совсем то же самое, визуально, но вы можете скрыть кнопку со стрелкой и создать рисунок, который действует как кнопка со стрелкой. TitledPane расширяет Labeled, так что вы можете управлять размещением графики относительно текста через свойство contentDisplay .

Во-первых, спрячьте кнопку со стрелкой в ​​таблице стилей:

.accordion .title > .arrow-button
{
    visibility: hidden;
}

В коде вы можете создать метку, действующую в качестве фальшивой кнопки, и установить ее в качестве графического элемента TitledPane. Вся строка заголовка чувствительна к мыши, поэтому интерактивный элемент управления (например, кнопка) не требуется.

Label collapseButton = new Label();
collapseButton.textProperty().bind(
    Bindings.when(titledPane.expandedProperty())
        .then("\u25bc").otherwise("\u25b6"));

titledPane.setGraphic(collapseButton);
titledPane.setContentDisplay(ContentDisplay.RIGHT);
0
ответ дан VGR 11 March 2019 в 03:42
поделиться

К сожалению, нет общедоступного API для перемещения стрелки вправо от TitledPane. Это не означает, что это невозможно, однако нам просто нужно динамически перевести стрелку, используя привязки. Чтобы остальная часть заголовка выглядела правильно, нам также нужно будет перевести текст и графику, если они есть, влево. Самый простой способ сделать все это - создать подкласс TitledPaneSkin и получить доступ к внутренним частям «области заголовка».

Вот пример реализации. Это позволяет вам позиционировать стрелку слева или справа с помощью CSS. Он также реагирует на изменение размеров, а также выравнивание и графические изменения.

package com.example;

import static javafx.css.StyleConverter.getEnumConverter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.css.CssMetaData;
import javafx.css.SimpleStyleableObjectProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.TitledPane;
import javafx.scene.control.skin.TitledPaneSkin;
import javafx.scene.layout.Region;
import javafx.scene.text.Text;

public class CustomTitledPaneSkin extends TitledPaneSkin {

    public enum ArrowSide {
        LEFT, RIGHT
    }

    /* ********************************************************
     *                                                        *
     * Properties                                             *
     *                                                        *
     **********************************************************/

    private final StyleableObjectProperty<ArrowSide> arrowSide
            = new SimpleStyleableObjectProperty<>(StyleableProperties.ARROW_SIDE, this, "arrowSide", ArrowSide.LEFT) {
        @Override protected void invalidated() {
            adjustTitleLayout();
        }
    };
    public final void setArrowSide(ArrowSide arrowSide) { this.arrowSide.set(arrowSide); }
    public final ArrowSide getArrowSide() { return arrowSide.get(); }
    public final ObjectProperty<ArrowSide> arrowSideProperty() { return arrowSide; }

    /* ********************************************************
     *                                                        *
     * Instance Fields                                        *
     *                                                        *
     **********************************************************/

    private final Region title;
    private final Region arrow;
    private final Text text;

    private DoubleBinding arrowTranslateBinding;
    private DoubleBinding textGraphicTranslateBinding;
    private Node graphic;

    /* ********************************************************
     *                                                        *
     * Constructors                                           *
     *                                                        *
     **********************************************************/

    public CustomTitledPaneSkin(TitledPane control) {
        super(control);
        title = (Region) Objects.requireNonNull(control.lookup(".title"));
        arrow = (Region) Objects.requireNonNull(title.lookup(".arrow-button"));
        text = (Text) Objects.requireNonNull(title.lookup(".text"));

        registerChangeListener(control.graphicProperty(), ov -> adjustTitleLayout());
    }

    /* ********************************************************
     *                                                        *
     * Skin Stuff                                             *
     *                                                        *
     **********************************************************/

    private void adjustTitleLayout() {
        clearBindings();
        if (getArrowSide() != ArrowSide.RIGHT) {
            // if arrow is on the left we don't need to translate anything
            return;
        }

        arrowTranslateBinding = Bindings.createDoubleBinding(() -> {
            double rightInset = title.getPadding().getRight();
            return title.getWidth() - arrow.getLayoutX() - arrow.getWidth() - rightInset;
        }, title.paddingProperty(), title.widthProperty(), arrow.widthProperty(), arrow.layoutXProperty());
        arrow.translateXProperty().bind(arrowTranslateBinding);

        textGraphicTranslateBinding = Bindings.createDoubleBinding(() -> {
            switch (getSkinnable().getAlignment()) {
                case TOP_CENTER:
                case CENTER:
                case BOTTOM_CENTER:
                case BASELINE_CENTER:
                    return 0.0;
                default:
                    return -(arrow.getWidth());
            }
        }, getSkinnable().alignmentProperty(), arrow.widthProperty());
        text.translateXProperty().bind(textGraphicTranslateBinding);

        graphic = getSkinnable().getGraphic();
        if (graphic != null) {
            graphic.translateXProperty().bind(textGraphicTranslateBinding);
        }
    }

    private void clearBindings() {
        if (arrowTranslateBinding != null) {
            arrow.translateXProperty().unbind();
            arrow.setTranslateX(0);
            arrowTranslateBinding.dispose();
            arrowTranslateBinding = null;
        }
        if (textGraphicTranslateBinding != null) {
            text.translateXProperty().unbind();
            text.setTranslateX(0);
            if (graphic != null) {
                graphic.translateXProperty().unbind();
                graphic.setTranslateX(0);
                graphic = null;
            }
            textGraphicTranslateBinding.dispose();
            textGraphicTranslateBinding = null;
        }
    }

    @Override
    public void dispose() {
        clearBindings();
        unregisterChangeListeners(getSkinnable().graphicProperty());
        super.dispose();
    }

    /* ********************************************************
     *                                                        *
     * Stylesheet Handling                                    *
     *                                                        *
     **********************************************************/

    public static List<CssMetaData<?, ?>> getClassCssMetaData() {
        return StyleableProperties.CSS_META_DATA;
    }

    @Override
    public List<CssMetaData<?, ?>> getCssMetaData() {
        return getClassCssMetaData();
    }

    private static class StyleableProperties {

        private static final CssMetaData<TitledPane, ArrowSide> ARROW_SIDE
                = new CssMetaData<>("-fx-arrow-side", getEnumConverter(ArrowSide.class), ArrowSide.LEFT) {

            @Override
            public boolean isSettable(TitledPane styleable) {
                Property<?> prop = (Property<?>) getStyleableProperty(styleable);
                return prop != null && !prop.isBound();
            }

            @Override
            public StyleableProperty<ArrowSide> getStyleableProperty(TitledPane styleable) {
                Skin<?> skin = styleable.getSkin();
                if (skin instanceof CustomTitledPaneSkin) {
                    return ((CustomTitledPaneSkin) skin).arrowSide;
                }
                return null;
            }

        };

        private static final List<CssMetaData<?, ?>> CSS_META_DATA;

        static {
            List<CssMetaData<?,?>> list = new ArrayList<>(TitledPane.getClassCssMetaData().size() + 1);
            list.addAll(TitledPaneSkin.getClassCssMetaData());
            list.add(ARROW_SIDE);
            CSS_META_DATA = Collections.unmodifiableList(list);
        }

    }

}

Затем вы можете применить этот скин ко всем TitledPane в вашем приложении из CSS, например, так:

.titled-pane {
    -fx-skin: "com.example.CustomTitledPaneSkin";
    -fx-arrow-side: right;
}

/*
 * The arrow button has some right padding that's added
 * by "modena.css". This simply puts the padding on the
 * left since the arrow is positioned on the right.
 */
.titled-pane > .title > .arrow-button {
    -fx-padding: 0.0em 0.0em 0.0em 0.583em;
}

Или вы можете настроить таргетинг только на определенные TitledPane с помощью добавления стиля. класс и использование указанного класса вместо .titled-pane.

Вышесказанное работает с JavaFX 11 и, вероятно, с JavaFX 10 и 9. Чтобы заставить его скомпилироваться на JavaFX 8, вам нужно изменить некоторые вещи:

  • Вместо этого импортировать com.sun.javafx.scene.control.skin.TitledPaneSkin.

    • Классы скинов были опубликованы в JavaFX 9.
  • Удалить вызовы на registerChangeListener(...) и unregisterChangeListeners(...). Я считаю правильным заменить их следующим:

    @Override
    protected void handleControlPropertyChange(String p) {
        super.handleControlPropertyChange(p);
        if ("GRAPHIC".equals(p)) {
            adjustTitleLayout();
        }
    }
    
  • Используйте new SimpleStyleableObjectProperty<ArrowSide>(...) {...} и new CssMetaData<TitledPane, ArrowSide>(...) {...}.

    • Вывод типа был улучшен в более поздних версиях Java.
  • Использовать (StyleConverter<?, ArrowSide>) getEnumConverter(ArrowSide.class).

    • В общей подписи getEnumConverter была ошибка, исправленная в более поздней версии. Использование броска работает вокруг проблемы. Вы можете @SuppressWarnings("unchecked") сыграть.

Проблема: Даже с учетом вышеуказанных изменений в JavaFX 8 возникает проблема - стрелка переводится только после фокусировки TitledPane. Похоже, что это не проблема с приведенным выше кодом, поскольку даже изменение свойства alignment не приводит к обновлению TitledPane до тех пор, пока оно не будет сфокусировано (даже если не используется вышеуказанный скин, а скорее только скин по умолчанию) , Мне не удалось найти решение этой проблемы (при использовании пользовательского скина), но, возможно, вы или кто-то еще можете. Я использовал Java 1.8.0_202 при тестировании на JavaFX 8.


Если вы не хотите использовать пользовательский скин или используете JavaFX 8 (это приведет к переводу стрелки без необходимости сначала фокусировать TitledPane), вы можете извлечь необходимые код, с некоторыми изменениями, в служебный метод:

public static void putArrowOnRight(TitledPane pane) {
    Region title = (Region) pane.lookup(".title");
    Region arrow = (Region) title.lookup(".arrow-button");
    Text text = (Text) title.lookup(".text");

    arrow.translateXProperty().bind(Bindings.createDoubleBinding(() -> {
        double rightInset = title.getPadding().getRight();
        return title.getWidth() - arrow.getLayoutX() - arrow.getWidth() - rightInset;
    }, title.paddingProperty(), title.widthProperty(), arrow.widthProperty(), arrow.layoutXProperty()));
    arrow.setStyle("-fx-padding: 0.0em 0.0em 0.0em 0.583em;");

    DoubleBinding textGraphicBinding = Bindings.createDoubleBinding(() -> {
        switch (pane.getAlignment()) {
            case TOP_CENTER:
            case CENTER:
            case BOTTOM_CENTER:
            case BASELINE_CENTER:
                return 0.0;
            default:
                return -(arrow.getWidth());
        }
    }, arrow.widthProperty(), pane.alignmentProperty());
    text.translateXProperty().bind(textGraphicBinding);

    pane.graphicProperty().addListener((observable, oldGraphic, newGraphic) -> {
        if (oldGraphic != null) {
            oldGraphic.translateXProperty().unbind();
            oldGraphic.setTranslateX(0);
        }
        if (newGraphic != null) {
            newGraphic.translateXProperty().bind(textGraphicBinding);
        }
    });
    if (pane.getGraphic() != null) {
        pane.getGraphic().translateXProperty().bind(textGraphicBinding);
    }
}

Примечание: хотя при этом стрелка справа помещается без необходимости сначала фокусировать TitledPane, TitledPane все еще страдает от проблема отмечена выше. Например, изменение свойства alignment не обновляет TitledPane, пока оно не будет сфокусировано. Я предполагаю, что это просто ошибка в JavaFX 8.

Этот способ работы не так «прост», как скин-подход, и требует двух вещей:

  1. TitledPane должен использовать значение по умолчанию TitledPaneSkin.
  2. TitledPane должно должно отображаться в Window (окно было , показывающее ) до вызова метода утилиты. [1176 ]

    • Из-за ленивой природы элементов управления JavaFX обложка и связанные с ней узлы не будут созданы, пока элемент управления не будет отображен в окне. Вызов вспомогательного метода до отображения элемента управления приведет к выбрасыванию NullPointerException, так как вызовы lookup вернут null.
    • При использовании FXML обратите внимание, что метод initialize вызывается во время вызова FXMLLoader.load (любой из перегрузок). Это означает, что при нормальных обстоятельствах созданные узлы еще не могут быть частью Scene, не говоря уже о показе Window. Вы должны подождать, пока сначала не отобразится TitledPane, затем , а затем вызвать служебный метод.

      Ожидание отображения TitledPane может быть достигнуто путем прослушивания свойства Node.scene, свойства Scene.window и свойства Window.showing (или вы можете прослушивать события WindowEvent.WINDOW_SHOWN). Однако, , если вы немедленно поместите загруженные узлы в показ Window , то вы можете отказаться от наблюдения за свойствами; вызовите служебный метод внутри вызова Platform.runLater изнутри initialize.

При использовании подхода с использованием скинов вся проблема ожидания окна показа исключается.


Обычное предупреждение. Этот ответ опирается на внутреннюю структуру TitledPane, которая может измениться в будущем выпуске. Будьте осторожны при изменении версий JavaFX. Я только (несколько) протестировал это на JavaFX 8u202 и JavaFX 11.0.2.

0
ответ дан Slaw 11 March 2019 в 03:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: