Создать панель тегов в JavaFX

Демонстрация ответа: (ответил 29 мая в 3:10)

**10/7/2016** you can find the code on GitHub

Образцы тегов

Фактический вопрос до ответа: (спросил 22 мая в 19:53)

Название может быть не слишком большим, но я хочу сделать это в JavaFX:

Примеры

YouTube

Пример тегов YouTube

StackOverFlow (имеет и автозаполнение):

Пример тегов StackOverflow

Вопрос: Мне не нужно писать мне код для этого. Вместо этого я хочу знать, как я могу добиться этого, используя JavaFX и некоторые идеи.

Ответ 1

Для тегов вы можете использовать пользовательский стиль HBox, содержащий Text (имя тега) node a Button (кнопка удаления (X)). Играя вокруг фона и границы, вы можете добиться желаемого вида тегов.

Обработчик onAction кнопки должен удалить тег из родителя...

Для всей панели тегов вы можете использовать другой HBox. Используйте подходящую рамку для правильного просмотра. В дополнение к тегам добавьте TextField без фона в качестве последнего элемента и установите для свойства Hgrow значение TextField на Priotity.ALWAYS, чтобы покрыть остальную часть доступного пространства.

Обработчик onAction этого TextField добавляет новые теги и очищает содержимое TextField.

Вы можете, например, используйте функции автозаполнения ControlsFX с помощью TextField или реализуйте его самостоятельно для пользовательского вида...

public class TagBar extends HBox {

    private final ObservableList<String> tags;
    private final TextField inputTextField;

    public ObservableList<String> getTags() {
        return tags;
    }

    public TagBar() {
        getStyleClass().setAll("tag-bar");
        getStylesheets().add(getClass().getResource("style.css").toExternalForm());
        tags = FXCollections.observableArrayList();
        inputTextField = new TextField();
        inputTextField.setOnAction(evt -> {
            String text = inputTextField.getText();
            if (!text.isEmpty() && !tags.contains(text)) {
                tags.add(text);
                inputTextField.clear();
            }
        });

        inputTextField.prefHeightProperty().bind(this.heightProperty());
        HBox.setHgrow(inputTextField, Priority.ALWAYS);
        inputTextField.setBackground(null);

        tags.addListener((ListChangeListener.Change<? extends String> change) -> {
            while (change.next()) {
                if (change.wasPermutated()) {
                    ArrayList<Node> newSublist = new ArrayList<>(change.getTo() - change.getFrom());
                    for (int i = change.getFrom(), end = change.getTo(); i < end; i++) {
                        newSublist.add(null);
                    }
                    for (int i = change.getFrom(), end = change.getTo(); i < end; i++) {
                        newSublist.set(change.getPermutation(i), getChildren().get(i));
                    }
                    getChildren().subList(change.getFrom(), change.getTo()).clear();
                    getChildren().addAll(change.getFrom(), newSublist);
                } else {
                    if (change.wasRemoved()) {
                        getChildren().subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear();
                    }
                    if (change.wasAdded()) {
                        getChildren().addAll(change.getFrom(), change.getAddedSubList().stream().map(Tag::new).collect(Collectors.toList()));
                    }
                }
            }
        });
        getChildren().add(inputTextField);
    }

    private class Tag extends HBox {

        public Tag(String tag) {
            getStyleClass().setAll("tag");
            Button removeButton = new Button("X");
            removeButton.setOnAction((evt) -> tags.remove(tag));
            Text text = new Text(tag);
            HBox.setMargin(text, new Insets(0, 0, 0, 5));
            getChildren().addAll(text, removeButton);
        }
    }

}

style.css

.tag-bar {
    -fx-border-color: blue;
    -fx-spacing: 3;
    -fx-padding: 3;
    -fx-max-height: 30;
}

.tag-bar .tag {
    -fx-background-color: lightblue;
    -fx-alignment: center;
}

.tag-bar .tag .button {
    -fx-background-color: transparent;
}
@Override
public void start(Stage primaryStage) {
    Button btn = new Button("Sort");

    StackPane.setAlignment(btn, Pos.BOTTOM_CENTER);

    TagBar tagBar = new TagBar();

    btn.setOnAction((ActionEvent event) -> {
        FXCollections.sort(tagBar.getTags());
    });

    Button btn2 = new Button("add \"42\"");
    btn2.setOnAction(evt -> {
        if (!tagBar.getTags().contains("42")) {
            tagBar.getTags().add("42");
        }
    });

    VBox root = new VBox();
    root.getChildren().addAll(tagBar, btn, btn2);
    root.setPrefSize(300, 400);

    Scene scene = new Scene(root);

    primaryStage.setScene(scene);
    primaryStage.show();
}

Ответ 2

Простая реализация этого кода!

