在 JTextPane 中对齐和内联组件(或图标)

Bla*_*nds 5 java swing jtextpane

我正在开发一个 Java 应用程序,除其他外,它应该在文本字段中显示 Magic: The Gathering 卡的详细信息(因为我使用的是 Swing,所以当前是一个JTextPane)。

这些细节包含文本和小图标,其中一些图标与文本内嵌(因此文本围绕它们流动),而根据我的设计,一些图标与同一行中的一些左对齐文本右对齐。

我从另一个应用程序中获取了一张图像,该应用程序的设计与我正在开发的应用程序非常相似(尽管不是使用 Java):

在此输入图像描述

它基本上应该是这样的。

现在,出于对一切的热爱,我无法让它在JTextPane.

我开始尝试使用 CSS 来做到这一点,但发现JEditorPane和 子类不支持“float”属性,所以我尝试使用 PaneStyledDocument来代替。

我一开始没有让第一部分工作(顶部的图标和右对齐文本总是忽略它们的对齐方式,并直接放置在行中左对齐文本之后),直到我发现这个问题

建议使用以下代码行:

pane.setEditorKit(new HTMLEditorKit());
Run Code Online (Sandbox Code Playgroud)

这确实解决了我的第一个问题。

但现在我陷入了第二部分,使第二部分中的这些图标与文本内联。这是我目前得到的:

在此输入图像描述

我发现,出于某种原因,当您JTextPane使用带有上面代码行的编辑器套件切换到 html 模式时,插入组件会变得完全疯狂。

顶部的图标(实际上我已将其合并为单个图像)和下面文本中的图标(未合并)都位于 的内部JLabels,但如果我将它们添加为图像或添加到 的内部并不重要JLabels。图像或标签绝对不会比你在那里看到的大,我根本不知道额外的空白来自哪里。

我发现了这个问题,答案表明这是某种错误,或者只是 .html 模式的奇怪行为JEditorPane

如果我再次删除上面的代码行,我最终会遇到原来的问题:

在此输入图像描述

根据图标在文本中的具体位置,我会得到各种不同的奇怪结果。我在下面整理了更多示例图片:

在此输入图像描述

那么,我该如何解决这个问题呢?除了这部分之外,这JTextPane对我来说工作得很好,但我可能会使用其他解决方案,只要最终结果看起来仍然相同。请记住,我可能想在其中添加一些其他组件(如 Button),因此如果可能的话,我想坚持使用 Swing 原生的组件。

用户将无法编辑 TextPane 的内容,但我想稍后添加一个选项,以便一键复制所有内容(所以我宁愿保留文本区域)。

下面,我整理了一个(不是那么简单的)工作示例供您使用:

(编辑:现在更新了底部的代码!旧代码仍然存在于以下链接下。)

http://pastebin.com/RwAdPCzb

我正在使用的图标如下。您需要重命名它们并更改代码中的路径。

在此输入图像描述
在此输入图像描述
在此输入图像描述

需要注意的一些事项:

  • 一开始,我使用“Style”类设置文本样式,如 Oracle 网站上的“如何使用编辑器窗格和文本窗格”教程(TextSamplerDemo.java,供您参考)中所述。这样,我什至无法在顶部完成右对齐部分。奇怪的是,当我使用“SimpleAttributeSet”类进行样式设计时,即使使用完全相同的设置,它也能工作。
  • 我为文本和包含图标的标签尝试了不同的对齐选项。无论我使用什么选项,都没有明显的差异。

更新1:

在 Sharcoux 的回答之后,我编辑了代码,在实际的 JTextPane 上方有 2 个 JLabels,其中包含应该具有不同对齐方式的两行(左对齐部分和右对齐部分)。JTextPane 现在不再使用 HTMLEditorKit,我使用 insertIcon() 将图标插入文本中。

这样,图标就可以(几乎)正确插入了!
图片在这里:
在此输入图像描述

不过,有两点我还是不太满意:

首先:
我需要将所有内容放入 JScrollPane 中,因为 TextPane 中的文本在我的实际应用程序中要长得多。由于我现在拥有三个组件而不仅仅是 TextPane,因此我需要将所有内容放入 JPanel 中,然后将其放入 ScrollPane 中。
但是,如果您这样做,JTextPane 不知道它的宽度不应再超过 JScrollPane 的宽度。它会停止文本换行,并且会变得与整个文本一样大。
我为此提出了一个新问题,因为我觉得这是 Swing 的一个基本问题,值得单独提出一个问题。如果您想提供帮助,请点击以下链接:
JPanel 内 JScrollPane 内的 JTextComponent

