(Java Swing) Как создать JTextPane с несколькими значками в одной строке?

Как говорится в вопросе, как мне успешно разместить несколько значков в одной строке текста в JTextPane? Каждый раз, когда я пытаюсь изменить значение actionText, результаты очень непредсказуемы. Например, это то, чего я пытаюсь достичь:

Классный текст для настольной игры, который я хотел бы иметь

Если я передаю только токены значков для создания значков (" ", например), они просто накладываются друг на друга (а может и нет, трудно сказать). Если я поставлю " , " или " и ", то в первой строке появится значок первого бойца, а во второй строке появится запятая и другой значок бойца.

В настоящее время я пытаюсь использовать решение, основанное на руководстве по Oracle для JTextPane: учебник по JTextPane. Вот кусок моего кода, который создает мои настраиваемые текстовые панели.

public final class GameTextPaneFactory {

private static final String[] ADVENTURER_TOKENS = {"<FIGHTER>", "<CLERIC>", "<WIZARD>", "<ROGUE>"};
private static final int TEXT_PANE_WIDTH = 30;

public static JTextPane createActionTextPane(String actionText) {
    ArrayList<String>[] wordsAndStyles = parseActionText(actionText);

    JTextPane actionTextPane = new JTextPane();
    StyledDocument doc = actionTextPane.getStyledDocument();
    addStylesToDocument(doc);

    try {
        for (int i=0; i < wordsAndStyles[0].size(); i++) {
            doc.insertString(doc.getLength(), wordsAndStyles[0].get(i),
                             doc.getStyle(wordsAndStyles[1].get(i)));
        }
    } catch (BadLocationException ble) {
        System.err.println("Couldn't insert initial text into text pane.");
    }

    actionTextPane.setEditable(false);
    return actionTextPane;      
}

private static void addStylesToDocument(StyledDocument doc) {
    // TODO add images (styles) here
    Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
    Style regular = doc.addStyle("regular", def);

    Style icons = doc.addStyle("fighterIcon", regular);
    StyleConstants.setAlignment(icons, StyleConstants.ALIGN_CENTER);
    ImageIcon fighterIcon = new ImageIcon("images/fighter_image.png", "fighter");
    StyleConstants.setIcon(icons, fighterIcon);

    icons = doc.addStyle("clericIcon", regular);
    StyleConstants.setAlignment(icons, StyleConstants.ALIGN_CENTER);
    ImageIcon clericIcon = new ImageIcon("images/cleric_image.png", "cleric");
    StyleConstants.setIcon(icons, clericIcon);

    icons = doc.addStyle("wizardIcon", regular);
    StyleConstants.setAlignment(icons, StyleConstants.ALIGN_CENTER);
    ImageIcon wizardIcon = new ImageIcon("images/wizard_image.png", "wizard");
    StyleConstants.setIcon(icons, wizardIcon);

    icons = doc.addStyle("rogueIcon", regular);
    StyleConstants.setAlignment(icons, StyleConstants.ALIGN_CENTER);
    ImageIcon rogueIcon = new ImageIcon("images/rogue_image.png", "rogue");
    StyleConstants.setIcon(icons, rogueIcon);
}

private static ArrayList<String>[] parseActionText(String text) {
    String[] words = text.split(" ");
    ArrayList<String> outputStrings = new ArrayList<String>();
    ArrayList<String> outputStyles = new ArrayList<String>();
    StringBuilder nextStringBuilder = new StringBuilder();
    int currentLineLength = TEXT_PANE_WIDTH;

    for(String word : words) {                      
        if(Arrays.asList(ADVENTURER_TOKENS).contains(word)) {
            if(nextStringBuilder.length() != 0) {
                outputStrings.add(nextStringBuilder.toString());
                outputStyles.add("regular");
                nextStringBuilder = new StringBuilder();
            }

            outputStrings.add(" "); // this is ignored, but cannot be empty
            switch(word) {
                case "<FIGHTER>":
                    outputStyles.add("fighterIcon");
                    break;
                case "<CLERIC>":
                    outputStyles.add("clericIcon");
                    break;
                case "<WIZARD>":
                    outputStyles.add("wizardIcon");
                    break;
                case "<ROGUE>":
                    outputStyles.add("rogueIcon");
                    break;
            }

            currentLineLength += 3; // an icon is about 3 characters in length
        } else {
            if(currentLineLength + word.length() + 1 > TEXT_PANE_WIDTH) {
                nextStringBuilder.append("\n");
                currentLineLength = 0;
            }

            nextStringBuilder.append(" " + word);
            currentLineLength += word.length() + 1;
        }
    }

    if(nextStringBuilder.length() != 0) {
        outputStrings.add(nextStringBuilder.toString());
        outputStyles.add("regular");
    }       
    @SuppressWarnings("unchecked")
    ArrayList<String>[] output = new ArrayList[2];
    output[0] = outputStrings;
    output[1] = outputStyles;
    return output;
}

}

