一個好的用戶界面(GUI)的設(shè)計通常可以在現(xiàn)實世界找到相應(yīng)的表現(xiàn)。例如,如果在您的面前擺放著一個類似于電腦鍵盤按鍵的一個簡單的按鈕,然而就是這么簡單的一個按鈕,我們就可以看出一個GUI設(shè)計的規(guī)則,它由兩個主要的部分構(gòu)成,一部分使得它具有了按鈕應(yīng)該具有的動作特性,例如可以被按下。另外一部分則負(fù)責(zé)它的表現(xiàn),例如這個按鈕是代表了A還是B。?
看清楚這兩點你就發(fā)現(xiàn)了一個很強大的設(shè)計方法,這種方法鼓勵重用reuse,而不是重新設(shè)計redesign。你發(fā)現(xiàn)按鈕都有相同的機理,你只要在按鈕的頂上噴上不同的字母便能制造出“不同”的按鈕,而不用為了每個按鈕而重新設(shè)計一份圖紙。這大大減輕了設(shè)計工作的時間和難度。
如果您把上述設(shè)計思想應(yīng)用到軟件開發(fā)領(lǐng)域,那么取得相似的效果一點都不讓人驚奇。一個在軟件開發(fā)領(lǐng)域應(yīng)用的非常廣泛的技術(shù)Model/View/Controller(MVC)便是這種思想的一個實現(xiàn)。
這當(dāng)然很不錯,但是或許您又開始疑惑這和java基礎(chǔ)類JFC(Java?Foundation?Class)中的用戶界面設(shè)計部分(Swing)又有什么關(guān)系呢?好的,我來告訴你。
盡管MVC設(shè)計模式通常是用來設(shè)計整個用戶界面(GUI)的,JFC的設(shè)計者們卻獨創(chuàng)性的把這種設(shè)計模式用來設(shè)計Swing中的單個的組件(Component),例如表格Jtable,樹Jtree,組合下拉列表框JcomboBox等等等等。這些組件都有一個Model,一個View,一個Controller,而且,這些model,view,controller可以獨立的改變,就是當(dāng)組件正在被使用的時候也是如此。這種特性使得開發(fā)GUI界面的工具包顯得非常的靈活。
MVC設(shè)計模式把一個軟件組件區(qū)分為三個不同的部分,model,view,controller。