第二:
这可能是不可能的,但我想我还是会问。当您以这种方式添加图标时,图标与文本具有相同的基线。有什么办法可以把它们移低一点吗?2-3 像素,也许吧?这样他们会更好地与文本对齐。下面两张图。
现在看起来是这样的:
在此输入图像描述
这就是我希望它看起来的样子:
在此输入图像描述

也许我可以子类化并覆盖 JTextPane 的某些部分,以将其上渲染的所有图标向下移动设定的像素量,或者类似的东西?

作为参考,这也是我的新的、更新的代码。如果您还想查看的话,我用pastebin.com 链接替换了上面的旧链接。


更新2:

我的第一个问题已经解决了!我也更新了下面的代码来反映这一点。

我的第二个问题仍然成立!

这是新代码:

import java.awt.EventQueue;
import java.awt.Graphics2D;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.JTextPane;
import javax.swing.JViewport;
 
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import javax.swing.ScrollPaneConstants;
 
public class MinimalExample extends JFrame {
 
    private JPanel contentPane;
    private JScrollPane scrollPane;
    private JTextPane textPane;
 
    // Setup some data for an example card:
    String name = "Absorb Vis";
    String set = "CON";
    String manaCost = "{6}{B}";
    String printedType = "Sorcery";
    String artist = "Brandon Kitkouski";
 
    String rulesText = "Target player loses 4 life and you gain 4 life.\n"
            + "Basic landcycling {1}{B} ({1}{B}, Discard this card: "
            + "Search your library for a basic land card, reveal it, and put "
            + "it into your hand. Then shuffle your library.)";
 
    HashMap<String, BufferedImage> manaSymbolImages;
    private ScrollablePanel textPanel;
    //private JPanel textPanel;
    private JPanel headlinesPanel;
    private JPanel firstHeadlinePanel;
    private JPanel secondHeadlinePanel;
    private JLabel titleLabel;
    private JLabel manaCostLabel;
    private JLabel typeLabel;
    private JLabel setLabel;
 
