JavaFX tags not wrapping around

Joined
Jul 6, 2023
Messages
2
Reaction score
0
Hey all I need some assistance in getting my tag app working as it should. Currently it displays values like this:
Jf-HTq8921.png


However, I am wanting it to wrap the tags instead of continuing to go horizontal. I changed all the HBox's to FlowPane's but that made it look all weird and didn't work well. I also tried doing VBox's but it still messes it up.

What I am wanting it to look like is this:
Hl-ICq-Qy-O2.png


The current code:
Java:
package application;
    
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;

public class Main3 extends Application{
    Color txtColor;
    
    @Override
    public void start(Stage primaryStage) throws Exception {
         primaryStage.setTitle("TagInputFx Demo");

         VBox root = new VBox();
         AutocompleteMultiSelectionBox tagInput = new AutocompleteMultiSelectionBox();         
         ObservableSet<String> sug = FXCollections.observableSet();         
         String[] listOfDBCalls = "Integrated Security=, user=, password=, pwd=, Database=, Encrypt=, Trusted_Connection=, Persist Security Info=, TrustServerCertificate=, User ID=, Initial Catalog=, AttachDbFileName=, Failover Partner=, Asynchronous Processing=, User Instance=, Packet Size=, Column Encryption Setting=, Network Library=, MultipleActiveResultSets=, Data Source=, Server=, Enclave Attestation Url=, Provider=, UID=, Connect Timeout=, Driver=, MARS_Connection=, Proxy Password=, Proxy User Id=, Host=, Pooling=, Max Pool Size=, Connection Lifetime=, Incr Pool Size=, Decr Pool Size=, DBA Privilege=, Load Balancing=, Dbq=, DistribTX=, OledbKey1=, OledbKey2=, ConnectString=, Version=, New=, UseUTF16Encoding=, Legacy Format=, Read Only=, DateTimeFormat=, BinaryGUID=, Cache Size=, Page Size=, Enlist=, Max Page Count=, Journal Mode=, Synchronous=, Compress=, UTF8Encoding=, Timeout=, NoTXN=, SyncPragma=, StepAPI=, LongNames=, Port=, location=, sslmode=, Protocol=, SslMode=, MinPoolSize=, MaxPoolSize=".split(",");
        
         for (String call : listOfDBCalls) {
             sug.add(call);
         }         
        
         tagInput.setSuggestions(sug);
         tagInput.setTextColor(Color.BLACK);         
        
         Text header = new Text("Tags:");
         root.getChildren().addAll(header, tagInput);
         Scene scene = new Scene(root, 600, 250);
         primaryStage.setScene(scene);
         primaryStage.show();       
    }
    

    public static void main3(String[] args) {
        launch(args);
    }
    
    public class AutocompleteMultiSelectionBox extends HBox {
        private final ObservableList<String> tags;
        private final ObservableSet<String> suggestions;
        private ContextMenu entriesPopup;
        private static final int MAX_ENTRIES = 15;
        private final TextField inputTextField;

