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

标签 java swing jtextpane

我正在使用Java应用程序进行开发,该应用程序除其他外应在文本字段中显示Magic:The Gathering卡的详细信息(因为我使用的是Swing,因此当前是JTextPane)。
这些详细信息包含文本和小图标,其中一些图标与文本内嵌(这样文本就在它们周围流动),而某些图标(根据我的设计)与同一行中的一些左对齐文本右对齐。
我从另一个应用程序中获取了一张图像,该应用程序的设计与我正在使用的应用程序非常相似(尽管不是在Java中):
enter image description here
这基本上就是它的外观。
现在,出于对所有事物的热爱,我无法以JTextPane的形式工作。
我开始尝试使用CSS进行此操作,但发现JEditorPane和子类不支持“float”属性,因此我尝试使用Pane的StyledDocument进行了尝试。
首先,我没有得到第一部分的帮助(顶部的图标和右对齐文本始终忽略其对齐方式,而是直接放在行中左对齐的文本之后),直到找到this question为止。
建议使用以下代码行:

pane.setEditorKit(new HTMLEditorKit());
这确实解决了我的第一个问题。
但是,现在我停留在第二部分,将第二部分中的那些图标与文本对齐。这是我目前得到的:
enter image description here
我发现由于某种原因,当您使用带有上面代码行的编辑器工具包将JTextPane切换为html模式时,插入组件简直是疯了。
顶部的两个图标(实际上我已经合并为单个图像)和下面的文本中的图标(未合并)都在JLabels内部,但是我将它们添加为图像还是内部都没关系JLabels。图像或标签绝对不比您在此处看到的大,我完全不知道多余的空白来自何处。
我找到了this question,答案表明这是某种错误,或者是JEditorPane的html模式的怪异行为。
如果再次删除上面的代码行,那么我将遇到原来的问题:
enter image description here
根据图标在文本中的确切位置,我会得到各种不同的奇怪结果。我在下面整理了一些示例图片:
enter image description here
所以,我怎么可能解决这个问题? 除了这部分,JTextPane对我来说很好用,但是我可以使用其他解决方案,只要最终结果仍然相同即可。请记住,我可能想在其中添加一些其他组件(例如Button),因此,如果可能的话,我想坚持使用Swing本身的某些组件。
用户将无法编辑TextPane的内容,但是我想稍后再添加一个选项,以一键复制所有内容(因此,我宁愿留在文本区域中)。
下面,我整理了一个(不是那么少的)工作示例供您试用:
(编辑:现在底部已更新代码!旧代码仍在下面的链接下。)
http://pastebin.com/RwAdPCzb
我正在使用的图标如下。您需要重命名它们并更改代码中的路径。
enter image description here
enter image description here
enter image description here
注意事项:
  • 最初,我使用`Style`类为文本设置了样式,如Oracle网站上的“如何使用编辑器窗格和文本窗格”教程(TextSamplerDemo.java,供您参考)中所述。这样,我什至无法在顶部进行右对齐。奇怪的是,当我使用`SimpleAttributeSet`类来进行样式设置时,即使使用了相同的设置,它也可以工作。
  • 我尝试了文本和包含图标的标签的不同对齐方式。不管我使用什么选项,都没有明显的区别。

  • 更新1:
    在Sharcoux回答之后,我对代码进行了编辑,以使实际JTextPane上方有2个JLabel,这些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的某些部分,以将呈现在其上的所有图标向下移动一个设置的像素量,或者类似的东西?
    作为参考,这也是我新的更新代码。如果您仍然想查看它,我将上面的旧文件替换为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:
         * 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;
            }
        }
    }
    

    最佳答案

    我认为问题在于您插入图像的方式,最有可能是从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);
            }
    
        }
    
    }
    

    关于java - 在JTextPane中对齐和内联组件(或图标),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32466776/

    相关文章:

    java - Mac 操作系统无法运行 .Jar 文件

    java - 将监听器定义的字符串变量导入到另一个包中

    java - 访问不同 Java 类的变量时遇到问题

    java - 我想在启动后从另一个服务调用服务方法,但我不知道如何创建该服务的对象

    java - 当工作区与帐户上的默认设置不同时,使用 Java Rally Rest API 插入测试用例结果失败

    java - 围绕应用程序包装 Applet

    java - JLabel 是否可以根据变量值更改其文本?

    java - 在 JEditorPane 中启用链接的键盘导航

    java - System.getProperty ("user.dir")与 Eclipse IDE

    java - JTextPane 不可变文本 block