Swing HTML Drawstring

Swing HTML drawString

I've found a short and a clean way to simulate the paintHtmlString; here's the code:

public class MyComponent extends JComponent {

private JLabel label = null;

public MyComponent() {
super();
}

private JLabel getLabel() {
if (label == null) {
label = new JLabel();
}
return label;
}

/**
* x and y stand for the upper left corner of the label
* and not for the baseline coordinates ...
*/
private void paintHtmlString(Graphics g, String html, int x, int y) {
g.translate(x, y);
getLabel().setText(html);
//the fontMetrics stringWidth and height can be replaced by
//getLabel().getPreferredSize() if needed
getLabel().paint(g);
g.translate(-x, -y);
}

protected void paintComponent(Graphics g) {
//some drawing operations...
paintHtmlString(g, "<html><u>some text</u></html>", 10, 10);
}
}

Thanks to every one for the help, I do appreciate that very much.

Swing drawString: Text bounds and line wrapping

Any class I could use?

Another option is to use a LineBreakMeasurer:

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.text.*;
import java.util.Objects;
import javax.swing.*;
import javax.swing.border.Border;

public final class LineBreakMeasurerTest {
private static final String TEXT = "1234567890 ABCDEFG HIJKLMN OPQRSTU VWXYZ";
private final JLabel lbl1 = new JLabel(TEXT);
private final JTextArea lbl2 = new JTextArea(TEXT);
private final JLabel lbl3 = new WrappingLabel(TEXT);
public JComponent makeUI() {
Border b = BorderFactory.createLineBorder(Color.GREEN, 5);
lbl1.setBorder(
BorderFactory.createTitledBorder(b, "JLabel"));
lbl2.setBorder(
BorderFactory.createTitledBorder(b, "JTextArea"));
lbl3.setBorder(
BorderFactory.createTitledBorder(b, "LineBreakMeasurer"));

lbl2.setFont(lbl1.getFont());
lbl2.setEditable(false);
lbl2.setLineWrap(true);
lbl2.setWrapStyleWord(true);
lbl2.setBackground(lbl1.getBackground());

JPanel p = new JPanel(new GridLayout(3, 1));
p.add(lbl1);
p.add(lbl2);
p.add(lbl3);
return p;
}

public static void main(String... args) {
EventQueue.invokeLater(() -> {
UIManager.put("swing.boldMetal", Boolean.FALSE);
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new LineBreakMeasurerTest().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}

class WrappingLabel extends JLabel {
//TEST: private AffineTransform at = AffineTransform.getScaleInstance(.8, 1d);
protected WrappingLabel(String text) {
super(text);
}
@Override protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(getForeground());
Insets i = getInsets();
float x = i.left;
float y = i.top;
int w = getWidth() - i.left - i.right;

AttributedString as = new AttributedString(getText());
//TEST: as.addAttribute(TextAttribute.FONT, g2.getFont());
//TEST: as.addAttribute(TextAttribute.TRANSFORM, at);
AttributedCharacterIterator aci = as.getIterator();
FontRenderContext frc = g2.getFontRenderContext();
LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);

while (lbm.getPosition() < aci.getEndIndex()) {
TextLayout tl = lbm.nextLayout(w);
tl.draw(g2, x, y + tl.getAscent());
y += tl.getDescent() + tl.getLeading() + tl.getAscent();
}
g2.dispose();
}
}

Swing graphics reposition drawString

Just delete your Window class altogether and replace it with a JFrame. Then the custom class should be a JPanel and just override the paintComponent. I am guessing it doesn't work so you're going through things to make it work and you've ended up with some pretty dodgy code.

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Graphics;
import javax.swing.Timer;
import java.awt.EventQueue;

public class Confusion{
static int x = 100;
static int y = 0;
static double theta = 0;

public static void startGui(){
JFrame frame = new JFrame("title");

JPanel panel = new JPanel(){
public void paintComponent(Graphics g){
g.drawString("string", x, y);
}
};
frame.setSize(640, 480);
frame.add(panel);
frame.setVisible(true);
Timer timer = new Timer( 30, (e)->{
x = (int)(300 + Math.sin(theta)*200);
y = (int)(300 - Math.cos(theta)*200);
theta += 0.1;
panel.repaint();
} );
timer.start();
}
public static void main(String[] args) throws Exception {
EventQueue.invokeAndWait( Confusion::startGui );
}
}

Java swing: i called once drawString and it printed my string multiple times

Your code ran fine on my Windows 10 system. I have a Java 13 JDK that I compile at Java 8.

I made a few changes to your main class. Maybe these changes stabilized the display. Run my code on your system and see.

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class LongJPanelExample {

public LongJPanelExample() {
JFrame frame = new JFrame("Long JPanel Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

MyPanel gg = new MyPanel();
frame.add(new JScrollPane(gg));

frame.pack();
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setVisible(true);
}

public static void main(String argv[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new LongJPanelExample();
}
});
}
}

