0

一度に1文字ずつデータを送信する外部デバイスがあります。これをJTextPaneのStyledDocumentに書き込んでいます。このデータはAWTスレッドではないスレッドで送信されるため、AWTEventsを作成し、それらをEventQueueにプッシュして、例外が発生しないようにAWTが書き込みを処理するようにする必要があります。

私は今面白い問題を抱えています...

私のテキストはドキュメントに逆方向に印刷されています。

これは明らかに、キャラクターを受け取ったときに一度に1つずつイベントキューにキャラクターをプッシュしているためです。キューは明らかに最後にプッシュされ、最初にポップされます。新しいイベントなどを追加する前にイベントを発生させて、イベントを順番に発生させることができるようにしようとしています。

http://www.kauss.org/Stephan/swing/index.htmlは、私がイベントを作成するために使用した例です。

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);
    }
}
4

4 に答える 4

2

私はSwingのスレッドルールを守ろうとする人々だけを励ますことができます。ただし、イベントを作成してプッシュする努力にもかかわらず、ドキュメントの長さの呼び出しとキャレットの位置の変更を使用してEventQueue、バックグラウンドでSwingコンポーネントにアクセスします。Thread

テキストを設定するためにテキストコンポーネントを拡張することは、私にはやり過ぎのように見えますが、この問題に取り組む最善の方法ではありません。個人的には、バックグラウンドThreadでバッファを埋め、そのバッファをテキストドキュメントにフラッシュすることがあります(たとえば、新しい行ごとに、タイマーを使用して1秒に2回、1000文字に達するたびに...)。更新には、単にを使用しますSwingUtilities.invokeLater

私の古いプロジェクトから取得した、これを説明するためのいくつかのサンプルコード。コンパイルされませんが、私のポイントを示しています。このクラスは、EDTでアクセスする必要があるにStringsを追加します。ISwingLoggerの使用法に注意しjavax.swing.Timerて、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 );
    }
  }
}
于 2012-10-01T20:52:14.353 に答える
1

実際の問題は、イベントが順不同で処理されることではなく、間もなく古くなる情報を使用してイベントを作成していることです。

new RUMAddTextEvent(this, doc.getLength(), ...

イベントが送信されると、ドキュメントの長さは0になる場合がありますが、イベントが処理されるまでにはそうではない場合があります。インデックスを指定する代わりにAppendTextEventを使用することでこれを解決できますが、位置ベースの挿入がある場合は、それらを考慮する必要があります。

ロット全体の代替オプションは、無効化モデルを使用することです。

// in data thread
void receiveData(String newData) {
    updateModelText(newData);
    invalidateUI();
    invokeLater(validateUI);
}

// called in UI thread
void validateUI() {
    if (!valid) {
        ui.text = model.text;
    }
    valid = true;
}
于 2012-10-01T20:38:17.710 に答える
1

私見、あなたは単純なはずの何かを取り、それを必要以上に複雑にしました(ちなみに私はこれの本当のマスターです;))

正確な問題を知らなければ、100%になるのは難しいですが、この小さな例は、同じ問題にどのように取り組むかを示しています。SwingUtilities.invokeLater/invokeAndWait

public class EventTest {

    public static void main(String[] args) {

        JFrame frame = new JFrame();
        frame.setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 400);
        JTextArea area = new JTextArea();
        frame.add(new JScrollPane(area));
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        Thread thread = new Thread(new Dispatcher(area));
        thread.setDaemon(true);
        thread.start();

    }

    public static class Dispatcher implements Runnable {

        private String message = "Hello from the other side\n\nCan you read this\n\nAll done now";
        private JTextArea area;

        public Dispatcher(JTextArea area) {
            this.area = area;
        }

        @Override
        public void run() {

            int index = 0;
            while (index < message.length()) {

                try {
                    Thread.sleep(250);
                } catch (InterruptedException ex) {
                }

                send(message.charAt(index));
                index++;

            }

        }

        public void send(final char text) {

            System.out.println("Hello from out side the EDT - " + EventQueue.isDispatchThread());

            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("Hello from the EDT - " + EventQueue.isDispatchThread());
                        area.append(new String(new char[]{text}));
                    }
                });
            } catch (Exception exp) {
                exp.printStackTrace();
            }

        }

    }

}

あなたの場合、私はおそらく、あなたが行いたい各更新を処理することができる一連のランナブルを作成するでしょう。

たとえば、現在の場所に文字列を挿入したり、現在のキャレットの場所から文字列を削除したりできるInsertRunnableand (サンプルコードから)を作成する場合があります。DeleteRunnable

これらに必要な情報を渡し、残りは彼らに任せます。

于 2012-10-01T20:43:33.960 に答える
0

...私がそれらを受け取ったとき。キューは明らかに最後にプッシュされ、最初にポップされます。私はしようとしています...

ここにいくつかの概念上の誤りがあります。キューはFIFOであり、最初にプッシュインされたものが最初にポップアウトされることを意味します。スタックはLIFOになり、最後にプッシュインされたものが最初にポップアウトされることを意味します。これがあなたの問題が本当にあった場所だと思います。

于 2017-05-21T19:35:21.027 に答える