在JTextPane中对齐和内联组件(或图标) [英] Aligning and Inlining components (or icons) in JTextPane

查看:94
本文介绍了在JTextPane中对齐和内联组件(或图标)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Java应用程序进行开发,该应用程序除其他外应在文本字段中显示Magic:The Gathering卡的详细信息(由于我使用的是Swing,因此当前为JTextPane ).

I'm working on an application in Java which, among other things, is supposed to display the details of a Magic: The Gathering card in a text field (since I'm using Swing, this is currently a JTextPane).

这些详细信息包含文本和小图标,其中一些图标与文本内嵌在一起(因此文本在它们周围流动),根据我的设计,一些图标与同一行中的一些左对齐文本右对齐

Those details contain text and small icons, with some of those icons inlined with the text (so the text flows around them), and some icons, by my design, right-aligned with some left-aligned text in the same line.

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

I took an image from another application which uses a very similar design to the one I'm working on (although not in Java):

这基本上是它的外观.

现在,出于对所有事物的热爱,我无法在JTextPane中使用它.

Now, for the love of everything, I can't get that to work in a JTextPane.

我开始尝试使用CSS进行此操作,但是发现JEditorPane和子类不支持"float"属性,因此我尝试使用Pane的StyledDocument进行了尝试.

I started with trying to do this with CSS, but found out that the JEditorPane and subclasses don't support the "float" attribute, so I tried it with using the Pane's StyledDocument instead.

首先我没有工作的第一部分(顶部的图标和右对齐文本始终忽略它们的对齐,而是直接放在行中左对齐的文本之后),直到我发现此问题.

I didn't get the first part to work (the icon and right-align text at the top always ignored their alignment and were placed directly after the left-aligned text in the line) at first, until I found this question.

建议使用以下代码行:

pane.setEditorKit(new HTMLEditorKit());

确实以某种方式解决了我的第一个问题.

which somehow indeed fixed my first issue.

但是,现在我停留在第二部分,将第二部分中的那些图标与文本对齐.这是我目前得到的:

But now I'm stuck at the second part, getting those icons in the second part inline with the text. Here's what I currently got:

我发现由于某种原因,当您使用带有上面代码行的编辑器工具包将JTextPane切换为html模式时,插入组件简直是疯了.

What I've found is that for some reason, when you switch the JTextPane to html mode with an editor kit with the line of code above, inserting components just goes completely crazy.

顶部的两个图标(实际上我已经合并到单个图像中)和下面的文本中的图标(未合并)都在JLabels内,但是我添加它们并不重要作为图像或在JLabels内部.图像或标签绝对不比您在此处看到的大,我根本不知道多余的空白来自哪里.

Both the icons on the top (which I have merged into a single image actually), and the ones in the text below (not merged) are both inside of JLabels, but it doesn't matter if I add them as images or inside of JLabels. The images or labels are definitely not bigger than what you see there, I have no idea at all where the extra whitespace is coming from.

我发现了这个问题,答案表明这是JEditorPane的html模式下的某种bug或只是怪异的行为.

I found this question, and the answer suggest that this is some kind of bug or just weird behavior with the html mode of the JEditorPane.

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

If I remove the above code line again, I end up with my original problem:

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

Depending on where exactly the icons are in the text, I get all kinds of different weird results. I put together some more example pictures below:

那么,我怎么可能解决这个问题?除了这部分以外,JTextPane对我来说一切正常,但只要最终结果仍然可以使用其他解决方案看起来一样.请记住,我可能想在其中添加一些其他组件(例如Button),因此,如果可能的话,我想坚持使用Swing本身的某些东西.

So, how could I possibly fix this? The JTextPane is working fine for me, except for this part, but I could possibly use some other solution, as long as the end result still looks the same. Remember that I might want to add some other components (like a Button) in there, so I'd like to stick to something native to Swing if possible at all.

用户将无法编辑TextPane的内容,但是我想稍后再添加一个选项,以一键复制所有内容(因此,我宁愿留在文本区域中).

The user will not be able to edit the TextPane's contents, but I'd like to add an option later to copy all of the content in one click (so I'd rather stay with a text area).

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

Below, I have put together a (not really that minimal) working example for you to play around with:

(现在底部已更新代码!旧代码仍位于以下链接下.)

http://pastebin.com/RwAdPCzb

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

The icons that I'm using are below. You'd need to rename them and change the path in the code.







一些注意事项:

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

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

After Sharcoux' answer, I have edited my code to have 2 JLabels above the actual JTextPane which hold the two lines that were supposed to have different alignings (a left- and a right-aligned part). The JTextPane doesn't use a HTMLEditorKit anymore now, and I use insertIcon() to insert the icons into the text.

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

This way, the icons are inserted (almost) correctly!
Image here:

但是,有两点我仍然不满意:

首先:
我需要将所有内容放入JScrollPane中,因为在我的实际应用程序中,TextPane中的文本要长得多.由于我现在拥有三个组件,而不仅仅是TextPane,因此我需要将所有内容放入一个JPanel中,并将其放入ScrollPane中.
但是,如果您这样进行操作,则JTextPane不知道其with不应超过JScrollPane的.它会停止自动换行,并且会变得与整个文本一样大.
我为此提出了一个新的问题,因为我认为这是Swing的基本问题,值得提出自己的问题.如果您想提供帮助,请访问以下链接:
JScrollPane内的JPanel内的JTextComponent

First:
I need to put everything into a JScrollPane because the text in the TextPane is much longer in my actual application. Since I now have three components instead of just the TextPane, I needed to put everything into a JPanel and that into the ScrollPane.
However, if you do it like this, the JTextPane doesn't know that its with should not exceed the JScrollPane's anymore. It stopps wrapping text and just grows as big as the entire text.
I have opened a new question for this, since I feel that this is a basic issue of Swing and deserves its own question. If you want to help, here is the link:
JTextComponent inside JPanel inside JScrollPane

第二:
这可能是不可能的,但我想我还是会问.以这种方式添加图标时,它们的基线与文本相同.他们有什么办法将它们降低一点吗? 2-3像素,也许吗?这样,它们将与文本更好地保持一致.以下两张图片.
现在是这样的:

这就是我想要的样子:

Second:
This is probably not possible, but I guess I'll ask anyway. The icons have the same baseline as the text when you add them this way. Is they any way to move them just a bit lower? 2-3 pixels, maybe? They would align much better with the text that way. Two pictures below.
This is how it looks now:

And this is how I would like it to look:

也许我可以继承并重写JTextPane的某些部分,以将呈现在其上的所有图标向下移动一个设置的像素量或类似的数字?

Maybe I can subclass and override some part of the JTextPane to move all icons that are rendered on it down a set pixel amount, or something like that?

作为参考,这也是我最新的更新代码.如果您仍然想查看它,我将上面的旧文件替换为pastebin.com链接.

For reference, here is also my new, updated code. I replaced the old one above with a pastebin.com link, if you still want to look at it.

我的第一个问题已经消除!我也更新了下面的代码以反映这一点.

My first problem has already been eliminated! I updated the code below to reflect that, too.

我的第二个问题仍然存在!

这是新代码:

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方法插入图像的方式.

I think that the issue is the way you insert your images, and most probably, from your combineSymbol method.

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

Here is the way to insert stuff in the 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之外使用标题行.文本编辑器并不是真正针对同一行上具有不同对齐方式的文本而设计的.这是我的建议:

But to make things easier, I strongly advice you to use a title line outside the JTextPane. Text editors aren't really made for text having different alignment on the same line. Here is what I would suggest :

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);
        }

    }

}

这篇关于在JTextPane中对齐和内联组件(或图标)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