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