我的代码的目的是模仿Google文档的功能 - 当用户在一台计算机上键入时,他们键入的字母会显示在另一台计算机上。出于简单的目的,每台机器在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);
}
}
当我尝试更改通过编辑器传递的代码时,我的代码现在唯一的问题位于文件内容主题中。我收到很多“不能在通知中变异”,而且有些“空指针异常”错误。生成此代码的代码位于下方,是我注释掉的部分。
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在选定答案的最后三个代码块中提供帮助和代码答案。
答案 0 :(得分: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
的构造函数中:
textArea.getDocument().addDocumentListener(this);
表明您正试图从textArea
方法中更改DocumentListener
的值。
正如here指出的那样,您应该为此目的使用DocumentFilter
。
这是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书,chapter 22。)
简而言之,问题是你的代码会发生这种情况:
Editor
个实例(我们称之为edt
)textArea
更改... FileContentSubject
(因为它是DocumentListener
)注意到该事件,然后通知所有已注册的观察者(包括edt
!)来呼叫他们的update()
方法... update()
的{{1}}方法更改了edt
并且......瞧!您正尝试更改启动活动的textArea
(活动结束前)!然后你要做的就是找到一种不通知谁开始活动的方法。下面的代码使用textArea
和Document.putProperty()
:它分离事件的源编辑器(Document.getProperty()
),通知每个人的更改,并重新挂起它(reference.detach(e);
)。< / p>
(另外,我将reference.attach(e);
的{{1}}替换为FileContentSubject
,因为JTextArea
就足够了。)
所以,继续改变String
代码:
String
更改后的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
}
就是这样!