Displaying a String letter by letter "animation" in a JLabel? - java

Is there any way to show a phrase, per se, "Welcome!", letter by letter with a very small delay between them? I would provide what I've tried but I haven't even come close to barely working, nothing worth mentioning. I suppose I would have to use a loop containing a scanner, yes? Any help appreciated, thanks:)

Caveats
Swing is a single threaded framework, that is, all updates and modifications to the UI are expected to be executed from within the context of the Event Dispatching Thread.
Equally, any operation which blocks the EDT will prevent it from processing (amongst other things), paint updates, meaning that the UI won't be updated until the block is removed.
Example
There are a few ways you could achieve this. You could use a SwingWorker and while it would be a good learning exercise, it would probably be a little over kill for this problem.
Instead, you can use a javax.swing.Timer. This allows you to schedule callbacks at regular intervals, these callbacks are executed within the context of the EDT which will you allow you to update the UI safely.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class AnimatedLabel {
public static void main(String[] args) {
new AnimatedLabel();
}
public AnimatedLabel() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.setSize(100, 100);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private String text = "Hello";
private JLabel label;
private int charIndex = 0;
public TestPane() {
setLayout(new GridBagLayout());
label = new JLabel();
add(label);
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String labelText = label.getText();
labelText += text.charAt(charIndex);
label.setText(labelText);
charIndex++;
if (charIndex >= text.length()) {
((Timer)e.getSource()).stop();
}
}
});
timer.start();
}
}
}
Take a look at Concurrency in Swing for more details
Update from comments
The main problem is your text value is wrapped in <html>
static String text = "<html>Welcome! I will ask simple, two-answer questions, and you will answer them. Simple as that. </html>";
And then you apply it to your label...
final JLabel centerText = new JLabel(text);
So when the timer runs, it ends up append the text again...
"<html>Welcome! I will ask simple, two-answer questions, and you will answer them. Simple as that. </html><html>Welcome! I will ask simple, two-answer questions, and you will answer them. Simple as that. </html>"
Which is invalid, because everything after the </html> will be ignored.
Instead, remove the <html> tags from the text
static String text = "Welcome! I will ask simple, two-answer questions, and you will answer them. Simple as that.";
And set the initial text of the label with <html>
final JLabel centerText = new JLabel("<html>);
Don't worry, Swing will take care of it...

Related

Java swing graphics not updating

I have a java application that uses Swing graphics which has a 2D array of textfields, each of which are updating their text properly throughout the application. On each iteration of change, I change the text of the textfield then I make it's background green for half a second and turn it back to white. The issue is that, after the first iteration of the change, the textfields no longer flash green. When I comment out the portion that converts the background back to white, the rest works and the cells progressively turn green one by one (correctly), which indicates that it is working and executing properly. I tried to address this by repainting and revalidating the UI but it's not working. What is going on?
Below is my code that updates the UI.
try {
textfieldArray[row][col].repaint();
textfieldArray[row][col].revalidate();
textfieldArray[row][col].setBackground(Color.green);
textfieldArray[row][col].repaint();
textfieldArray[row][col].revalidate();
Thread.sleep(300);
textfieldArray[row][col].setBackground(Color.white);
textfieldArray[row][col].repaint();
textfieldArray[row][col].revalidate();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Swing is a single threaded framework, it also NOT thread safe. This means two things.
First, you should never perform any long running or blocking operations within the context of the Event Dispatching Thread and...
Secondly, you should never update/modify the UI, or anything the UI depends on, from outside the context of the Event Dispatching Thread.
This means, your Thread.sleep is blocking the EDT, preventing from process paint requests.
Instead, you need some way you can trigger an update to occur after a specified delay. If that doesn't scream Swing Timer, I don't know what does
I would highly recommend taking the time to read through Concurrency in Swing for the background of why your code isn't working and possibly How to Use Swing Timers for a solution
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTextField field;
public TestPane() {
setLayout(new GridBagLayout());
field = new JTextField("All your bases beloging to us", 20);
JButton blink = new JButton("Blink");
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
field.setForeground(null);
field.setBackground(null);
}
});
timer.setRepeats(false);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(field, gbc);
blink.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
field.setForeground(Color.WHITE);
field.setBackground(Color.GREEN);
timer.start();
}
});
add(blink, gbc);
}
}
}

