第2章探讨了如何處理Swing元件的事件生産者與消費者。我們了解了Swing元件的事件處理如何超出原始的AWT元件的事件處理功能。在本章中,我們會進一步深入Swing元件設計,來探讨稱之為Model-View-Controller(MVC)的體系地構。
3.1 了解MVC流
在1980年後首次被引入Smalltalk後,MVC體系結構是第2章所描述的觀察者模式的一種特殊形式。MVC的模型部分存放元件的狀态,并且用作Subject。MVC的視圖部分用作Subject的觀察者來顯示模型狀态。視圖建立控制器,其中定義了使用者界面如何響應使用者輸入。
3.1.1 MVC通信
圖3-1顯示MVC元素如何進行通信-在這種情況下,使用Swing的多行文本元件JTextArea。由MVC的角度來看,JTextArea作為MVC體系結構中的視圖部分。顯示在元件内部的是一個Document,他是JTextArea的模型。Document存放JTextArea的狀态資訊,例如文本内容。在JTextArea内部是一個InputMap格式的控制器。他将鍵盤輸入映射為ActionMap中的指令,并且這些指令被映射到可以通知Document的TextAction對象。當發生通知時,Document建立一個DocumentEvent并将其發送回JTextArea。
3.1.2 Swing元件的UI委托
這個例子示範了Swing世界中MVC體系結構的一個重要方面。在視圖與控制器之間需要發生複雜的互動。Swing設計将這兩個元素組合為一個委托對象來簡化設計。這導緻了每一個Swing元件具有一個負責渲染目前元件狀态并處理使用者輸入事件的UI委托。
有時,使用者事件會導緻不影響模型的視圖改變。例如,當标位置是視圖的一個屬性。模型并不關心光标位置,隻關心文本内容。影響光标位置的使用者輸入并不會傳遞給模型。相反,影響Document内容的使用者輸入(例如按下回退鍵)會被傳遞。按下回退鍵會導緻由模型中移除一個字元。正是由于這種結合,每一個Swing元件具有一個UI委托。
為了示範,圖3-2顯示了具有模型與UI委托的JTextArea組成。用于JTextArea的UI委托由TextUI接口開始,并在BasicTextUI中進行基本實作。相應的,由用于JTextArea的BasicTextAreaUI進行特例化。BasicTextAreaUI建立一個視圖,或者是一個PlainView或者是一個WrappedPlainView。在模型一側,事情則要相對簡單得多。Document接口由AbstractDocument類來實作,然後由PlainDocument進行具體化。
文本元件将會在第15章與第16章進行更為全面的解釋。正如圖3-2所示,文本元件的使用涉及到許多内容。在大多數情況下,我們并不需要處理到圖中所示的程度。然而,所有這些類都是在幕後進行作用的。MVC體系結構的UI委托部分将會在第20章我們如何自定義委托時再進行深入的讨論。
3.2 共享資料模型
因為資料模型隻存儲狀态資訊,我們可以在多個元件之間共享模型。然後每一個元件視圖可以用來修改模型。
在圖3-3所示的情況中,三個不同的JTextArea元件可以用來修改一個Document模型。如果使用者修改了一個JTextArea的内容,模型就會發生變化,使得其他的文本區域自動的反映更新的文檔狀态。對于任意的Document視圖并沒有必須手動通知其他的視圖共享模型。
資料模型的共享可以通過下面兩種方法來實作:
- 我們可以建立與任意元件相分離的資料模型并通知所有的元件使用這個資料模型。
- 我們可以先建立一個元件,由第一個元件擷取模型,然後與其他的元件進行共享。
-
package swingstudy.ch03; import java.awt.Container; import java.awt.EventQueue; import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.text.Document; public class ShareModel { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Share Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container content = frame.getContentPane(); JTextArea textarea1 = new JTextArea(); Document document = textarea1.getDocument(); JTextArea textarea2 = new JTextArea(document); JTextArea textarea3 = new JTextArea(document); content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS)); content.add(new JScrollPane(textarea1)); content.add(new JScrollPane(textarea2)); content.add(new JScrollPane(textarea3)); frame.setSize(300, 400); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
圖3-4顯示了在編輯共享文檔之後程式的樣子。注意,三個文本區域具有檢視(或是修改)文檔不同部分的功能。例如,他們并不會隻限于隻在末尾部添加文本。這是因為每一個文本區域都會分别管理位置與光标。位置與光标是視圖的屬性,而不是模型的屬性。
3.3 了解預定義的資料模型
當使用Swing元件時,了解每一個元件後面的資料模型是有益的,因為資料模型存儲他們的狀态。了解每一個元件的資料模型有助于我們将可見的元件(視圖部分)與其邏輯(資料模型部分)相分離。例如,了解了這種分離,我們就會明白為什麼JTextArea内部的光标位置不是資料模型部分,而是視圖部分。
表3-1提供了一份Swing元件,每一個元件描述資料模型的接口以及特定實作的完整清單。如果某個元件并沒有列出來,則該元件由其父類繼承了資料模型,如AbstractButton。另外,在某些情況下,多個接口會用來描述一個元件,因為資料存儲在一個模型中,而資料的選擇是在另一個模型中。例如JComboBox,MutableComboBoxModel接口由ComboBoxModel擴充而來。預定義的類不僅實作了ComboBoxModel接口,同時也實作了MutableComboBoxModel接口。
Swing元件模型
元件 | 資料模型接口 | 實作 |
AbstractButton | ButtonModel | DefaultButtonModel |
JColorChooser | ColorSelectionModel | DefaultColorSelectionModel |
JComboBox | ComboBoxModel MutableComboBoxModel | N/A DefaultComboBoxModel |
JFileChooser | ListModel | BasicDirectoryModel |
JList | ListModel ListSelectionModel | AbstractListModel DefaultListModel DefaultListSelectionModel |
JMenuBar | SingleSelectinModel | DefaultSingleSelectionModel |
JPopupMenu | SingleSelectionModel | DefaultSingleSelectionModel |
JProgressBar | BoundedRangeModel | DefaultBoundedRangeModel |
JScrollbar | BoundedRangeModel | DefaultBoundedRangeModel |
JSlider | BoundedRangeModel | DefaultBoundedRangeModel |
JSpiner | SpinnerModel | AbstractionSpinnerModel SpinnerDateModel SpinnerListModel SpinnerNumberModel |
JTabbedPane | SingleSelectionModel | DefaultSingleSelectionModel |
JTable | TableModel TableColumnModel ListSelectionModel | AbstractTableModel DefaultTableModel DefaultTableColumnModel DefaultListSelectionModel |
JTextComponent | Document | AbstractDocument PlainDocument StyledDocument DefaultStyleDocument HTMLDocument |
JToggleButton | ButtonModel | JToggleButton ToggleButtonModel |
JTree | TreeModel TreeSelectionModel | DefaultTreeModel DefaultTreeSelectionModel JTree.EmptySelectionModel |
當直接通路一個元件的模型時,如果我們修改模型,所有注冊的視圖都會被自動通知。相應的,這會使得視圖重新驗證自身來保證元件顯示他們的正确的目前狀态。這種狀态修改自動傳播的特性也是為什麼MVC如此流行的原因之一。另外,使用MVC體系結構有助于程式在随着時間與複雜性增長修改時變得更容易維護。如果我們改變可視元件庫也不再需要擔心丢失狀态資訊。
3.4 小結
本章提供了關于Swing元件如何使用修改的MVC體系結構的一個快速浏覽。我們探讨了修改的MVC體系結構的構成以及一個特定的元件,JTextArea如何映射到這個體系結構。另外,本章讨論了在元件之間資料模型的共享并且列出了所有用于不同Swing元件的資料模型。
在第4章,我們将會開始了解構成Swing庫的單個元件。另外,當我們檢測Swing庫中的基本JComponet元件時我們會探讨Swing元件類層次結構。