        public AutocompleteMultiSelectionBox() {
            getStyleClass().setAll("tag-bar");
            getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            
            tags = FXCollections.observableArrayList();
            suggestions = FXCollections.observableSet();
            inputTextField = new TextField();
            
            this.entriesPopup = new ContextMenu();
            
            setListner();
            
            inputTextField.setOnKeyPressed(event -> {
                if (event.getCode().equals(KeyCode.BACK_SPACE) && !tags.isEmpty() && inputTextField.getText().isEmpty()) {
                    String last = tags.get(tags.size() - 1);
                    String orgTag = last.split("=")[0] + "=";
                        
                    if (orgTag.length() > 2) {
                        suggestions.add(orgTag);
                        tags.remove(last);
                    }
                } else if (event.getCode().toString() == "ENTER" || event.getCode().toString() == "TAB" && !inputTextField.getText().isEmpty()) {
                    String newTag = inputTextField.getText();
                    String orgTag = newTag.split("=")[0] + "=";

                    if (orgTag.length() > 2) {
                        suggestions.add(newTag);
                        tags.add(newTag);
                        inputTextField.setText("");
                        suggestions.remove(orgTag);
                        suggestions.remove(newTag);
                    }
                } else if (event.getCode().toString() == "ESCAPE") {
                    inputTextField.clear();
                } else if (event.getCode().toString() == "DOWN" || event.getCode().toString() == "UP") {
                    entriesPopup.getSkin().getNode().lookup(".menu-item").requestFocus();
                }
                
                System.out.println(event.getCode().toString());                   
            });

            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 TextFlow buildTextFlow(String text, String filter) {
            int filterIndex = text.toLowerCase().indexOf(filter.toLowerCase());
            Text textBefore = new Text(text.substring(0, filterIndex));
            Text textAfter = new Text(text.substring(filterIndex + filter.length()));
            Text textFilter = new Text(text.substring(filterIndex, filterIndex + filter.length()));
            
            textFilter.setFill(Color.ORANGE);
            textFilter.setFont(Font.font("Helvetica", FontWeight.BOLD, 12));
            
            return new TextFlow(textBefore, textFilter, textAfter);
        }

        private void setListner() {
            inputTextField.textProperty().addListener((observable, oldValue, newValue) -> {
                if (newValue.isEmpty()) {
                    entriesPopup.hide();
                } else {
                    List<String> filteredEntries = suggestions
                            .stream()
                            .filter(e -> e.toLowerCase().contains(newValue.toLowerCase()))
                            .collect(Collectors.toList());

                    if (!filteredEntries.isEmpty()) {
                        populatePopup(filteredEntries, newValue);
                        
                        if (!entriesPopup.isShowing()) {
                            entriesPopup.show(this, Side.BOTTOM, 0, 0);
                        }
                    } else {
                        entriesPopup.hide();
                    }
                }
            });

            focusedProperty().addListener((observableValue, oldValue, newValue) -> entriesPopup.hide());
        }

        private void populatePopup(List<String> searchResult, String searchRequest) {
            List<CustomMenuItem> menuItems = new LinkedList<>();
            
            searchResult.stream()
                    .limit(MAX_ENTRIES)
                    .forEach(result -> {
                        TextFlow textFlow = buildTextFlow(result, searchRequest);
                        textFlow.prefWidthProperty().bind(AutocompleteMultiSelectionBox.this.widthProperty());
                        CustomMenuItem item = new CustomMenuItem(textFlow, true);
                        
                        menuItems.add(item);

                        item.setOnAction(actionEvent -> {
                            if (result.endsWith("=")) {
                                //Dont close it yet.. keep typing
                                inputTextField.setText(result);
                                inputTextField.requestFocus();
                                inputTextField.end();
                            } else {
                                //It has values on both sides of the =
                                //so lets add it to the tag list
                                tags.add(result);
                                suggestions.remove(result);
                                inputTextField.clear();
                                entriesPopup.hide();
                            }
                        });
                    });

            entriesPopup.getItems().clear();
            entriesPopup.getItems().addAll(menuItems);
        }

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

        public final ObservableSet<String> getSuggestions() {
            return suggestions;
        }
        
        public final void setTextColor(Color c) {
            txtColor = c;
        }

        public final void setSuggestions(ObservableSet<String> suggestions) {
            this.suggestions.clear();
            this.suggestions.addAll(suggestions);
        }

        private class Tag extends HBox {
            Tag(String tag) {
                Button removeButton = new Button();
                Image img = new Image(getClass().getResource("delete.png").toExternalForm());
                ImageView view = new ImageView(img);
                
                view.setFitHeight(16);
                view.setPreserveRatio(true);
                
                getStyleClass().add("tag");
                
                removeButton.setBackground(null);
                removeButton.setOnAction(event -> {
                    tags.remove(tag);
                    suggestions.add(tag);
                    inputTextField.requestFocus();
                });

                Text text = new Text(tag);
                
                text.setFill(txtColor);
                text.setFont(Font.font(text.getFont().getFamily(), FontWeight.BOLD, text.getFont().getSize()));

                setAlignment(Pos.CENTER);
                setSpacing(2);
                setPadding(new Insets(0, 0, 0, 5));

                removeButton.setGraphic(view);
                
                getChildren().addAll(text, removeButton);
            }
        }
    }
}
Is there a way of doing this with the hbox?

Using the code for the FlowPane this is what the output looks like:
cju-DUFg-Y3.png
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,039
Messages
2,570,376
Members
47,031
Latest member
AndreBucki

Latest Threads

Top