Search code examples
javaswingawteventqueue

AWTEvent and EventQueue


I have an external device that is sending me data 1 character at a time. I'm writing this to a StyledDocument on a JTextPane. This data is sent to me on a thread that is not the AWT thread so I need to create AWTEvents and push them to the EventQueue so AWT handles the writing so that I do not get an Exception.

I have a funny issue now...

My text is printing to the document backwards.

This is obviously because I am pushing the characters to the Event queue 1 at a time as i receive them. A queue is obviously last pushed is first popped. I'm trying to thing of a way that I can fire the event before I add a new one or something similar so that I can get the events to fire in order.

http://www.kauss.org/Stephan/swing/index.html is the example I used to create the events.

private class RUMAddTextEvent extends AWTEvent {

    public static final int EVENT_ID = AWTEvent.RESERVED_ID_MAX + 1;
    private int index;
    private String str;
    private AttributeSet as;

    public RUMAddTextEvent(Component target, int index, String str, AttributeSet as) {
        super(target, EVENT_ID);
        this.index = index;
        this.str = str;
        this.as = as;
    }

    public int getIndex() {
        return index;
    }

    public String getStr() {
        return str;
    }

    public AttributeSet getAs() {
        return as;
    }
}

private class RUMRemoveTextEvent extends AWTEvent {

    public static final int EVENT_ID = AWTEvent.RESERVED_ID_MAX + 1;
    int index;
    int size;

    RUMRemoveTextEvent(Component target, int index, int size) {
        super(target, EVENT_ID);
        this.index = index;
        this.size = size;
    }

    public int getIndex() {
        return index;
    }

    public int getSize() {
        return size;
    }
}

/**
 * Prints a character at a time to the RUMComm window.
 *
 * @param c
 */
public void simpleOut(Character c) {
    cursor.x++;
    if (lines.isEmpty()) {
        this.lines.add(c.toString());
    } else {
        this.lines.add(cursor.y, this.lines.get(cursor.y).concat(c.toString()));
        this.lines.remove(cursor.y + 1);

    }

    try {
        //doc.insertString(doc.getLength(), c.toString(), as);

        eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
        eventQueue.postEvent(new RUMAddTextEvent(this, doc.getLength(), c.toString(), as));
        getCaret().setDot(doc.getLength());
    } catch (Exception ex) {
        //Exceptions.printStackTrace(ex);
    }
}

/**
 * Creates a new line
 */
public void newLine() {
    cursor.y++;
    cursor.x = 0;
    this.lines.add("");

    //doc.insertString(doc.getLength(), "\n", null);
    eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
    eventQueue.postEvent(new RUMAddTextEvent(this, doc.getLength(), "\n", null));
    getCaret().setDot(doc.getLength());

}

/**
 * Backspace implementation.
 *
 */
public void deleteLast() {
    int endPos = doc.getLength();
    //doc.remove(endPos - 1, 1);
    eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
    eventQueue.postEvent(new RUMRemoveTextEvent(this, endPos - 1, 1));
    cursor.x--;

}

 @Override
protected void processEvent(AWTEvent awte) {
    //super.processEvent(awte);
    if (awte instanceof RUMAddTextEvent) {
        RUMAddTextEvent ev = (RUMAddTextEvent) awte;
        try {
            doc.insertString(ev.getIndex(), ev.getStr(), ev.getAs());
        } catch (BadLocationException ex) {
            Exceptions.printStackTrace(ex);
        }
    } else if (awte instanceof RUMRemoveTextEvent) {
        RUMRemoveTextEvent ev = (RUMRemoveTextEvent) awte;
        try {
            doc.remove(ev.getIndex(), ev.getSize());
        } catch (BadLocationException ex) {
            Exceptions.printStackTrace(ex);
        }

    } else {
        super.processEvent(awte);
    }
}

Solution

  • I can only encourage people who try to take care of the Swing threading rules. However, despite your efforts to create events and push them onto the EventQueue you still access the Swing component on the background Thread with the calls for the length of the document and the altering of the caret position.

    Extending a text component in order to set text on it looks like overkill to me, and certainly not the best way to approach this problem. Personally I would let the background Thread fill a buffer and flush that buffer to the text document once in a while (e.g. at each new line, twice a second using a Timer, each time I reach 1000 characters, ... ). For the update, I would simply use SwingUtilities.invokeLater.

    Some example code to illustrate this, retrieved from an old project of mine. It won't compile but it illustrates my point. The class appends Strings to an ISwingLogger which should be accessed on the EDT. Note the usage of the javax.swing.Timer to have periodical updates on the EDT.

    import javax.swing.Timer;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    
    public class SwingOutputStream extends ByteArrayOutputStream{
      private final ISwingLogger fSwingLogger;
    
      private Timer fTimer;
      private final StringBuilder fBuffer = new StringBuilder( 1000 );
    
      public SwingOutputStream( ISwingLogger aSwingLogger ) {
        fSwingLogger = aSwingLogger;
    
        fTimer = new Timer( 200, new ActionListener() {
          public void actionPerformed( ActionEvent aActionEvent ) {
            flushBuffer();
          }
        } );
        fTimer.setRepeats( false );
      }
    
      @Override
      public void flush() throws IOException {
        synchronized( fBuffer ){
          fBuffer.append( toString( "UTF-8") );
        }
        if ( fTimer.isRunning() ){
          fTimer.restart();
        } else {
          fTimer.start();
        }
    
        super.flush();
        reset();
      }
    
      private void flushBuffer(){
        synchronized ( fBuffer ){
          final String output = fBuffer.toString();
          fSwingLogger.appendString( output );
          fBuffer.setLength( 0 );
        }
      }
    }