    public static void main(String[] args) {
 
        EventQueue.invokeLater(new Runnable() {
 
            public void run() {
 
                try {
                    MinimalExample frame = new MinimalExample();
                    frame.setVisible(true);
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
 
    public MinimalExample() {
 
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 230, 400);
        contentPane = new JPanel();
        contentPane.setBackground(Color.WHITE);
        contentPane.setBorder(null);
        setContentPane(contentPane);
        /* HTMLEditorKit eKit = new HTMLEditorKit();
         * textPane.setEditorKit(eKit); HTMLDocument htmlDoc = (HTMLDocument)
         * textPane.getDocument(); htmlDoc.setPreservesUnknownTags(false); */
        contentPane.setLayout(new GridLayout(0, 1, 0, 0));
 
        textPanel = new ScrollablePanel();
        //textPanel = new JPanel();
        textPanel.setBackground(Color.WHITE);
        textPanel.setLayout(new BorderLayout(0, 0));
 
        headlinesPanel = new JPanel();
        headlinesPanel.setBorder(new EmptyBorder(2, 5, 3, 5));
        headlinesPanel.setBackground(Color.WHITE);
        textPanel.add(headlinesPanel, BorderLayout.NORTH);
        headlinesPanel.setLayout(new GridLayout(0, 1, 0, 0));
 
        firstHeadlinePanel = new JPanel();
        firstHeadlinePanel.setBorder(new EmptyBorder(0, 0, 3, 0));
        firstHeadlinePanel.setOpaque(false);
        headlinesPanel.add(firstHeadlinePanel);
        firstHeadlinePanel.setLayout(new BorderLayout(0, 0));
 
        titleLabel = new JLabel("");
        titleLabel.setFont(new Font("Tahoma", Font.BOLD, 12));
        firstHeadlinePanel.add(titleLabel, BorderLayout.WEST);
 
        manaCostLabel = new JLabel("");
        firstHeadlinePanel.add(manaCostLabel, BorderLayout.EAST);
 
        secondHeadlinePanel = new JPanel();
        secondHeadlinePanel.setBorder(null);
        secondHeadlinePanel.setOpaque(false);
        headlinesPanel.add(secondHeadlinePanel);
        secondHeadlinePanel.setLayout(new BorderLayout(0, 0));
 
        typeLabel = new JLabel("");
        typeLabel.setFont(new Font("Tahoma", Font.PLAIN, 12));
        secondHeadlinePanel.add(typeLabel, BorderLayout.WEST);
 
        setLabel = new JLabel("");
        setLabel.setFont(new Font("Tahoma", Font.BOLD, 12));
        secondHeadlinePanel.add(setLabel, BorderLayout.EAST);
 
        scrollPane = new JScrollPane();
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setBackground(Color.WHITE);
        contentPane.add(scrollPane);
 
        textPane = new JTextPane();
        textPane.setBorder(new EmptyBorder(0, 3, 0, 3));
        textPane.setAlignmentY(0.3f);
        textPane.setEditable(false);
 
        textPanel.add(textPane, BorderLayout.CENTER);
 
        scrollPane.setViewportView(textPanel);
 
        loadManaCostIcons();
        setPaneText();
    }
 
    // This part inserts the text into the document of the text pane.
    public void setPaneText() {
 
        titleLabel.setText(name);
        manaCostLabel.setIcon(combineSymbols(manaCost));
        typeLabel.setText(printedType);
        setLabel.setText(set);
 
        StyledDocument textPaneDoc = textPane.getStyledDocument();
 
        SimpleAttributeSet defaultAtts = new SimpleAttributeSet();
        StyleConstants.setFontFamily(defaultAtts, "SansSerif");
        StyleConstants.setFontSize(defaultAtts, 12);
 
        SimpleAttributeSet rulesAtts = new SimpleAttributeSet(defaultAtts);
 
        SimpleAttributeSet artistAtts = new SimpleAttributeSet(defaultAtts);
        StyleConstants.setFontSize(artistAtts, 10);
 
        addTextWithSymbols(rulesText, rulesAtts);
 
        try {
 
            textPaneDoc.insertString(textPaneDoc.getLength(), artist, artistAtts);
        }
        catch (BadLocationException e) {
 
            e.printStackTrace();
        }
 
        textPane.revalidate();
        textPane.repaint();
    }
 
    /* This adds the rest of the text to the pane. The codes for the symbols get
     * replaced by the actual symbols and the text gets inserted piece by piece. */
    public void addTextWithSymbols(String text, SimpleAttributeSet style) {
 
        StyledDocument textPaneDoc = textPane.getStyledDocument();
        Pattern symbolPattern = Pattern.compile("\\{(.*?)\\}");
 
        try {
 
            Matcher symbolMatcher = symbolPattern.matcher(text);
            int previousMatch = 0;
 
            while (symbolMatcher.find()) {
 
                int start = symbolMatcher.start();
                int end = symbolMatcher.end();
                String subStringText = text.substring(previousMatch, start);
                String currentMatch = text.substring(start, end);
 
                if (subStringText.isEmpty() == false) {
 
                    textPaneDoc.insertString(textPaneDoc.getLength(), subStringText, style);
                }
 
                ImageIcon currentIcon = new ImageIcon(manaSymbolImages.get(currentMatch));
 
                SimpleAttributeSet iconAtts = new SimpleAttributeSet();
                JLabel iconLabel = new JLabel(currentIcon);
                StyleConstants.setComponent(iconAtts, iconLabel);
 
                textPane.insertIcon(currentIcon);
                previousMatch = end;
            }
 
            String subStringText = text.substring(previousMatch);
 
            if (subStringText.isEmpty() == false) {
               
                textPaneDoc.insertString(textPaneDoc.getLength(), subStringText + "\n", style);
            }
        }
        catch (Exception e) {
 
            e.printStackTrace();
        }
    }
 
    /* Everything below is more or less irrelevant. However, you might need to
     * adjust the image image file paths. */
 
    public void loadManaCostIcons() {
 
        manaSymbolImages = new HashMap<String, BufferedImage>();
        try {
 
            // Most likely, those paths won't work for you!
            File bFile = new File("resource/B.png");
            File c1File = new File("resource/1.png");
            File c6File = new File("resource/6.png");
 
            manaSymbolImages.put("{B}", ImageIO.read(bFile));
            manaSymbolImages.put("{1}", ImageIO.read(c1File));
            manaSymbolImages.put("{6}", ImageIO.read(c6File));
        }
        catch (IOException e) {
 
            e.printStackTrace();
        }
    }
 
    public ImageIcon combineSymbols(String symbols) {
 
        String[] manaSymbols = symbols.split("(?<=})");
        int combinedWidth = 0;
        int maxHeight = 0;
 
        for (int i = 0; i < manaSymbols.length; i++) {
 
            BufferedImage currentSymbolImage = manaSymbolImages.get(manaSymbols[i]);
            combinedWidth += currentSymbolImage.getWidth();
 
            if (maxHeight < currentSymbolImage.getWidth()) {
                maxHeight = currentSymbolImage.getWidth();
            }
        }
 
        BufferedImage combinedManaCostImage = new BufferedImage(combinedWidth, maxHeight,
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = combinedManaCostImage.createGraphics();
 
        int currentPosition = 0;
 
        for (int i = 0; i < manaSymbols.length; i++) {
 
            BufferedImage tempCurrentImage = manaSymbolImages.get(manaSymbols[i].trim());
            graphics.drawImage(tempCurrentImage, null, currentPosition, 0);
            currentPosition += tempCurrentImage.getWidth();
        }
 
        graphics.dispose();
        return (new ImageIcon(combinedManaCostImage));
    }
 
    /* Original source of this is here:
     * /sf/ask/1104811011/
     * And one update to it is here:
     *  */
    private static class ScrollablePanel extends JPanel implements Scrollable {
 
        @Override
        public Dimension getPreferredScrollableViewportSize() {
 
            return super.getPreferredSize();
        }
 
        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
                int direction) {
 
            return 16;
        }
 
        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation,
                int direction) {
 
            return 16;
        }
 
        @Override
        public boolean getScrollableTracksViewportWidth() {
 
            return true;
        }
 
        @Override
        public boolean getScrollableTracksViewportHeight() {
 
            boolean track = true;
            Container parent = getParent();
            if (parent instanceof JViewport) {
 
                JViewport viewport = (JViewport) parent;
                if (viewport.getHeight() < getPreferredSize().height) {
                    track = false;
                }
 
            }
 
            return track;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Sha*_*oux 4

我认为问题在于您插入图像的方式,最有可能是从您的 mixSymbol 方法插入图像的方式。

以下是在 JTextPane 中插入内容的方法:

public class Test {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame mainFrame = new JFrame("test");
                mainFrame.setSize(300, 100);
                mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                Container pane = mainFrame.getContentPane();
                pane.setLayout(new BorderLayout());

                JTP jtp = new JTP();
                pane.add(jtp);

                mainFrame.setVisible(true);
            }
        });
    }

    static class JTP extends JTextPane {

        JTP() {
            HTMLEditorKit eKit = new HTMLEditorKit();
            setEditorKit(eKit);
            HTMLDocument htmlDoc = (HTMLDocument) getDocument();//the HTMLEditorKit automatically created an HTMLDocument
            htmlDoc.setPreservesUnknownTags(false);//I advice you to put this line if you plan to insert some foreign HTML

            //inserting plain text (just change null for an attributeSet for styled text)
            try {
                htmlDoc.insertString(0, "test", null);
            } catch (BadLocationException ex) {
                Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
            }

            //inserting images
            insertIcon(new ImageIcon("image.png"));
            insertIcon(new ImageIcon("image.png"));

            //inserting components (With component, you should specify the yAlignment by yourself)
            JLabel label = new JLabel(new ImageIcon("image.png"));
            label.setAlignmentY(JLabel.TOP);
            insertComponent(label);
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

但为了让事情变得更容易,我强烈建议您在 JTextPane 之外使用标题行。文本编辑器并不是真正为同一行上具有不同对齐方式的文本而设计的。这是我的建议:

public class Test {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame mainFrame = new JFrame("test");
                mainFrame.setSize(300, 100);
                mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                Container pane = mainFrame.getContentPane();
                pane.setLayout(new BorderLayout());
                pane.setBackground(Color.WHITE);

                pane.add(new JTP());
                pane.add(new Title(), BorderLayout.NORTH);

                mainFrame.setVisible(true);
            }
        });
    }

    static class JTP extends JTextPane {

        JTP() {
            setEditable(false);
            setOpaque(false);

            HTMLEditorKit eKit = new HTMLEditorKit();
            setEditorKit(eKit);
            HTMLDocument htmlDoc = (HTMLDocument) getDocument();//the HTMLEditorKit automatically created an HTMLDocument
            htmlDoc.setPreservesUnknownTags(false);//I advice you to put this line if you plan to insert some foreign HTML

            //inserting plain text (just change null for an attributeSet for styled text)
            try {
                htmlDoc.insertString(0, "capacity : ", null);
            } catch (BadLocationException ex) {
                Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
            }

            //inserting images
            insertIcon(new ImageIcon("image.png"));
            insertIcon(new ImageIcon("image.png"));

            //inserting components (With component, you should specify the yAlignment by yourself)
            JLabel label = new JLabel(new ImageIcon("image.png"));
            label.setAlignmentY(JLabel.TOP);
            insertComponent(label);
        }

    }

    static class Title extends JPanel {

        Title() {
            setLayout(new BorderLayout());
            setOpaque(false);
            add(new JLabel("<html><b>Card title</b></html>"), BorderLayout.CENTER);
            add(new JLabel(new ImageIcon("image.png")), BorderLayout.EAST);
        }

    }

}
Run Code Online (Sandbox Code Playgroud)