import ....

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{

        BorderPane root = new BorderPane();
        HBox tagsPane = new HBox(10);
        tagsPane.setStyle("-fx-border-color: #F1F1F1;" +
                "          -fx-border-width: 1px;" +
                "          -fx-border-radius: 10;" +
                "          -fx-border-insets: 5");
        root.setBottom(tagsPane);

        TextField textField = new TextField();
        textField.setPromptText("Tag name - ENTER to add");
        textField.setOnKeyPressed(event -> {
            if (event.getCode() == KeyCode.ENTER) {
                tagButton(tagsPane, textField.getText());
                textField.clear();
            }
        });

        root.setTop(textField);

        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 450, 275));
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }

    //little image as 15x15 for example
    Image toUse = new Image("sample/delete.png");

    //box is the pane where this buttons will be placed
    public void tagButton(HBox box,String tag){
        ImageView closeImg = new ImageView(toUse);
        Button result = new Button(tag,closeImg);
        result.setPrefHeight(20);
        result.setContentDisplay(ContentDisplay.RIGHT);

        result.setOnAction(event -> box.getChildren().remove(result));
        box.getChildren().add(result);
}

}

введите описание изображения здесь

Также, если вам нужны разные события для клика по тегу и нажмите "X", вы можете реализовать tagButton следующим образом:

public void tagButton(HBox box,String tag){
    ImageView closeImg = new ImageView(toUse);
    HBox button = new HBox();
    button.setStyle("-fx-padding:4;" +
            "        -fx-border-width: 2;" +
            "        -fx-border-color: black;" +
            "        -fx-border-radius: 4;" +
            "        -fx-background-color: f1f1f1;" +
            "        -fx-border-insets: 5;");
    button.setPrefHeight(20);
    button.getChildren().addAll(new Label(tag),closeImg);

    closeImg.setOnMouseClicked(event -> 
            box.getChildren().remove(button)
    );
    button.setOnMouseClicked(event -> {
        //doSomethig
    });

    box.getChildren().add(button);
}

Ответ 3

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

public class CloseTag extends HBox implements Comparable<CloseTag> {

    private Label     label;
    private Label     closeIcon;

    public CloseTag(String text) {
        setStyle("-fx-padding:8;");

        Text icon = GlyphsDude.createIcon(FontAwesomeIcon.TIMES_CIRCLE);
        closeIcon = new Label(null, icon);

        label = new Label(text, new StackPane(closeIcon));
        label.setContentDisplay(ContentDisplay.RIGHT);
        getChildren().add(label);
    }

    public void setOnCloseAction(EventHandler<? super MouseEvent> action) {
        closeIcon.setOnMouseClicked(action);
    }

    public String getText() {
        return label.getText();
    }

    @Override
    public int compareTo(CloseTag other) {
        return getText().compareTo(other.getText());
    }
}



public class TagPane extends FlowPane {

    private TextField textField;

    public TagPane() {
        setStyle("-fx-padding:8;" + "-fx-hgap:10;");
        setOnMouseClicked(evt -> onMouseClickedd(evt));

        textField = new TextField();
        textField.setOnKeyPressed(evt -> onKeyPressed(evt, textField));
    }

    private void onMouseClickedd(MouseEvent mouseEvent) {
        if (mouseEvent.getTarget() != this || textField.getParent() != null ) {
            return;
        }

        getChildren().add(textField);
        textField.requestFocus();
    }

    private void onKeyPressed(KeyEvent evt, TextField textField) {
        if (evt.getCode() == KeyCode.ENTER || evt.getCode() == KeyCode.TAB) {
            createTag(textField.getText());
            textField.clear();
        }
    }

    private void createTag(String text) {
        CloseTag tag = new CloseTag(text);
        tag.setOnCloseAction(evt -> removeTag(tag));
        getChildren().remove(textField);
        getChildren().add(tag);
    }

    private void removeTag(CloseTag tag) {
        getChildren().remove(tag);
    }

} 

Ответ 4

Это тоже моя версия

введите описание изображения здесь

введите описание изображения здесь

введите описание изображения здесь

Весь основной класс

его почему-то давно.

Но подвести итог. Вам нужно

1: FlowPane для контейнера, и вам не нужно беспокоиться об обертывании, оно будет обертываться как вертикально, так и горизонтально.

2: Label, конечно, для вашего текста, который имеет GraphicProperty

3: Path - ну, вы можете использовать Button и добавить к нему Shape или Image, но это будет много узлов, поэтому я использовал Path, и я нарисовал X красная кнопка.

Остальное - стиль вашего предпочтительного цвета

ИЗМЕНИТЬ что-то вроде этого?

введите описание изображения здесь

вы можете настроить его для получения этого вывода

setFont(Font.font("Serif Regular", FontWeight.SEMI_BOLD,12));

используйте эту строку на TextField