2

我的代码的目的是模仿 Google Docs 的功能——当用户在一台机器上键入时,他们键入的字母会出现在另一台机器上。为简单起见,每台机器在 gui 中键入文本,主类处理所有更改。

每台机器都有一个“编辑器”,并链接到总共 1 个“文件内容主题”。“文件内容主题”应该进行用户所做的更改并将更新的代码发送给所有“编辑器”

在此处输入图像描述

这从一个简单的驱动程序开始,我在其中创建一个文件内容主题,创建两个编辑器并将它们连接在一起

public class Driver {
    public static void main(String[] args) {
    FileContentSubject filecontentsubject = new FileContentSubject();
    
    Editor e1 = new Editor(filecontentsubject);
    Editor e2 = new Editor(filecontentsubject);
    
    filecontentsubject.attach(e1);
    filecontentsubject.attach(e2);  
}
}

编辑器看起来像这样(两个弹出窗口,而不是其余窗口): 在此处输入图像描述

制作编辑器的代码在这里:

import javax.swing.*;
import java.util.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;

import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;


public class Editor extends JFrame implements DocumentListener, Observer {

private FileContentSubject reference;
private Document doc;

    private JScrollPane textAreaScrollPane;
    private JTextArea textArea;
      
 
public Editor(FileContentSubject filecontentsubject) {
    super("Editor");
    initComponents();
    
    this.reference = filecontentsubject;
    textArea.getDocument().addDocumentListener(reference);
     
}
 
 
private void initComponents(){
    
    textArea = new JTextArea();
    textArea.setColumns(5);
    textArea.setLineWrap(true);
    textArea.setRows(50);
    textArea.setWrapStyleWord(true);
     
    textAreaScrollPane = new JScrollPane(textArea);
    
    
    setLocation(600,100);
    setSize(500,400);
    setVisible(true);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(textArea, BorderLayout.CENTER);
    
}


@Override
public void changedUpdate(DocumentEvent arg0) {
}


@Override
public void insertUpdate(DocumentEvent arg0) {      
    reference.insertUpdate(arg0);
}


@Override
public void removeUpdate(DocumentEvent arg0) {
    reference.removeUpdate(arg0);
}


@Override
public void update() {
    //textArea.setText(reference.getJTextArea());
    //textArea.setText(reference.temp);
    
}   
}

我的代码现在唯一的问题是在File Content Subject中,当我尝试更改通过编辑器传递的代码时。我收到很多“不能在通知中变异”和一些“空指针异常”错误。生成此代码的代码如下,是我注释掉的部分。

import java.util.ArrayList;
import java.util.List;

import javax.swing.JTextArea;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Position;


public class FileContentSubject implements Subject, DocumentListener {

private JTextArea textArea;
private Document doc;

private SubjectImpl reference;


@Override
public void attach(Observer o) {
    reference.attach(o);
}

@Override
public void detach(Observer o) {
    reference.detach(o);
}

@Override
public void notifyAllObservers() {
    reference.notifyAllObservers();
}


public FileContentSubject(){
    reference = new SubjectImpl();
    
    textArea = new JTextArea();
    textArea.setTabSize(5);
    textArea.setLineWrap(true);
    textArea.setWrapStyleWord(true);
    textArea.getDocument().addDocumentListener(this);
}

@Override
public void changedUpdate(DocumentEvent arg0) {}

@Override
public void insertUpdate(DocumentEvent arg0) {
    doc = (Document)arg0.getDocument();

    
//      try {
//          //this.textArea.setText(doc.getText(0, doc.getLength()-1));
//      } catch (BadLocationException e) {
//          // TODO Auto-generated catch block
//          e.printStackTrace();
//      }
    notifyAllObservers();
}

@Override
public void removeUpdate(DocumentEvent arg0) {
    doc = (Document)arg0.getDocument();
    
//      try {
//          this.textArea.setText(doc.getText(0, doc.getLength()-1));
//      } catch (BadLocationException e) {
//          // TODO Auto-generated catch block
//          e.printStackTrace();
//      }       
    
    notifyAllObservers();
}





public String getJTextArea(){
    return textArea.getText();
}
}

所以我的问题是,如何将文本(通过编辑器传入 DocumentEvent)传递到文件内容主题中,让它更改文件内容主题,并通知所有编辑者?


我的其他课程使这一切发生(不重要,但为了清楚起见):

主题界面

/**
 * Interface
 */
public interface Subject {

public void attach(Observer o);
public void detach(Observer o);

public void notifyAllObservers();

}

观察者界面

public interface Observer {

    public void update();
}

SubjectImpl 类

import java.util.ArrayList;
import java.util.List;


public class SubjectImpl implements Subject {

private List <Observer> observers;

public SubjectImpl(){
    observers = new ArrayList<Observer>();
}


@Override
public void attach(Observer o) {
    observers.add(o);
}

@Override
public void detach(Observer o) {
    observers.remove(o);
}

@Override
public void notifyAllObservers() {
    for(Observer o: observers){
        o.update();
    }
}
}



回答

需要防止正在编辑自身的编辑器被更新。这是通过文档属性完成的,并将所有内容都保存在字符串中而不是 JTextArea 中。非常感谢 acdcjunior 的帮助,代码答案位于所选答案的最后三个代码块上。

4

1 回答 1

2

java.lang.IllegalStateException: Attempt to mutate in notification抛出是因为在这些行中FileContentSubject

