4

I've managed to make a typewriter class that does what I want it to for the most part. It will output the string given to it one character at a time, pausing between each one as if they were typed, pausing a bit longer after periods. The problem I'm having now is that when I use this class it only works once. When I call it twice (or more) it tries to run them at the same time. This causes major problems. So I need a way for the first instance of this class to run and each one that follows to wait "it's turn" before starting. Below is an example of the desired outcome and the current.

import objectdraw.*; // Where active object comes from.     
import javax.swing.JTextArea;

public class Typewriter extends ActiveObject {

  private JTextArea out;
  private String in;

  public Typewriter(String s, JTextArea output) {

    in = s;
    out = output;
    start();

  }

  public void run() {

    synchronized(out) {

      for(int i=0; i<in.length(); i++) {

        out.append(in.substring(i,i+1));
        if(in.charAt(i) == '.') {
          pause(30);
        } else {
          pause(200);
        }

      }

    }      

  }

}

Current:

CODE: new Typewriter("\nHello", output); new Typewriter("\nWorld", output);

CURRENT OUTPUT

HW ol elr ldo

DESIRED OUTPUT

Hello
World

Obviously I left out most of the code from the Typewriter class. If that's really needed I could post it. The javadocs for ActiveObject can be found here. This is how I was taught threads and I'm afraid it may be the problem.

EDIT:

Per an answer below I've added the synchronized(out) line but I'm getting a nullpointerexception when I try to run the code.

Exception in thread "main" java.lang.NullPointerException
    at objectdraw.ActiveObject.<init>(ActiveObject.java:239)
    at com.caldwellysr.TBA.Typewriter.<init>(Typewriter.java:11)
    at com.caldwellysr.TBA.Client.initGame(Client.java:78)
    at com.caldwellysr.TBA.Client.<init>(Client.java:66)
    at com.caldwellysr.TBA.Client.main(Client.java:24)

The Typewriter line 11 is the header for the constructor. Client line 78 is where I call new Typewriter("Testing", output); where output is a JTextArea Client line 66 is a call to initGame() which has the Typewriter in it Client line 24 is the JFrame constructor.

Abbas
  • 6,720
  • 4
  • 35
  • 49
CaldwellYSR
  • 3,056
  • 5
  • 33
  • 50

3 Answers3

3

It seems that ActiveObject inherits from the Thread class and runs asynchronously so you won’t know when your thread runs. When you create 2 ActiveObject instances, the 2 for loops will not necessarily run one after the other. The result you see is your text area being updated by 2 threads simultaneously.

EDIT:

You can run your code synchronously with the synchronized clause:

synchronized(out){
    for(int i=0; i<in.length(); i++) {
        out.append(in.substring(i,i+1));

        if(in.charAt(i) == '.') {
            pause(30);
        }
        else {
            pause(200);
        }
    }      
}
Abbas
  • 6,720
  • 4
  • 35
  • 49
  • So the only way to have the desired output would be to NOT use ActiveObject? – CaldwellYSR Jan 30 '12 at 04:47
  • Take a look at the code I have added, it would work for a class inherited from a Thread class or any other class that inherits from Thread. – Abbas Jan 30 '12 at 05:17
  • I've given this an upvote because it looks like the right answer but I'm getting a strange nullpointerexception so I can't tell if it's working or not exactly. When I figure that part out and can tell if it's going to work I'll mark this the right answer. – CaldwellYSR Jan 30 '12 at 05:30
  • The only 'foreign' code is pause. 1) Comment pause and then try run you code 2) Replace 'pause' with try { Thread.sleep(30); } catch(InterruptedException ex) { Thread.currentThread().interrupt(); }. – Abbas Jan 30 '12 at 05:47
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/7157/discussion-between-caldwellysr-and-abbas) – CaldwellYSR Jan 30 '12 at 05:50
1

Although my last answer was for javascript, however, the theory should be the same. For you case to work, you probably does not need to create a new object every time. You just need to new an object once, then each time when you want to put new words in JTextArea, you can just call a function in the class to append the new string to the "in" string. And create a time function in the class to periodically output new char in the "in" string.

Something like this:

import objectdraw.*; // Where active object comes from.
import javax.swing.JTextArea;

public class Typewriter extends ActiveObject {

  private JTextArea out;
  private String in;
  private int index;

  public Typewriter(String s, JTextArea output) {

    in = s;
    index = 0;
    out = output;
    start();

  }

  public void run() {

    while (1) {
      if (index < in.length) {
          out.append(in.substring(index,index+1));
          index++;
      }

      pause(200);
   }       
  }

  public void add_string(String s) {
      in += s;
  }

}

Then you can call: Typewriter tw = new Typewriter("Hello"); tw.add_string("world");

Jingshao Chen
  • 3,405
  • 2
  • 26
  • 34
0

Here is a very easy way to do this if you just use this code and it should help. You can make it go faster by changing the time variable in mill secs.

import javax.swing.JTextArea;

 public class TypeWriter {

 private static final long time = 100;

 public static void TypeWriterEffect(String s, JTextArea output) {

   String[] words = s.split("");  


   for (String word : words)  
   {  
       output.append(word);

       try {
        Thread.sleep(time);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
   }  
  }
 }

What you would do in the other class is this:

 static JTextArea textWindow;
 TypeWriter.TypeWriterEffect("This is a Type Writer effect", textWindow);

Now don's just put the variable I put, that is just to show you what type the variable to use so use your own JTextArea variable that you have added to your JFrame or JPanel.

Hope this helps :)

TheNiv
  • 13
  • 3