How to make a JButton appear "as rolled over"

A JButton has a different appearance when rolled over. That appearance is different from the "selected" appearance.
I want to display my button "as if" it was rolled over, so that user understands that if he hits the Return key, that button will be triggered.
The problem is not the same as setting the default button, because I am in a situation where I really want to get the user to understand that although he wouldn't expect it, if he hits enter that button will be activated. More details below for those who want some. Setting button as default would make button the default one, but wouldn't be significantly signaling to the user.
In my case the strong enough signal is the appearance that the button has when it is rolled over.
How to do that ?
More details on the situation, for those who want some :
I have a list of buttons representing options, and a text box at the top, which acts as a filter on the buttons
when filter is such that only one option remains, hitting return directly clicks that option's button
in reality user would have had to select the button with tab or arrow, and then hit enter.
since that shortcut is not obvious I want to signal it to user
Based on your question, what you "really" want, is the JRootPane#setDefaultButton, which will highlight the button, in a OS specific manner and if the user presses the default "action" key (Enter in most cases) will call it's ActionListener
For example...
The "normal" button is just a plain old JButton, the Hacked sets the rollOver to enabled and Default has been set as the default button for the JRootPane
As you can see, you're suggest fix does nothing on MacOS, don't know what it might do on other platforms
I suggest you have a look at How to Use Root Panes for more details
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JButton asDefault = new JButton("Default");
public TestPane() {
JButton hack = new JButton("Hacked");
hack.getModel().setRollover(true);
hack.setRolloverEnabled(true);
asDefault.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Defaulted");
}
});
add(new JButton("Normal"));
add(hack);
add(asDefault);
}
#Override
public void addNotify() {
super.addNotify();
JRootPane rootPane = SwingUtilities.getRootPane(this);
if (rootPane != null) {
rootPane.setDefaultButton(asDefault);
}
}
}
}
So using button.getModel().setRollover(true); doesn't work on all platforms and on those platforms it does work on, I suspect the user will simply need to move the mouse through it to return it to normal
button.getModel().setRollover(true);

Loop Instead of Button for displaying images in JLabel? [duplicate]