class MyPanel extends JPanel {
private static final long serialVersionUID = 1L;

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawString("HI I LOVE ELON MUSK", 90, 300035);
return;
}

public Dimension getPreferredSize() {
return new Dimension(500, 300060);
}
}

How to use HTML in Swing

For a more general solution for those coming from Google, you can add HTML to Swing components very easily, as long as the content being added starts and
ends with the proper HTML tags.

For example, you may insert HTML text into a JLabel such that:

label.setText("<html> Text </html>");

You could color center it and underline the first letter like this:

label.setText("<html><center><u>T</u>ext</center></html>");

Here's a good tutorial on how to use HTML in your Swing components.

--

For your specific solution, you can simply use HTML when adding text to the label. Instead of using used.getText() you should store the text in a String somewhere (that does not enclose the opening and closing HTML tags), and update that String, in order to be able to effectively manage the opening and closing HTML tags. So it could go something like this

String text; // Earlier in the code
...
text += ("<br> " + lUsed);
used.setText("<html>" + text + "</html>")

Among other things, you could attempt setting a preferred or maximum size of the label so that it is not allowed to become to large. Also, you could make the label a text panel or text area of some kind instead, which would automatically support word wrapping, scrolling, and other features that you may want.

Here's a guide on how to use JTextAreas.

Changing the colour of text in drawstring()

  • How does this compile: g.drawString("this is something I want people to <p color="#00FF00">NOTICE</p>", x, y); as ' " ' is a special character we must escape it with \

  • You cast to Graphics2D but dont use it (not relevant to problem but can cause anomalies).

It should be:

Graphics2D g2 = (Graphics2D) g;
g2.drawString("this is something I want people to <p color=\"#00FF00\">NOTICE</p>", x, y);

to add colour simply call setColor(Color c) on Graphics object:

g2.setColor(Color.GREEN);

However this will set the entire String to be drawn green, if you want only parts to be drawn green use JLabel for HTML support (up to HTML3.2):

JLabel label = new JLabel("<html>this is something I want people to <p color=\"#00FF00\">NOTICE</p></html>");

full example:

Sample Image

NB As you can see notice is on its own line thats because of the paragraph tag rather use font tag to get it on a single line like so:

Sample Image

import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class Test {

public Test() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("<html>this is something I want people to <p color=\"#00FF00\">NOTICE</p></html>");

// JLabel label = new JLabel("<html>this is something I want people to <font color=\"#00FF00\">NOTICE</font></html>");//will be shown on single line

frame.add(label);

frame.pack();
frame.setVisible(true);
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new Test();
}
});
}
}

String won't display using drawString()

Start by getting rid of

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
questionPanel.repaint();
}

Seriously this is just dangerous and could set you for a endless loop which will consume your CPU cycles

Next, I modified your QuestionPanel so the text is actually rendered somewhere within the realms of probability ...

private class QuestionPanel extends JPanel {

public QuestionPanel() {
this.setLayout(new BorderLayout());
this.add(scoreString, BorderLayout.EAST);
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g.drawString(tf, 10, y);
}
}

But seriously, why aren't you just using a JLabel?

Now, any time tf changes, you need to call QuestionPanel's repaint method.

So, that's in the next and check(String) methods.

And finally (for now), you should never, ever do...

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

inside the context of the event dispatching thread. This is stopping the repaints from been processed, so, for one whole second, nothing will change.

If you want to stop the user for a second, disable the buttons and/or other controls and use another Swing Timer (for simplicity)

This is demonstrated in this self contained, compilable and runnable example...

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;