Если у кого-то есть лучшее решение, я весь внимание. Спасибо!


person Billy M.    schedule 05.03.2014    source источник
comment
Можете ли вы добавить фотографию своего textpane?   -  person AJ.    schedule 05.03.2014


Ответы (2)


Попробуйте добавить

outputStrings.add(" "); // this is ignored, but cannot be empty
outputStyles.add("regular");

После каждого нового стиля "Авантюрист"

outputStrings.add(" "); // this is ignored, but cannot be empty
switch (word) {
    case "<FIGHTER>":
        outputStyles.add("fighterIcon");
        break;
    case "<CLERIC>":
        outputStyles.add("clericIcon");
        break;
    case "<WIZARD>":
        outputStyles.add("wizardIcon");
        break;
    case "<ROGUE>":
        outputStyles.add("rogueIcon");
        break;
}
outputStrings.add(" "); // this is ignored, but cannot be empty
outputStyles.add("regular");

Обновлено

Я немного поиграл, посмотрев, смогу ли я сделать форматирование немного лучше, это в основном то, что я придумал...

Приключения

Вместо использования стилей я обычно вставляю текст и изображения прямо в текстовую панель. Кажется, есть проблемы с одинаковыми стилями, установленными рядом друг с другом, так что вместо того, что происходит, они в основном объединены в одну запись в документе, это объясняет, почему у вас были проблемы с вашими стилями. По какой-то причине у меня была аналогичная проблема с иконками, поэтому мне приходилось каждый раз создавать новый экземпляр...

Это немного грубо и готово, но основная идея есть. В основном он использует API регулярных выражений для поиска всех совпадений «ключевых слов», вставляет текст перед ним, а затем вставляет специальный значок в зависимости от ключевого слова...

public static JTextPane createActionTextPane(String actionText) {
    JTextPane actionTextPane = new JTextPane();
    actionTextPane.setOpaque(false);

    StyledDocument doc = actionTextPane.getStyledDocument();

    Pattern pattern = Pattern.compile("<FIGHTER>|<CLERIC>|<GOLD>");
    Matcher matcher = pattern.matcher(actionText);
    int previousMatch = 0;
    while (matcher.find()) {

        int startIndex = matcher.start();
        int endIndex = matcher.end();
        String group = matcher.group();

        String subText = actionText.substring(previousMatch, startIndex);
        if (!subText.isEmpty()) {
            actionTextPane.replaceSelection(subText);
        } 
        switch (group) {
            case "<FIGHTER>":
                actionTextPane.insertIcon(new ImageIcon("fifight.gif"));
                break;
            case "<CLERIC>":
                actionTextPane.insertIcon(new ImageIcon("mage.gif"));
                break;
            case "<GOLD>":
                actionTextPane.insertIcon(new ImageIcon("Gold.png"));
                break;
        }

        previousMatch = endIndex;

    }
    String subText = actionText.substring(previousMatch);
    if (!subText.isEmpty()) {
        actionTextPane.replaceSelection(subText);
    }

    actionTextPane.setEditable(false);
    return actionTextPane;
}

Теперь, честно говоря, я не беспокоился о ширине линий или тому подобном, и вместо этого использовал возможности переноса JScrollPane и JTextComponent... но это на ваше усмотрение...

person MadProgrammer    schedule 05.03.2014
comment
Спасибо большое! Я выбрал самое первое решение, поскольку оно было таким же простым, как добавление этих двух строк кода. Игра, которую я пытаюсь сделать, не очень сложна, поэтому все получилось отлично. Если он когда-либо расширится, я, скорее всего, буду использовать что-то похожее на то, что вы разместили под обновленной записью. Ваше здоровье! - person Billy M.; 05.03.2014

Ознакомьтесь с разделом Автоматическая замена текста смайлов подходящими изображениями. Вероятно, это не совсем то, что вы ищете, но это должно дать вам некоторые идеи.

person camickr    schedule 05.03.2014