Model是代表組件狀態(tài)和低級行為的部分,它管理著自己的狀態(tài)并且處理所有對狀態(tài)的操作,model自己本身并不知道使用自己的view和controller是誰,系統(tǒng)維護著它和view之間的關(guān)系,當(dāng)model發(fā)生了改變系統(tǒng)還負(fù)責(zé)通知相應(yīng)的view。
View代表了管理model所含有的數(shù)據(jù)的一個視覺上的呈現(xiàn)。一個Model可以有一個以上的View,但是Swing中卻很少有這樣的情況。
Controller管理著model和用戶之間的交互的控制。它提供了一些方法去處理當(dāng)model的狀態(tài)發(fā)生了變化時的情況。
使用鍵盤上的按鈕的例子來說明一下:Model就是按鈕的整個機械裝置,View/Controller就是按鈕的表面部分。
下面的圖解釋了如何把一個JFC開發(fā)的用戶界面分為model,view,controller,注意,view/Controller被合并到了一起,這是MVC設(shè)計模式通常的用法,它們提供了組件的用戶界面(UI)。
?
用Button的例子詳細(xì)說明
為了更好的理解MVC設(shè)計模式和Swing用戶界面組件之間的關(guān)系,讓我們更加深入的進(jìn)行分析。我將采用最常見的組件button來說明。
我們從model來開始。
Model
一個按鈕的model所應(yīng)該具備的行為由一個接口ButtonModel來完成。一個按鈕model實例封裝了其內(nèi)部的狀態(tài),并且定義了按鈕的行為。它的所有方法可以分為四類:
1、查詢內(nèi)部狀態(tài)
2、操作內(nèi)部狀態(tài)
3、添加和刪除事件監(jiān)聽器
4、發(fā)生事件
其他的用戶界面組件有它們各自的與組件相關(guān)的Model,但是所有的組件Model都提供這四類方法。
View?&?Controller
上面的圖中講述一個按鈕的view/controller由一個接口ButtonUI完成。如果一個類實現(xiàn)了這個接口,那么它將會負(fù)責(zé)創(chuàng)建一個用戶界面,處理用戶的操作。它的所有方法可以被分為三大類:
1、繪制Paint
2、返回幾何類型的信息
3、處理AWT事件
其他用戶界面組件有他們自己的組件相關(guān)的View/Controller,但是他們都提供上述三類方法。
程序員通常并不會直接和model以及view/controller打交道,他們通常隱藏于那些繼承自java.awt.Component的組件里面了,這些組件就像膠水一樣把MVC三者合三為一。也正是由于這些繼承的組件對象,一個程序員可以很方便的混合使用Swing組件和AWT組件,然后,我們知道,Swing組件有很多都是直接繼承自相應(yīng)的AWT組件,它能提供比AWT組件更加方便易用的功能,所以通常情況下,我們沒有必要混合使用兩者。
一個實例
現(xiàn)在我們已經(jīng)明白了Java類與MVC各個部分的對應(yīng)關(guān)系,我們可以更加深入一點去分析問題了。下面我們將要講述一個小型的使用MVC模式開發(fā)的例子。因為JFC十分的復(fù)雜,我只能把我的例子局限于一個用戶界面組件里面(如果你猜是一個按鈕的例子,那么你對了!)
讓我們來看看這個例子的所有部分吧。
Button類
最顯而易見的開始的地方就是代表了按鈕組件本省的代碼,因為這個類是大部分程序員會接觸的。
就像我前面提到的,按鈕用戶界面組件類實際上就是model和view/controller的之間的黏合劑。每個按鈕組件都和一個model以及一個controller關(guān)聯(lián),model定義了按鈕的行為,而view/controller定義了按鈕的表現(xiàn)。而應(yīng)用程序可以在任何事件改變這些關(guān)聯(lián)。讓我們看看得以實現(xiàn)此功能的代碼。
public?void?setModel(ButtonModel?buttonmodel)
{
if?(this.buttonmodel?!=?null)
{
this.buttonmodel.removeChangeListener(buttonchangelistener);
this.buttonmodel.removeActionListener(buttonactionlistener);
buttonchangelistener?=?null;
buttonactionlistener?=?null;
}
this.buttonmodel?=?buttonmodel;
if?(this.buttonmodel?!=?null)
{
buttonchangelistener?=?new?ButtonChangeListener();
buttonactionlistener?=?new?ButtonActionListener();
this.buttonmodel.addChangeListener(buttonchangelistener);
this.buttonmodel.addActionListener(buttonactionlistener);
}
updateButton();
}
public?void?setUI(ButtonUI?buttonui)
{
if?(this.buttonui?!=?null)
{
this.buttonui.uninstallUI(this);
}
this.buttonui?=?buttonui;
if?(this.buttonui?!=?null)
{
this.buttonui.installUI(this);
}
updateButton();
}
public?void?updateButton()
{
invalidate();
}?
在進(jìn)入下一節(jié)之前,你應(yīng)該多花一些時間來仔細(xì)閱讀一下Button類的源代碼。
ButtonModel類
ButtonModel維護著三種類型的狀態(tài)信息:是否被按下(pressed),是否“武裝上了”(armed),是否被選擇(selected)。它們都是boolean類型的值。
一個按鈕被按下(pressed)是指當(dāng)鼠標(biāo)在按鈕上面的時候,按下鼠標(biāo)但是還沒有松開鼠標(biāo)按鈕的狀態(tài),及時用戶此時把鼠標(biāo)拖拽到按鈕的外面也沒有改變這種狀態(tài)。
一個按鈕是否“武裝了”(armed)是指按鈕被按下,并且鼠標(biāo)還在按鈕的上面。
一些按鈕還可能被選擇(selected),這種狀態(tài)通過重復(fù)的點擊按鈕取得true或者false的值。
下面的代碼是狀態(tài)pressed的一個缺省的實現(xiàn)。狀態(tài)armed以及selected實現(xiàn)的代碼與之類似。ButtonModel類應(yīng)該被繼承,這樣可以覆蓋缺省的狀態(tài)定義,實現(xiàn)有個性的按鈕。
private?boolean?boolPressed?=?false;
public?boolean?isPressed()
{
return?boolPressed;
}
public?void?setPressed(boolean?boolPressed)
{
this.boolPressed?=?boolPressed;
fireChangeEvent(new?ChangeEvent(button));
}?
按鈕的模型button?model還負(fù)責(zé)通知其他對象(事件監(jiān)聽器)它們所感興趣的事件。從下面的代買中我們可以看出當(dāng)按鈕的轉(zhuǎn)臺發(fā)生改變的時候就會發(fā)出一個ChangeEvent。下面就是代碼:
private?Vector?vectorChangeListeners?=?new?Vector();
public?void?addChangeListener(ChangeListener?changelistener)
{
vectorChangeListeners.addElement(changelistener);
}
public?void?removeChangeListener(ChangeListener?changelistener)
{
vectorChangeListeners.removeElement(changelistener);
}
protected?void?fireChangeEvent(ChangeEvent?changeevent)
{
Enumeration?enumeration?=?vectorChangeListeners.elements();
while?(enumeration.hasMoreElements())
{
ChangeListener?changelistener?=(ChangeListener)enumeration.nextElement();
changelistener.stateChanged(changeevent);
}
}
在進(jìn)入下一節(jié)之前,你應(yīng)該多花一些時間來仔細(xì)閱讀一下ButtonModel類的源代碼。?
ButtonUI類
按鈕的view/controller是負(fù)責(zé)構(gòu)建表示層的。缺省情況下它僅僅是用背景色畫一個矩形而已,他們的子類繼承了他們并且覆蓋了繪制的方法,使得按鈕可以有許多不同的表現(xiàn),例如MOTIF,Windows?95,Java樣式等等。
public?void?update(Button?button,?Graphics?graphics)
{
}
public?void?paint(Button?button,?Graphics?graphics)
{
Dimension?dimension?=?button.getSize();
Color?color?=?button.getBackground();
graphics.setColor(color);
graphics.fillRect(0,?0,?dimension.width,?dimension.height);?
}
ButtonUI類并不自己處理AWT事件,他們會使用一個定制的事件監(jiān)聽器把低級的AWT事件翻譯為高級的Button模型期望的語義事件。下面就是安裝/卸載事件監(jiān)聽器的代碼。
private?static?ButtonUIListener?buttonuilistener?=?null;
public?void?installUI(Button?button)
{
button.addMouseListener(buttonuilistener);
button.addMouseMotionListener(buttonuilistener);
button.addChangeListener(buttonuilistener);
}
public?void?uninstallUI(Button?button)
{
button.removeMouseListener(buttonuilistener);
button.removeMouseMotionListener(buttonuilistener);
button.removeChangeListener(buttonuilistener);
}
View/Controller實際上就是一些方法。他們不維護任何自己的狀態(tài)信息。因此,許多按鈕的實例可以共享一個ButtonUI實例。ButtonUI是通過在方面的參數(shù)列表里面加上按鈕的引用來區(qū)分各個不同的按鈕。
同樣,希望你能多花一些時間來看看ButtonUI類,然后咱們進(jìn)入下一節(jié)。
ButtonUIListener類
ButtonUIListener類可以幫助Button類去轉(zhuǎn)變鼠標(biāo)或者鍵盤的輸入為對按鈕模型的操作。這個監(jiān)聽器類實現(xiàn)了:MouseListener,MouseMotionListener,ChangeListener接口,并且處理一下事件:
public?void?mouseDragged(MouseEvent?mouseevent)
{
Button?button?=?(Button)mouseevent.getSource();
ButtonModel?buttonmodel?=?button.getModel();
if?(buttonmodel.isPressed())
{
if?(button.getUI().contains(button,?mouseevent.getPoint()))
{
buttonmodel.setArmed(true);
}
else
{
buttonmodel.setArmed(false);
}
}
}
public?void?mousePressed(MouseEvent?mouseevent)
{
Button?button?=?(Button)mouseevent.getSource();
ButtonModel?buttonmodel?=?button.getModel();
buttonmodel.setPressed(true);
buttonmodel.setArmed(true);
}
public?void?mouseReleased(MouseEvent?mouseevent)
{
Button?button?=?(Button)mouseevent.getSource();
ButtonModel?buttonmodel?=?button.getModel();
buttonmodel.setPressed(false);
buttonmodel.setArmed(false);
}
public?void?stateChanged(ChangeEvent?changeevent)
{
Button?button?=?(Button)changeevent.getSource();
button.repaint();
}
總結(jié)
我希望你能按照上面講述的方法去做。如果不能,那么所有的努力都將白費。這個例子以及Swing用戶界面組件的好處在于你不用去花時間去弄明白他們底層是如何設(shè)計實現(xiàn)的就可以很方便的使用他們了。他們都提供了缺省的model以及view/controller,然后,當(dāng)你自己做組件的時候,你會發(fā)現(xiàn)上面的思想的強大之處。
posted on 2005-10-15 16:55
Sung 閱讀(202)
評論(0) 編輯 收藏 所屬分類:
Thinking in Design