So I have been having some trouble trying to create a teletype effect for my swing program. I essentially want to update a JFrame at 40ms increments with a new letter, "typing" out a message to the user. However, it flickers a lot when I try to do this. The method is below:
public static void animateTeletype(String input, JTextArea displayArea)
throws InterruptedException {
displayArea.setText("");
String s = "";
for(int i = 0; i<input.length(); i++) {
s += input.substring(i, i+1);
displayArea.setText(textToDisplay);
Thread.sleep(40);
displayArea.update(displayArea.getGraphics());
}
}
I figure the problem stems from updating the text too fast, and it has to update more than it can handle. I am not sure how I would go about this issue, as reducing tick time will make text scroll too slowly. Any advice is appreciated!
** I've solved the problem. This is my new code:
static Timer timer = null;
public static void animateTeletype(final String input, final JTextArea displayArea) throws InterruptedException
{
final String[] s = new String[1];
s[0] = " ";
final int[] i = new int[1];
i[0] = 0;
displayArea.setText("");
timer = new Timer(30, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
s[0] = input.substring(i[0], i[0]+1);
i[0]++;
displayArea.append(s[0]);
if(displayArea.getText().equals(input))
timer.stop();
}
});
timer.start();
}
displayArea.update(displayArea.getGraphics());
Don't use the update() method. There is never any reason to do that. Get rid of that statement.
Swing components will automatically repaint themselves.
displayArea.setText(textToDisplay);
Don't uset setText(...) to add new text.
Instead you should be using:
displayArea.append( "some more text" );
Don't use Thread.sleep(40) for animation. It you want animation then use a Swing Timer to schedule the animation.
I suggest you look at other section of the tutorial for Swing basics. Maybe something like How to Use Text Fields.
Related
I have a problem with the optimisation of adding about 4 thousand newly created JPanel components to an already existing panel.
The problem is that, surprisingly, it's quite slow. It takes nearly 10 seconds to finish adding them all and that is quite slow for me.
public static void main(String[] args) {
JMenuItem mntmGenerateRandom = new JMenuItem("Generate random int");
mntmGenerateRandom.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
Random rand = new Random();
for(int i = 0; i < 4000; i++){
intPositions.add(rand.nextInt(4000));
System.out.println(i);
}
quicksort(0, intPositions.size()-1, intPositions);
long start = System.currentTimeMillis();
repaintPanels();
long stop = System.currentTimeMillis();
System.out.println("Done in " + (stop-start) + "ms");
}
});
}
private void repaintPanels(){
panelArray.clear();
ExecutorService service = Executors.newWorkStealingPool();
service.submit(new Runnable() {
public void run() {
for(int i = 0; i < intPositions.size(); i++){
panelArray.add(i, new JPanel());
panelArray.get(i).setBounds(intPositions.get(i), 1, 1, panelParent.getHeight()-2);
panelParent.add(panelArray.get(i), 1);
}
}
});
panelParent.repaint();
panelParent.revalidate();
}
Here's the simplified code, removed everything unnecessary. The slowest part is when I call panelParent.add(panelArray.get(i), 1);
Could anyone helped me make it any faster (if it's even possible)?
If Joop is going to guess at a solution, then so will I.
As I noted in comments a JPanel is typically used as a container as a component that holds other components, and you don't appear to be using it to this purpose but rather (to my eyes -- but I'm not 100% sure until you clarify your problem for us), to draw an image. If so, if what you're trying to do is to draw a changing image of blocks or links, one that changes as your sorting algorithm (or whatever you're trying to do in your model) changes, then why not instead use a single drawing JPanel, one whose paintComponent has been overridden, and whose paintComponent draws the state of the model as the model changes. This too is much lighter weight than what you're trying to achieve.
Use a JTable instead. It can use one single JComponent child (like a JPanel) to draw all cells, using a CellRenderer and a CellEditor.
This is a fly weight pattern, you also might implement yourself: drawing in one single JPanel all "panels."
In first, I'm using Eclipse IDE. So my question is: I create a progress bar, but I want to make it load according to the place where the program is. I have a refresh button, and when I click it my entire program update. What I want is a progress bar that accompanying the process of updating and ends when it ends.
Sorry if my English isn't the best, but I'm a young Portuguese developer.
my btn code
JButton btnActualizar = new JButton("\u21BB");
btnActualizar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
TxtIpExternoLoja.setText(" ");
TxtSSIDLoja.setText(" ");
TxtFirewallLoja.setText(" ");
TxtIpLojaEtho.setText(" ");
TxtMaskLojaEtho.setText(" ");
TxtGWLojaEtho.setText(" ");
TxtDns1LojaEtho.setText(" ");
TxtDns2LojaEtho.setText(" ");
TxtIpLojaWlan.setText(" ");
TxtMaskLojaWlan.setText(" ");
TxtGwLojaWlan.setText(" ");
TxtDns1LojaWlan.setText(" ");
TxtDns2LojaWlan.setText(" ");
TxtIpLojaVpn.setText(" ");
TxtMaskLojaVpn.setText(" ");
TxtGwLojaVpn.setText(" ");
TxtDns1LojaVpn.setText(" ");
TxtDns2LojaVpn.setText(" ");
// DefaultTableModel model = (DefaultTableModel)
// tablePing.getModel();
// model.setRowCount(0);
TxtTime.setText(" ");
i = 0;
t.start();
btnActualizar.setEnabled(false);
update();
}
});
my progressbar code:
JProgressBar progressBar = new JProgressBar(0, 15);
GridBagConstraints gbc_progressBar = new GridBagConstraints();
gbc_progressBar.insets = new Insets(0, 0, 0, 5);
gbc_progressBar.gridx = 3;
gbc_progressBar.gridy = 0;
PanelBotoes.add(progressBar, gbc_progressBar);
GridBagConstraints gbc_btnImprimir = new GridBagConstraints();
gbc_btnImprimir.insets = new Insets(0, 0, 0, 5);
gbc_btnImprimir.gridx = 5;
gbc_btnImprimir.gridy = 0;
PanelBotoes.add(btnImprimir, gbc_btnImprimir);
progressBar.setStringPainted(true);
progressBar.setValue(0);
t = new Timer(interval, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (i == 15) {
t.stop();
btnActualizar.setEnabled(true);
} else {
i++;
progressBar.setValue(i);
}
}
});
Take a look at this page: https://docs.oracle.com/javase/tutorial/uiswing/components/progress.html.
Specifically, the section on "Using Determinate Progress Bars" should be what you are looking for. The example code is a little complex, but probably about as simple as it can get with this stuff.
Take note: the example uses a SwingWorker (more on those here) to update the progress value. This is because the progress bar is drawn in the Swing thread and won't be updated if the process is running on that thread too.
You will need to update your program on a thread which is not the Swing thread.
Edit 1 - For your question update with code
So it looks like you've got the idea with the progress bar code. I've not run it, but what you've got looks like it should work. Your next step is to replace the timer with the update() methods process.
To do this you'll need to do the following:
Make sure your update() method is running in a thread separate from the swing thread (something like a SwingWorker could be used here).
From within the update thread you need to call progressBar.setValue(x) when you want to update the bar, where x = how far your process has got.
Add some more calls to progressBar.setValue(x) in the update thread. You can put these wherever you like, but it's probably best to put them before and after long processes.
Note: you have to use threads for progress bars. This means you could run into things like deadlock and race conditions. Be careful!
This is a continuation from my last post Java: Animated Sprites on GridLayout. Thanks to a reply, it gave me an idea in where I just had to insert a loop in the trigger condition and call pi[i].repaint() in it. So far it works. Though I tried to integrate it to my game which composed of multiple sprites, it had no improvement in it. Without the animation, the sprites show on the grid with no problems. I inserted the animation loop in the GridFile class and it didn't show. I also tried to insert the animation loop in the MainFile, it showed irregular animations, kinda like a glitch. Can someone tell me where did I went wrong? Ideas are welcome.
MainFile class
public class MainFile {
JFrame mainWindow = new JFrame();
public JPanel gridPanel;
public MainFile() {
gridPanel= new GridFile();
mainWindow.add(gridPanel,BorderLayout.CENTER);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setSize(700,700);
mainWindow.setResizable(false);
mainWindow.setVisible(true);
}
public static void main(String[]args){
new MainFile();
}
}
GridFile class
public class GridFile extends JPanel{
ImageIcon gameBackground = new ImageIcon(getClass().getResource("Assets\\GridBackground.png"));
Image gameImage;
int[] pkmArray = new int[12];
int random = 0;
Pokemon[] pkm = new Pokemon[36];
JPanel[] pokeball = new JPanel[36];
int j = 0;
public GridFile(){
setLayout(new GridLayout(6,6,6,6));
setBorder(BorderFactory.createEmptyBorder(12,12,12,12));
gameImage = gameBackground.getImage();
for(int i = 0;i < 36;i++){
do{
random = (int)(Math.random() * 12 + 0);
if(pkmArray[random] <= 3){
pokeball[i] = new Pokemon(random);
pokeball[i].setOpaque(false);
pokeball[i].setLayout(new BorderLayout());
pkmArray[random]++;
}
}while(pkmArray[random] >= 4);
add(pokeball[i],BorderLayout.CENTER);
}
while(true){
for(int i = 0; i < 36; i++){
pokeball[i].repaint();
}
}
}
public void paintComponent(Graphics g){
if(gameImage != null){
g.drawImage(gameImage,0,0,getWidth(),getHeight(),this);
}
}
}
Use a swing Timer for the repainting, and give a bit time between the frames for swing to do the painting work. There's no point trying to draw faster than what could be displayed anyway. If you have the animation loop in main(), the repaint manager will try to drop some of the repaint requests that appear close to each other, which can be the cause of the irregular animation you see.
You should create and access swing components only in the event dispatch thread. You current approach is breaking the threading rules.
Addition: When the animation loop is where you have it now, the GridFile constructor never returns, which explains that you'll see nothing because the code never gets far enough to show the window.
I was directed to the website Hackertyper.com - and it intrigued me very much. So I decided that, using Java, I would attempt to re-write it. I stumbled on some issues while I did it.
Currently, so far from my intro Java course, I only learned how to use the Scanner class, so at the moment, my "code" only takes an input after pressing the "Enter" key. At the same time, upon pressing enter, the entire String gets printed out.
My questions are:
Are there any libraries in which it would take an input (any key on the keyboard, in this case) and do something without pressing "Enter", and.
What do I need to do in order to take an input, stop the piece of code, wait for the next input, before executing the next loop?
So far below is the very poorly written code of what I have.
String paragraph = "hellothere";
Scanner newInput = new Scanner(System.in);
String input = newInput.nextLine();
for(int i = 0; i < paragraph.length(); i++){
if(input.equals("2")){
System.out.print(paragraph.charAt(i));
input = newInput.nextLine(); // call input again after executed the loop
}
}
After I solve these issues I think I will dabble with IO and let Java call a text file in which inside would have pieces of code, which onKeyPress would type out the i-th index.
Thanks!
I assume you mean Hacker Typer, not hacker type?
1) I think what you need is a key listener event
2) You could write a method that is called when the key event fires. The method would then return a piece of text. You store the text in an array, so it always returns the next piece of code.
To create the array from string you can use String.split
edit: i just saw that it always puts two characters, not the next word.
I would recommend you used the SWT toolkit.
For a functional example, run my code below. You only need to add the SWT dependencies.
Writing into the console is silly. This is much cooler:
public class Waffles
{
// Keeps track of how many times any key is pressed
private static int keyCounter = 0;
// Used to continuously append characters from the input string
private final StringBuilder builder;
// This can be read from a file, or whatever
private final String GIBBERISH;
public static void main(final String[] args)
{
new Waffles();
}
private Waffles()
{
builder = new StringBuilder();
// Put your dummy code here (extract from a file)
GIBBERISH = " I freaking love waffles!";
// Loop the SWT Display
init();
}
private void init()
{
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setText("HackerTyper");
shell.setSize(500, 500);
shell.setLayout(new GridLayout());
shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
// Here we create the text area
createWidget(shell);
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private void createWidget(final Composite parent)
{
// Wrap it for multi line text, and set it to read only, so we can't modify
// the text 'manually'
final Text textArea = new Text(parent, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY);
textArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
// Yay colours!
textArea.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
textArea.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_GREEN));
// This is what was recommended by the other two answers
textArea.addKeyListener(new KeyListener()
{
#Override
public void keyPressed(final KeyEvent arg0)
{
// *magic*
textArea.setText( getGibberish() );
}
#Override
public void keyReleased(final KeyEvent arg0)
{
}
});
}
private String getGibberish()
{
if (keyCounter > GIBBERISH.length() - 1)
keyCounter = 0; // Careful not to go out of string bounds
// Continuously append it, then pass the whole text to the textArea
builder.append( GIBBERISH.charAt(keyCounter++) );
return builder.toString();
}
}
FEEL like a hacker.
BE like a hacker. HUZZZAAA!
I'm really tired.
There is an interface called KeyListener but this only works with swing classes, and based on you're code this a console application. You could use JNativehook which allows you to use swing events in a console application. You should learn basic swing events before using this though.
I have ten words in a string array. As an example: {"A B","C D","E F","G H"......}.
I have to display these words in succession, holding each word on screen for 2 seconds. With each new word displayed, the font size needs to get smaller as well.
How do I do this on a BlackBerry?
You can use a Timer to control the timing:
import java.util.Timer
int index;
String[] words;
...
TimerTask task = new TimerTask() {
public void run()
{
// TODO: Check the bounds and kill the timer when we're done.
// Invoke the code in the UI Thread
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run()
{
// Code to enumerate and concat the string...
}
});
}
};
timer timer = new Timer();
timer.scheduleAtFixedRate(task, 0, 2000);
To change font size:
// in the class body:
final int minFontSize = 5;
int maxFontSize = minFontSize + words.length();
// in the timer's run method:
net.rim.device.api.ui.Font defaultFont = this.getFont();
net.rim.device.api.ui.Font myFont = defaultFont.derive(0, maxFontSize - index)
// change the font of the ui control
field.setFont(myFont);
I haven't checked the code to see if it compiles, but I hope you get the idea.