Выравнивание и встраивание компонентов (или значков) в JTextPane
Я работаю над приложением на Java, которое, помимо прочего, должно отображать детали карты Magic: The Gathering в текстовом поле (так как я использую Swing, это в настоящее время JTextPane
).
Эти детали содержат текст и небольшие значки, причем некоторые из этих значков выделены текстом (так что текст обтекает их), а некоторые значки, по моему дизайну, выровнены по правому краю с некоторым выравниванием по левому краю в одной строке.
Я взял изображение из другого приложения, которое использует дизайн, очень похожий на тот, над которым я работаю (хотя не на Java):
Вот как это должно выглядеть в принципе.
Теперь, ради любви ко всему, я не могу заставить это работать в JTextPane
,
Я начал с попытки сделать это с помощью CSS, но обнаружил, что JEditorPane
и подклассы не поддерживают атрибут "float", поэтому я попробовал это с использованием панели StyledDocument
вместо.
Сначала не сработала первая часть (значок и текст с выравниванием по правому краю всегда игнорировали их выравнивание и помещались непосредственно после текста с выравниванием по левому краю в строке), пока я не нашел этот вопрос.
Было предложено использовать следующую строку кода:
pane.setEditorKit(new HTMLEditorKit());
что-то действительно исправило мою первую проблему.
Но теперь я застрял во второй части, получая эти значки во второй части в соответствии с текстом. Вот что я сейчас получил:
Я обнаружил, что по какой-то причине, когда вы переключаете JTextPane
в режиме HTML с помощью редактора с строкой кода выше, вставка компонентов просто сходит с ума.
И значки в верхней части (которые я фактически слил в одно изображение), и значки в тексте ниже (не объединены) находятся внутри JLabels
, но это не имеет значения, если я добавлю их в виде изображений или внутри JLabels
, Изображения или метки определенно не больше, чем вы видите там, я понятия не имею, откуда взялись лишние пробелы.
Я нашел этот вопрос, и ответ предполагает, что это какая-то ошибка или просто странное поведение в режиме html JEditorPane
,
Если я снова удалю приведенную выше строку кода, я получу исходную проблему:
В зависимости от того, где именно значки находятся в тексте, я получаю всевозможные странные результаты. Я собрал еще несколько примеров фотографий ниже:
Итак, как я мог это исправить? JTextPane
работает нормально для меня, за исключением этой части, но я мог бы использовать какое-то другое решение, пока конечный результат все еще выглядит так же. Помните, что я, возможно, захочу добавить туда некоторые другие компоненты (например, кнопку), поэтому я хотел бы придерживаться чего-то родного для Swing, если это вообще возможно.
Пользователь не сможет редактировать содержимое TextPane, но я бы хотел добавить опцию позже, чтобы скопировать все содержимое одним щелчком мыши (так что я бы предпочел остаться с текстовой областью).
Ниже я собрал (не совсем минимальный) рабочий пример, с которым вы можете поиграть:
(РЕДАКТИРОВАТЬ: обновленный код в нижней части! Старый код все еще там по следующей ссылке.)
Значки, которые я использую, приведены ниже. Вам нужно будет переименовать их и изменить путь в коде.
Некоторые вещи, на которые стоит обратить внимание:
- В начале я стилизовал текст с помощью класса `Style`, как описано в учебном пособии" Как использовать панели редактора и текстовые панели "на веб-сайте Oracle (TextSamplerDemo.java, для вашей справки). При этом я даже не смог выполнить правую часть вверху. Странно, но когда я использовал класс SimpleAttributeSet для стилизации, даже с теми же настройками, это работает.
- Я пробовал разные варианты выравнивания как текста, так и надписей, которые содержат значки. Независимо от того, какие варианты я использовал, не было никакой видимой разницы вообще.
ОБНОВЛЕНИЕ 1:
После ответа Sharcoux я отредактировал свой код так, чтобы над JTextPane было 2 JLabels, которые содержат две строки, которые должны были иметь разные выравнивания (часть, выровненная по левому и правому краям). JTextPane больше не использует HTMLEditorKit, и я использую insertIcon() для вставки значков в текст.
Таким образом, значки вставляются (почти) правильно!
Изображение здесь:
Тем не менее, есть две маленькие вещи, которыми я все еще не удовлетворен:
Первый: Мне нужно поместить все в JScrollPane, потому что текст в TextPane намного больше в моем реальном приложении. Поскольку теперь у меня есть три компонента вместо просто TextPane, мне нужно было поместить все в JPanel, а это в ScrollPane. Однако, если вы сделаете это так, JTextPane не будет знать, что его значение больше не должно превышать значение JScrollPane. Он останавливает обтекание текста и становится таким же большим, как весь текст. Я открыл для этого новый вопрос, так как чувствую, что это основная проблема Swing и заслуживает отдельного вопроса. Если вы хотите помочь, вот ссылка: JTextComponent внутри JPanel внутри JScrollPane
Во- вторых:
Вероятно, это невозможно, но я все равно спрошу. Значки имеют ту же базовую линию, что и текст, когда вы добавляете их таким образом. Есть ли способ переместить их чуть ниже? 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://stackru.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 ответа
Я думаю, что проблема заключается в том, как вы вставляете свои изображения, и, скорее всего, из вашего метода combSymbol.
Вот способ вставить материал в 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);
}
}
}
Вы можете попытаться определить свои собственные TabStops для выравнивания значков.
Если вы знаете размер иконки и ширину JTextPane, просто добавьте свой контент как "Имя человека -tab- icon" и установите пользовательский TabSet для абзаца. TabSet имеет только один TabStop. Позиция TabStop = jTextPaneWidth - iconWidth