代码之家  ›  专栏  ›  技术社区  ›  Blacklands

在JTextPane中对齐和嵌入组件(或图标)

  •  4
  • Blacklands  · 技术社区  · 9 年前

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

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

    我从另一个应用程序中拍摄了一张图片,该应用程序使用了与我正在开发的应用程序非常相似的设计(虽然不是Java):

    enter image description here

    这是它基本上应该看起来的样子。

    现在,出于对一切的热爱,我不能让它在 文本窗格 .

    我开始尝试用CSS来做这件事,但发现 JEditorPane 并且子类不支持“float”属性,所以我使用Pane的 StyledDocument 相反

    起初,我没有让第一部分开始工作(顶部的图标和右对齐文本总是忽略它们的对齐方式,直接放在行中左对齐文本的后面),直到我发现 this question .

    建议使用以下代码行:

    pane.setEditorKit(new HTMLEditorKit());
    

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

    但现在我被困在第二部分,让第二部分的图标与文本对齐。以下是我目前得到的:

    enter image description here

    我发现,由于某种原因,当你切换 文本窗格 使用带有上述代码行的编辑器套件,在html模式下,插入组件简直是疯了。

    顶部的两个图标(实际上我已将其合并为一个图像)和下面文本中的图标(未合并)都位于 JLabels ,但将它们添加为图像或 J标签 。图片或标签肯定不会比你在那里看到的大,我根本不知道多余的空白是从哪里来的。

    我找到了 this question ,并且答案表明这是某种bug,或者只是使用 JEditor窗格 .

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

    enter image description here

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

    enter image description here

    那么,我怎么可能解决这个问题呢? 这个 文本窗格 对我来说很好,除了这部分,但我可以使用其他解决方案,只要最终结果看起来仍然一样。请记住,我可能想在其中添加一些其他组件(如按钮),所以如果可能的话,我希望坚持使用Swing的本地组件。

    用户将无法编辑文本窗格的内容,但我希望稍后添加一个选项,以便在一次单击中复制所有内容(因此我宁愿留在文本区域)。

    下面,我为您整理了一个(不是很小的)工作示例:

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

    http://pastebin.com/RwAdPCzb

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

    enter image description here
    enter image description here
    enter image description here

    需要注意的一些事项:

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

    更新1:

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

    这样,图标就(几乎)正确插入了!
    此处的图像:
    enter image description here

    然而,有两件小事我仍然不满意:

    第一:
    我需要将所有内容放入JScrollPane,因为TextPane中的文本是 在我的实际应用中。由于我现在有三个组件,而不仅仅是TextPane,所以我需要将所有内容放入JPanel,并将其放入ScrollPane。
    但是,如果这样做,JTextPane不知道它的with不应该再超过JScrollPane。它停止包装文本,并与整个文本一样大。
    我为此提出了一个新的问题,因为我觉得这是Swing的一个基本问题,值得提出自己的问题。如果你想帮忙,这里是链接:
    JTextComponent inside JPanel inside JScrollPane

    第二:
    这可能是不可能的,但我想无论如何我都会问。以这种方式添加图标时,图标的基线与文本相同。他们有没有办法把它们放低一点?可能是2-3像素?这样他们会更好地与文本保持一致。下面有两张照片。
    现在是这样的:
    enter image description here
    这就是我希望它看起来的样子:
    enter image description here

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

    作为参考,这里也是我的新的、更新的代码。我用一个粉盒替换了上面的旧的。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:
         * https://stackoverflow.com/questions/15783014/jtextarea-on-jpanel-inside-jscrollpane-does-not-resize-properly
         * 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;
            }
        }
    }
    
    2 回复  |  直到 4 年前
        1
  •  3
  •   Sharcoux    9 年前

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

    以下是在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);
            }
    
        }
    
    }
    

    但为了使事情更简单,我强烈建议您在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);
            }
    
        }
    
    }
    
        2
  •  1
  •   StanislavL    9 年前

    您可以尝试定义自己的TabStop来对齐图标。

    如果您知道图标的大小和JTextPane的宽度,只需将内容添加为 “Person Name-tab-icon”并为段落设置自定义TabSet。TabSet只有一个TabStop。TabStop位置=jTextPaneWidth-iconWidth