Java 在JTextPane中对齐和内联组件(或图标)
我正在用Java开发一个应用程序,该应用程序除其他外,应该显示一个魔术的细节:在文本字段中显示采集卡(因为我使用的是Swing,所以它目前是一个Java 在JTextPane中对齐和内联组件(或图标),java,swing,jtextpane,Java,Swing,Jtextpane,我正在用Java开发一个应用程序,该应用程序除其他外,应该显示一个魔术的细节:在文本字段中显示采集卡(因为我使用的是Swing,所以它目前是一个JTextPane) 这些细节包含文本和小图标,其中一些图标与文本内联(因此文本围绕着它们流动),根据我的设计,一些图标与同一行中的一些左对齐文本右对齐 我从另一个应用程序中获取了一个图像,该应用程序使用了与我正在开发的应用程序非常相似的设计(尽管不是Java): 基本上应该是这样的 现在,出于对一切的热爱,我不能让它在JTextPane中工作 我开始
JTextPane
)
这些细节包含文本和小图标,其中一些图标与文本内联(因此文本围绕着它们流动),根据我的设计,一些图标与同一行中的一些左对齐文本右对齐
我从另一个应用程序中获取了一个图像,该应用程序使用了与我正在开发的应用程序非常相似的设计(尽管不是Java):
基本上应该是这样的
现在,出于对一切的热爱,我不能让它在JTextPane
中工作
我开始尝试用CSS实现这一点,但发现JEditorPane
和子类不支持“float”属性,因此我尝试使用该窗格的StyledDocument
起初,我没有让第一个部分工作(顶部的图标和右对齐文本总是忽略它们的对齐方式,并直接放在行中左对齐文本的后面),直到我找到
建议使用以下代码行:
pane.setEditorKit(new HTMLEditorKit());
这确实解决了我的第一个问题
但现在我被困在第二部分,让第二部分中的图标与文本对齐。以下是我目前得到的信息:
我发现,出于某种原因,当您使用带有上述代码行的编辑器套件将JTextPane
切换到html模式时,插入组件简直是疯了
顶部的图标(实际上我已合并为一个图像)和下面文本中的图标(未合并)都位于JLabels
的内部,但无论我是将它们添加为图像还是JLabels
的内部。图片或标签绝对不会比你看到的大,我根本不知道额外的空白是从哪里来的
我发现了,答案表明这是某种bug,或者只是JEditorPane
的html模式的奇怪行为
如果我再次删除上述代码行,我最终会遇到原来的问题:
根据图标在文本中的确切位置,我会得到各种不同的奇怪结果。我将下面的更多示例图片放在一起:
那么,我怎么可能解决这个问题呢?除了这一部分外,JTextPane
对我来说运行良好,但我可以使用其他解决方案,只要最终结果看起来仍然一样。请记住,我可能希望在其中添加一些其他组件(如按钮),因此如果可能的话,我希望使用Swing的原生组件
用户将无法编辑TextPane的内容,但我想稍后添加一个选项,在一次单击中复制所有内容(因此我宁愿保留文本区域)
下面,我为您提供了一个(不是很简单的)工作示例:
(编辑:现在在底部更新了代码!旧代码仍在下面的链接下。)
下面是我正在使用的图标。您需要重命名它们并更改代码中的路径。需要注意的一些事项:
- 开始时,我使用“Style”类对文本进行了样式设置,如Oracle网站上的“如何使用编辑器窗格和文本窗格”教程(textsampledemo.java,供您参考)所述。有了这个,我甚至不能在顶部做右对齐部分。奇怪的是,当我使用'SimpleAttributeSet'类进行样式设置时,即使设置非常相同,它也能工作
- 我尝试了文本和包含图标的标签的不同对齐选项。无论我使用什么选项,都没有明显的区别。
更新1: 在Sharcoux的回答之后,我编辑了我的代码,在实际的JTextPane上方有2个JLabel,其中包含两行,这两行应该具有不同的对齐方式(左对齐部分和右对齐部分)。 JTextPane现在不再使用HTMLEditorKit,我使用insertIcon()将图标插入到文本中 这样,图标插入(几乎)正确
此处图像:
然而,有两件小事我仍然不满意: 第一:
我需要将所有内容放入JScrollPane中,因为在我的实际应用程序中,TextPane中的文本要长得多。因为我现在有三个组件,而不仅仅是文本窗格,所以我需要将所有内容都放到JPanel中,然后再放到滚动窗格中。
但是,如果您这样做,JTextPane就不知道它的with值不应该再超过JScrollPane值。它会停止包装文本,并与整个文本一样大。
我为此提出了一个新问题,因为我觉得这是Swing的一个基本问题,应该有自己的问题。如果您想提供帮助,请访问以下链接:
秒:
这可能是不可能的,但我想我还是会问。以这种方式添加图标时,图标与文本具有相同的基线。他们有没有办法把它们移低一点?可能是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:
* 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;
}
}
}
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);
}
}
}
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);
}
}
}