@Override
public void insertUpdate(DocumentEvent arg0) {
    doc = (Document) arg0.getDocument();

    try {
        this.textArea.setText(doc.getText(0, doc.getLength()-1));
    } catch (BadLocationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    notifyAllObservers();
}

@Override
public void removeUpdate(DocumentEvent arg0) {
    doc = (Document) arg0.getDocument();

    try {
        this.textArea.setText(doc.getText(0, doc.getLength()-1));
    } catch (BadLocationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    notifyAllObservers();
}

FileContentSubject's 的构造函数中的这一行:

textArea.getDocument().addDocumentListener(this);

表明您正在尝试textAreaDocumentListener方法内部更改 的值。

正如这里所指出的,你应该使用 aDocumentFilter来达到这个目的

这是一个使用 ' 的简单工作示例DocumentFilter(它将所有键入的字符替换为其大写版本):

//UpcaseFilter.java
//A simple DocumentFilter that maps lowercase letters to uppercase.

import javax.swing.*;
import javax.swing.text.*;

public class UpcaseFilter extends DocumentFilter {

    public void insertString(DocumentFilter.FilterBypass fb, int offset,
            String text, AttributeSet attr) throws BadLocationException {
        fb.insertString(offset, text.toUpperCase(), attr);
    }

    // no need to override remove(): inherited version allows all removals
    public void replace(DocumentFilter.FilterBypass fb, int offset, int length,
            String text, AttributeSet attr) throws BadLocationException {
        fb.replace(offset, length, text.toUpperCase(), attr);
    }

    public static void main(String[] args) {
        DocumentFilter dfilter = new UpcaseFilter();

        JTextArea jta = new JTextArea();
        JTextField jtf = new JTextField();
        ((AbstractDocument) jta.getDocument()).setDocumentFilter(dfilter);
        ((AbstractDocument) jtf.getDocument()).setDocumentFilter(dfilter);

        JFrame frame = new JFrame("UpcaseFilter");
        frame.getContentPane().add(jta, java.awt.BorderLayout.CENTER);
        frame.getContentPane().add(jtf, java.awt.BorderLayout.SOUTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 120);
        frame.setVisible(true);
    }
}

(上面的例子取自Java Swing, 2nd Edition book,第 22 章。)

这就是引发异常的原因,但您必须做更多的事情来纠正您的代码:

简而言之,问题在于您的代码中发生了这种情况:

  • 当一个Editor实例的(我们称之为edttextArea改变时......
  • ... FileContentSubject(因为它是DocumentListener)通知事件,然后通知其所有注册的观察者(edt包括!)调用他们的update()方法...
  • ...改变它的update()方法和...瞧!您正在尝试更改活动的发起人(在活动结束之前)!edttextAreatextArea

然后,您所要做的就是找到一种不通知活动发起人的方法。下面的代码使用Document.putProperty()and Document.getProperty()来实现:它分离事件的源编辑器 ( reference.detach(e);),将更改通知每个人,然后重新附加它 ( reference.attach(e);)。

(另外,我将FileContentSubject's替换JTextArea为 aStringString足够了。)

因此,这是更改后的FileContentSubject代码:

import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

public class FileContentSubject implements Subject, DocumentListener {

    // private JTextArea textArea; // removed as a String field will suffice
    // private Document doc; // should not be a field!

    private String state;

    public String getState() {
        return this.state;
    }

    private SubjectImpl reference;

    @Override
    public void attach(Observer o) {
        reference.attach(o);
    }

    @Override
    public void detach(Observer o) {
        reference.detach(o);
    }

    @Override
    public void notifyAllObservers() {
        reference.notifyAllObservers();
    }

    public FileContentSubject() {
        reference = new SubjectImpl();

//      textArea = new JTextArea();
//      textArea.setTabSize(5);
//      textArea.setLineWrap(true);
//      textArea.setWrapStyleWord(true);
//      textArea.getDocument().addDocumentListener(this);
    }

    @Override
    public void changedUpdate(DocumentEvent arg0) {
    }

    @Override
    public void insertUpdate(DocumentEvent arg0) {
        Document doc = (Document) arg0.getDocument();
        try {
            // this.textArea.setText(doc.getText(0, doc.getLength()-1));
            this.state = doc.getText(0, doc.getLength());
        } catch (BadLocationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        Editor e = (Editor) doc.getProperty("ownerEditor");
        reference.detach(e); // so it will not be notified of its own change
        notifyAllObservers(); // tell everybody else to catch up with the changes
        reference.attach(e); // reattaches the editor
    }

    @Override
    public void removeUpdate(DocumentEvent arg0) {
        Document doc = (Document) arg0.getDocument();
        try {
            // this.textArea.setText(doc.getText(0, doc.getLength()-1));
            this.state = doc.getText(0, doc.getLength());
        } catch (BadLocationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        Editor e = (Editor) doc.getProperty("ownerEditor");
        reference.detach(e); // so it will not be notified of its own change
        notifyAllObservers(); // tell everybody else to catch up with the changes
        reference.attach(e); // reattaches the editor
    }

    // public String getJTextArea() {
    //     return textArea.getText();
    // }
}

改变Editor的构造函数:

public Editor(FileContentSubject filecontentsubject) {
    super("Editor");
    initComponents();

    this.reference = filecontentsubject;
    textArea.getDocument().addDocumentListener(reference);
    textArea.getDocument().putProperty("ownerEditor", this); // <---- ADDED LINE
}

update()

@Override
public void update() {
    //textArea.setText(reference.getJTextArea());
    //textArea.setText(reference.temp);
    textArea.setText(reference.getState()); // ADDED
}

而已!

于 2013-04-14T22:14:15.083 回答