从JEditorPane入手,分析其中的MVC模式

JEditorPane的包级关系

java.lang.Object
    java.awt.Component
        java.awt.Container
            javax.swing.JComponent
                javax.swing.text.JTextComponent
                    javax.swing.JEditorPane
                        javax.swing.JTextPane
复制代码

JEditorPane的介绍

它是用于编辑各种内容的文本组件。该组件使用EditorKit的EditorKit来完成其行为。它有效地转化为适当的文本编辑器,用于提供给他们的内容。 编辑器在任何给定时间绑定的内容类型由当前EditorKit EditorKit确定。 如果内容设置为新的URL,则其类型用于确定应用于加载内容的EditorKit 。

model

这里的model就是document

 private Document initializeModel(EditorKit kit, URL page) {
        Document doc = kit.createDefaultDocument();
        if (pageProperties != null) {
            // transfer properties discovered in stream to the
            // document property collection.
            for (Enumeration<String> e = pageProperties.keys(); e.hasMoreElements() ;) {
                String key = e.nextElement();
                doc.putProperty(key, pageProperties.get(key));
            }
            pageProperties.clear();
        }
        if (doc.getProperty(Document.StreamDescriptionProperty) == null) {
            doc.putProperty(Document.StreamDescriptionProperty, page);
        }
        return doc;
    }
复制代码

创建了一个初始化model,然后在对page中的属性添加进model,现在就是一个有内容的Document了。

view

public View create(Element elem) {
            Document doc = elem.getDocument();
            Object i18nFlag
                = doc.getProperty("i18n"/*AbstractDocument.I18NProperty*/);
            if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
                // build a view that support bidi
                return createI18N(elem);
            } else {
                return new WrappedPlainView(elem);
            }
        }

        View createI18N(Element elem) {
            String kind = elem.getName();
            if (kind != null) {
                if (kind.equals(AbstractDocument.ContentElementName)) {
                    return new PlainParagraph(elem);
                } else if (kind.equals(AbstractDocument.ParagraphElementName)){
                    return new BoxView(elem, View.Y_AXIS);
                }
            }
            return null;
        }

复制代码

根据前面创建的document的结构形成相应视图框架

model to view

这里是默认继承父类JTextComponent的modelToView()来实现

public Rectangle modelToView(int pos) throws BadLocationException {
    return getUI().modelToView(this, pos);
}                                                                               
复制代码

将model中的给定位置转换为view坐标系中的位置。pos必须是正数。

首先先看几个继承关系

java.lang.Object
    javax.swing.plaf.ComponentUI
        javax.swing.plaf.TextUI
            javax.swing.plaf.basic.BasicTextUI
复制代码
javax.swing.JComponent
    javax.swing.text.JTextComponent
复制代码

JTextComponent中的两个方法

 public void setUI(TextUI ui) {
    super.setUI(ui);
}
复制代码
public TextUI getUI() { return (TextUI)ui; }
复制代码

这里的TextUI是ComponentUI类型,也就是这里的modelToView依然是一个继承方法,具体实现在BasicTextUi中,代码如下

public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias) throws BadLocationException {
        Document doc = editor.getDocument();
        if (doc instanceof AbstractDocument) {
            ((AbstractDocument)doc).readLock();
        }
        try {
            Rectangle alloc = getVisibleEditorRect();
            if (alloc != null) {
                rootView.setSize(alloc.width, alloc.height);
                Shape s = rootView.modelToView(pos, alloc, bias);
                if (s != null) {
                  return s.getBounds();
                }
            }
        } finally {
            if (doc instanceof AbstractDocument) {
                ((AbstractDocument)doc).readUnlock();
            }
        }
        return null;
    }
复制代码

这里的getVisibleEditorRect()其实是初始化一个rectangle,具体代码如下

protected Rectangle getVisibleEditorRect() {
        Rectangle alloc = editor.getBounds();
        if ((alloc.width > 0) && (alloc.height > 0)) {
            alloc.x = alloc.y = 0;
            Insets insets = editor.getInsets();
            alloc.x += insets.left;
            alloc.y += insets.top;
            alloc.width -= insets.left + insets.right;
            alloc.height -= insets.top + insets.bottom;
            return alloc;
        }
        return null;
    }
复制代码

BasicTextUI中的modelToView()

public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
            if (view != null) {
                return view.modelToView(pos, a, b);
            }
            return null;
        }
复制代码

这里的rootView是继承自View,View中的modelToView()

public abstract Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException;
复制代码

controller

在JEditorPane中

 public synchronized void addHyperlinkListener(HyperlinkListener listener) {
    listenerList.add(HyperlinkListener.class, listener);
}
复制代码

这里的listenerList是JEditorPane中继承JComponent得到的

JComponent中

protected EventListenerList listenerList = new EventListenerList();
复制代码

至于EventListenerList

单个实例可用于使用列表来容纳所有实例的所有侦听器(所有类型的所有类型)。 使用EventListenerList的类的责任是提供类型安全的API(最好符合JavaBean规范)以及将事件通知方法分配给列表中适当的事件侦听器的方法。 这个类提供的主要好处是它在没有听众的情况下相对便宜,并且在一个地方提供了事件监听器列表的序列化,以及一定程度的MT安全性(正确使用时)。

public synchronized <T extends EventListener> void add(Class<T> t, T l) {
        if (l==null) {
            // In an ideal world, we would do an assertion here
            // to help developers know they are probably doing
            // something wrong
            return;
        }
        if (!t.isInstance(l)) {
            throw new IllegalArgumentException("Listener " + l +
                                         " is not of type " + t);
        }
        if (listenerList == NULL_ARRAY) {
            // if this is the first listener added,
            // initialize the lists
            listenerList = new Object[] { t, l };
        } else {
            // Otherwise copy the array and add the new listener
            int i = listenerList.length;
            Object[] tmp = new Object[i+2];
            System.arraycopy(listenerList, 0, tmp, 0, i);

            tmp[i] = t;
            tmp[i+1] = l;

            listenerList = tmp;
        }
    }
复制代码

但是仅仅有listener是不够的,还需要有“fire”方法,下面是一个例子

 EventListenerList listenerList = new EventListenerList();
 FooEvent fooEvent = null;

 public void addFooListener(FooListener l) {
     listenerList.add(FooListener.class, l);
 }

 public void removeFooListener(FooListener l) {
     listenerList.remove(FooListener.class, l);
 }


 // Notify all listeners that have registered interest for
 // notification on this event type.  The event instance
 // is lazily created using the parameters passed into
 // the fire method.
 
 protected void fireFooXXX() {
     // Guaranteed to return a non-null array
     Object[] listeners = listenerList.getListenerList();
     // Process the listeners last to first, notifying
     // those that are interested in this event
     for (int i = listeners.length-2; i>=0; i-=2) {
         if (listeners[i]==FooListener.class) {
             // Lazily create the event:
             if (fooEvent == null)
                 fooEvent = new FooEvent(this);
             ((FooListener)listeners[i+1]).fooXXX(fooEvent);
         }
     }
 } 
复制代码

fire这个方法一般在实现在EventListenerList没有,但是在Component中有体现,这点可能是为了减少代码的耦合度。

到此简单的MVC模式介绍完了。

下一个问题是如何将Shape通过paint方法“画”到计算机界面。

猜你喜欢

转载自juejin.im/post/5cde4b3b51882525be132a7f