in a java aplication I have a Jlabel which i want to assign a new image to every time i click a button, using a for loop i can get it to just display the last image skipping all in between images, i know there is a error in my logic here maybe i should not be using a for loop?? any advice
private String imageList[];
ImageIcon image;
imageList = new String[] {"src\\Tour_Eiffel_Wikimedia_Commons.jpg","src\\Ben.jpg", "src\\Rio.jpg", "src\\Liberty.jpg", "src\\Pyramid.jpg"};
//constructor setting first image to display on load
public GeographyGameGUI() {
image = new ImageIcon(imageList[0]);
imageLbl.setIcon(image);
}
//button method
private void nextBtnActionPerformed(java.awt.event.ActionEvent evt) {
for (imgCount = 1; imgCount < imageList.length; imgCount++) {
image = new ImageIcon(imageList[imgCount]);
imageLbl.setIcon(image);
}
if i dont use a for loop and simply use a counter (displayed below) which i declare outside of the button method it loops correctly displaying the images but runs into a ArrayIndexOutOfBoundsException. what is the best practice here? thanks
image = new ImageIcon(imageList[imgCount]);
imageLbl.setIcon(image);
imgCount++;
You're, essentially, blocking the Event Dispatching Thread, prevent it from updating the UI. See Concurrency in Swing for more details
Instead, you should use a javax.swing.Timer to loop over the images, allowing the UI to update before changing to the next one...
See How to use Swing Timers for more details.
Java arrays are zero indexed, this means that the first element in the array is a position 0, not 1
Don't reference src directly within your code, the src directory will not exist once the application is built and packaged
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel label;
private String[] imageList = new String[] {"/Tour_Eiffel_Wikimedia_Commons.jpg","/Ben.jpg", "/Rio.jpg", "/Liberty.jpg", "/Pyramid.jpg"};
public TestPane() {
setLayout(new BorderLayout());
label = new JLabel();
add(label);
JButton btn = new JButton("Play");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
btn.setEnabled(false);
Timer timer = new Timer(1000, new ActionListener() {
private int count;
#Override
public void actionPerformed(ActionEvent e) {
if (count < imageList.length) {
try {
label.setIcon(
new ImageIcon(
ImageIO.read(
TestPane.this.getClass().getResource(imageList[count]))));
} catch (IOException exp) {
exp.printStackTrace();
}
count++;
} else {
((Timer)e.getSource()).stop();
}
}
});
timer.stop();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Your counter reaches the end of the array so you get out of bounds exception. After each increment you should check whether the end of array has been reached, and if so, set the counter to 0.
If you want to iterate over a few images with a delay on single click you need to use SwingWorker. Using delays in your action listener will suspend event dispatch thread, which means that no other updates or interactions with swing components will be available (it is likely that refreshes will not be done correctly too).
If you do a few updates (setIcon) in a very short time, Swing usually refreshes the component after the last of them, which means that only last image will be visible.
Have a look here: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html

JFrame Start Up Display Delay

I am having difficulties displaying a JFrame with all of the contents showing immediately. I would like to show the JFrame with all Components already added, but the window appears to become visible out-of-focus, wait a second, then come into focus with the Component(s) added. The code for my SSCCE is extremely short/simple, so I don't know why this effect is happening.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DelayTest
{
public static void main(String[] args)
{
JLabel label = new JLabel("DELAY TEST");
label.setHorizontalAlignment(JLabel.CENTER);
label.setPreferredSize(new Dimension(400, 60));
JFrame frame = new JFrame("Delay Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(label);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
I know that this effect does not happen if I comment out label.setPreferredSize(new Dimension(400,60));, so though it may simply be a correlation & not a causation, I'm guessing somehow PreferredSizes have to do with it. But, why?
ALSO, this effect occurs even if I create the GUI in a separate Runnable thread apart from the main thread. Although clearly the effect is related to the order of thread execution, I do not believe that specific factor is the cause.
Upon startup:
About 1 second later:
The likely delay is caused by the need for the system to initialize the synchronize the Event Dispatching Thread with the native system before it can render content.
You should make sure that your UI is initialised and shown from within the context of the Event Dispatching Thread, which should make the initial startup more synchronised...
Take a look at Initial Threads for more details
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestDelay {
public static void main(String[] args) {
new TestDelay();
}
public TestDelay() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JLabel label = new JLabel("Delay Test");
Font font = label.getFont();
label.setFont(font.deriveFont(Font.BOLD, 24f));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(label);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

testing suite with swing

I'm started to make a "testing suite" in java. I'm currently working on sentence completion type tests. Basically I have a text with placeholders, where the user should put in some text, and which will be evaluated later. I found out that by using FlowLayout I could fit the JLabels and JTextFields after each other. The problem is when a block of text is too long. It should span into multiple lines, and I'm not sure how to actually do that. And while that's okay, if I push a small text from the end of the line to a new line, but I'm still stuck if the whole text block is longer than the line width.
And I don't want to reinvent the wheel, so is there any opensource libraries for testing suites? My googlefu failed.
The best solution I've found to this problem is using Rob Camick's WrapLayout
WrapLayout is essentially an extension of FlowLayout which wraps the content when it can no longer fit horizontally.
Check out the linked blog above as it explains why your having the problems you are.
Updated
Another option would be to use JTextPane and insert fields into, for example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.HeadlessException;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.BadLocationException;
import javax.swing.text.StyledDocument;
public class TestText {
public static void main(String[] args) {
new TestText();
}
public TestText() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JTextPane tp = new JTextPane();
tp.replaceSelection("Asd, asd, asd, fgh ");
addField(tp);
tp.replaceSelection(" more funky text here ");
addField(tp);
tp.replaceSelection(" and this must wrap on the edge. The color code of red is: #");
addField(tp);
tp.replaceSelection(". ");
tp.setEditable(false);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(tp));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
protected void addField(JTextPane tp) {
JTextField field = new JTextField(10);
field.setAlignmentY(0.75f);
tp.insertComponent(field);
}
});
}
}
Note, the editor itself is not editable, but the text fields are...

Categories