public class Test {

public static void main(String[] args) {
new Test();
}

public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
GameFrame frame = new GameFrame();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

public class GameFrame extends JFrame {

private JButton restart, finish;
private JPanel buttons;

public GameFrame() {
super("Trivia");
TriviaPanel tp = new TriviaPanel();
this.setLayout(new BorderLayout());
this.add(tp, BorderLayout.CENTER);
restart = new JButton("New game");
finish = new JButton("End game");
buttons = new JPanel();
buttons.add(restart);
buttons.add(finish);
this.add(buttons, BorderLayout.SOUTH);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

}

public static class TriviaPanel extends JPanel {

// private TimerListener tl = new TimerListener();
// private Timer timer = new Timer(10000, tl);
private static int score;
private JTextField scoreString;
private final int CORRECT = 10, INCORRECT = 5;
private JButton[] buttons;
private Pool pool;
private Question question;
private JButton pressed;
private final int NUM_OF_ANSWERS = 4;
private Listener lis = new Listener();
//private JPanel questionPanel;
private JPanel answersPanel;
private String tf;
private Font font = new Font("Serif", Font.BOLD, 24);
private JTextField tf2 = new JTextField();
private QuestionPanel questionPanel;

public TriviaPanel() {
score = 0;
scoreString = new JTextField();
scoreString.setFont(font);
questionPanel = new QuestionPanel();
//questionPanel.setLayout(new BorderLayout());
//questionPanel.add(scoreString,BorderLayout.EAST);
this.setLayout(new GridLayout(2, 1));
pool = null;
try {
pool = new Pool();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
question = pool.getQuestion();
String[] answers = question.shuffle();
tf = question.getQuestion();
//tf2.setText(question.getQuestion());
//questionPanel.add(tf2,BorderLayout.CENTER);
this.add(questionPanel);
answersPanel = new JPanel();
answersPanel.setLayout(new GridLayout(2, 2));
buttons = new JButton[NUM_OF_ANSWERS];
for (int i = 0; i < NUM_OF_ANSWERS; i++) {
buttons[i] = new JButton(answers[i]);
answersPanel.add(buttons[i]);
buttons[i].addActionListener(lis);

}
this.add(answersPanel);
// timer.start();
scoreString.setText("Score: " + score + "/" + pool.getIterator() * CORRECT);
}

// @Override
// protected void paintComponent(Graphics g) {
// super.paintComponent(g);
// questionPanel.repaint();
// }
private void next() {
Question q = pool.getQuestion();
question = q;
tf = q.getQuestion();
String[] answers = q.shuffle();
for (int i = 0; i < NUM_OF_ANSWERS; i++) {
buttons[i].setText(answers[i]);
}
questionPanel.repaint();
}

private void gameOver() {
JOptionPane.showConfirmDialog(null,
"Score: " + score, "Select an Option...", JOptionPane.YES_NO_CANCEL_OPTION);
}

private void check(String guess) {
// timer.stop();
String answer = question.getCorrectAnswer();
if (guess.equals(answer)) {
score += CORRECT;
tf = "Correct!!!";
} else {
score -= INCORRECT;
tf = "Wrong answer";
}
questionPanel.repaint();
scoreString.setText("Score: " + score + "/" + pool.getIterator() * CORRECT);
// OH GOD THIS IS A BAD IDEA!
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }

Timer timer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Timer timer = (Timer) e.getSource();
timer.stop();
// Personally, I'd use a listener, but this will do
afterCheckDelay();
}
});
timer.start();
}

protected void afterCheckDelay() {
if (pool.getIterator() < pool.getSize()) {
//timer.restart();
next();
} else {
gameOver();
}
}

private class QuestionPanel extends JPanel {

public QuestionPanel() {
this.setLayout(new BorderLayout());
this.add(scoreString, BorderLayout.EAST);
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g.drawString(tf, 10, y);
}
}

private class Listener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
pressed = (JButton) e.getSource();
String guess = pressed.getText();
check(guess);
}

}

private class TimerListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
// timer.stop();
// score -= INCORRECT;
// scoreString.setText("Score: " + score + "/" + pool.getIterator() * CORRECT);
// repaint();
// timer.restart();
// next();
}
}
}

public static class Pool {

private ArrayList<Question> questions = new ArrayList<>();
// private Scanner input = new Scanner(new File("src/trivia.txt"));
private static int iterator = 0;

public Pool() throws FileNotFoundException {
Question question = new Question("Why am I doing this", "Because I'm awesome", "You feel for the trick", "You have no idea", "To much caffine");
questions.add(question);
// while (input.hasNext()) {
// String q = input.nextLine();
// String a = input.nextLine();
// String d1 = input.nextLine();
// String d2 = input.nextLine();
// String d3 = input.nextLine();
// Question question = new Question(q, a, d1, d2, d3);
// questions.add(question);
// }
Collections.shuffle(questions);
//System.out.println(questions);
}

public Question getQuestion() {
Question q = questions.get(iterator);
iterator++;
return q;
}

public int getSize() {
return questions.size();
}

public static int getIterator() {
return iterator;
}
}

public static class Question {

private final int NUM_OF_ANSWERS = 4;
private String question;
private String[] answers = new String[NUM_OF_ANSWERS];
private final int CORRECT_ANSWER = 0;

public Question(String qu, String an, String dm1, String dm2, String dm3) {
question = qu;
answers[0] = an;
answers[1] = dm1;
answers[2] = dm2;
answers[3] = dm3;
}

public String getCorrectAnswer() {
return answers[CORRECT_ANSWER];
}

public String getQuestion() {
return question;
}

public String[] getAnswers() {
return answers;
}

public String toString() {
String str = question;
for (int i = 0; i < 4; i++) {
str += " " + answers[i];
}
str += "\n";
return str;
}

public String[] shuffle() {
String[] shuffled = new String[NUM_OF_ANSWERS];
for (int i = 0; i < NUM_OF_ANSWERS; i++) {
shuffled[i] = answers[i];
}
Collections.shuffle(Arrays.asList(shuffled));
return shuffled;
}
}
}


Related Topics



Leave a reply



Submit