看了这篇文章,对LWUIT的 MVC 模式有了部分了解,看来以前的认识是错误的,就像文章最后那段代码
MVC 是一个优秀的体系结构设计模式。MVC 把软件系统分为三个基本部分:模型(Model),视图(View)和控制器(Controller)。如下图所示:
上图对 MVC 模式做了简单解释,明白了上图对 MVC 的各个划分部分的职责就很容易理解了。模型(Model)又称“数据模型”(Model),用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方 法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但 是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上 发生的改变。 视图(View)又称视图层,能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要 访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。 控制器(Controller)起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改 变。 Swing MVC 是公认的 MVC 设计的典范,Swing 的各个可视化组件都使用 MVC 模式来设计。在 Swing 中,M(Model,数据模型)是 JTextField 的 Document,JTabel 的 TableModel,JTree 的 TreeModel 。。。V(View,视图)则是 ComponentUI。C(控制器,Controller)却不是很明显,可以简单地将其 Event 机制看做一个 Swing 团队开发给 Swing 程序员的 C。 LWUIT 引入了 MVC 的设计思想,其实 LWUIT 就是一个 JavaME 的 MVC 框架,LWUIT 官方就把 Swing Like MVC 列在了其主要特征的第二位。了解了 Swing 的 MVC 之后,参照 LWUIT 的 API,我想你可以将 LWUIT 的 M、V、C 对号入座了吧?没错,正如你所猜到的,在 LWUIT 中,M(Model,数据模型)是 List 的 ListModel,Tabel 的 TableModel,Tree 的 TreeModel 。。。V(View,视图)则是 ComponentUI(即 API 中给的各个 LWUIT 控件)。C(控制器,Controller)却不是很明显,可以简单地将其 Event 机制看做一个 LWUIT 团队开发给 LWUIT 程序员的 C。如果你觉得这样说对控制器的理解很抽象,你可以把你的程序里每个实现了 com.sun.lwuit.events.ActionListener 接口的类当成一个控制器。 MVC 的优势就是各司其责:M 只管把数据组织好就是了;V 只管把信息完美地展现给用户;而 C 则只负责对用户事件进行监听,并做出相应处理,它是 MVC 的组织者。这让人想起来流行的 Java EE 四层体系架构:展现层、业务层、数据访问层(操作保存数据库)、持久层(数据保存为文件),各层各司其职。那是以业务模型为中心的大型应用,我们的 JavaME 只是手机客户端的应用小程序,没有那么复杂的业务逻辑要处理,也没有庞大的业务数据要持久化,没有必要分那么多层,就一个表现层足够了。也可以根据需要适 当把模型层扩展一下。如下图所示,对使用了 LWUIT 的 Java ME 应用程序中责任分工做的一个概述。
MVC 可以使我们应用程序的表现层部分更加低耦合、高内聚、灵活度更高。那么我们在 LWUIT 程序中应该怎样使用 MVC 模式呢?
1、Form 或者其他顶层容器中,由各个 LWUIT 组件构成了 View 视图层。用来展现数据,提供用户操作的图形界面。
2、一个或者一组业务对象是 Model。它们存放了 LWUIT 组件要显示的数据。它们是业务对象,因此,可以直接在业务层代码中使用,执行复杂的业务计算。为了让 LWUIT 组件实时展现业务对象的数据,我们需要让 LWUIT 组件监听业务对象,一旦业务对象发生改变,就重新根据新的数据,构建新的 LWUIT 组件的 Model,从而在 LWUIT 组件上展现最新的业务对象数据。为了让业务对象能够得到用户最新输入的数据,我们还需要将业务对象注册到 LWUIT 组件上。一旦 LWUIT 组件的数据发生了改变,就通知业务对象。业务对象根据 LWUIT 组件的 Model,修改业务对象的值。
3、在 LWUIT 各个组件上注册响应事件的监听器(控制器),以响应用户的操作。
于是 MVC 后的 LWUIT 程序的执行流程就是这个样子了:用户看到一个 LWUIT 控件构成的界面 -> 用户在界面上进行操作 -> LWUIT 控件的控制器被触发 -> 可能就激发了 ActionListener 的事件,引起业务对象自动使用 LWUIT 的 Model 数据进行更新 -> 业务对象更新数据,又会激发业务对象上的 DataChangedListener 的事件,这会引起所有监听业务对象 LWUIT 控件更新其 Model 的数据,从而改变 LWUIT 控件的显示 -> 我们可能针对业务对象,执行业务层代码,进行复杂的业务计算,从而得到新的业务对象的值,这同样会激发业务对象的 DataChangedListener 的事件,让 LWUIT 控件的 Model 得到更新,从而改变 LWUIT 控件显示的界面 -> 当然,我们也可以直接修改某些 LWUIT 组件的 Model 或者外观。
可以看出,通过业务对象和 Lwuit 控件的 Model 关联起来,在 LWUIT 应用程序中只需要“以业务对象为中心”,操纵业务对象,执行业务操作就可以了。
List 控件是最常用的 LWUIT 控件,因为我们的手机屏幕总是受限的。关于 List 我们前面的博客中有过接触——《玩转 LWUIT 之四:LWUIT 控件(中)》里介绍的 ComboBox 就是一个 List 的子类。我们写一个关于 List 的 MVC 应用。仍然使用 LWUIT 官方提供的开发指南中给的 List 使用的例子。源代码如下(当然实际运用中要复杂一些):
1 package com.defonds.lwuit;
2
3 import com.sun.lwuit.Button;
4 import com.sun.lwuit.CheckBox;
5 import com.sun.lwuit.Command;
6 import com.sun.lwuit.Component;
7 import com.sun.lwuit.Display;
8 import com.sun.lwuit.Form;
9 import com.sun.lwuit.List;
10 import com.sun.lwuit.animations.CommonTransitions;
11 import com.sun.lwuit.events.ActionEvent;
12 import com.sun.lwuit.events.ActionListener;
13 import com.sun.lwuit.events.DataChangedListener;
14 import com.sun.lwuit.layouts.FlowLayout;
15 import com.sun.lwuit.list.DefaultListModel;
16 import com.sun.lwuit.list.ListCellRenderer;
17 import com.sun.lwuit.list.ListModel;
18 import com.sun.lwuit.plaf.UIManager;
19 import com.sun.lwuit.util.Resources;
20
21 public class HelloMidlet extends javax.microedition.midlet.MIDlet implements DataChangedListener{
22
23 private Form exampleContainer; // declare a Form
24 private String[] content = { " Red " , " Blue " , " Green " , " Yellow " }; // just a data source demo,of course,the data can be from the network,storage etcetera...
25 private int contentLeng = 4 ; // remeber the size of content
26 private ListModel myListModel; // declare a ListModel
27 private ListModel myListModel2; // declare a ListModel
28 private List list; // declare a List
29 private List list2; // declare a List
30 private Button button; // declare a Button
31
32 public void startApp() {
33 // init the LWUIT Display
34 Display.init( this );
35 // Setting the application theme is discussed
36 // later in the theme chapter and the resources chapter
37 try {
38 Resources r = Resources.open( " /myresources.res " );
39 UIManager.getInstance().setThemeProps(r.getTheme( " myresources " ));
40 } catch (java.io.IOException e) {}
41
42 exampleContainer = new Form( " Form Title " ); // Create a Form
43 myListModel = new DefaultListModel(content); // Initialize a default list model with "content" inside
44 myListModel.addDataChangedListener( this ); // Adding a a data changed listener to catch user operation
45 myListModel2 = new DefaultListModel(); // Initialize a default list model with nothing inside
46 myListModel2.addItem( " White " ); // add an item to model
47 myListModel2.addItem( " Black " ); // add an item to model
48
49 button = new Button( " Move selected item down " ); // Create a Button
50 button.setAlignment(Component.CENTER); // Sets the Alignment of the button
51 button.addActionListener( new ActionListener(){
52 public void actionPerformed(ActionEvent evt) {
53 myListModel.removeItem(list.getSelectedIndex());
54 }
55 });
56
57 list = new List(myListModel); // Creating a List with "myListModel"
58 list2 = new List(myListModel2); // Creating a List with "myListModel2"
59 list.setListCellRenderer( new checkBoxRenderer()); // Setting a checkBox renderer
60 list2.setListCellRenderer( new checkBoxRenderer()); // Setting a checkBox renderer
61
62 exampleContainer.setLayout( new FlowLayout()); // Set LayoutManager
63 exampleContainer.addComponent(list); // Add a List to the Form content pane
64 exampleContainer.addComponent(button); // Add a Button to the Form content pane
65 exampleContainer.addComponent(list2); // Add a List to the Form content pane
66 exampleContainer.setTransitionOutAnimator(CommonTransitions.createFade( 400 )); // Set Transitions animation of Fade
67 exampleContainer.addCommand( new Command( " Run " , 2 )); // Add Command key
68 exampleContainer.show(); // Show it
69 }
70
71 public void pauseApp() {}
72
73 public void destroyApp( boolean unconditional) {}
74
75 // implements the method of DataChangedListener
76 public void dataChanged( int type, int index) {
77 myListModel2.addItem(content[index]); // add the deleted item to myListModel2
78 while (index < contentLeng - 1 ){
79 content[index] = content[ ++ index];
80 }
81 content[index] = null ;
82 contentLeng -- ; // refresh the content to synchronize with myListModel
83 exampleContainer.refreshTheme(); // refresh the form theme
84 }
85
86 /**
87 * Demonstrates implementation of a renderer derived from a CheckBox
88 */
89 private static class checkBoxRenderer extends CheckBox implements ListCellRenderer{
90 /** Creates a new instance of checkBoxRenderer */
91 public checkBoxRenderer(){
92 super ( "" );
93 }
94
95 // Setting the current check box text and status
96 public Component getListCellRendererComponent(List list,
97 Object value, int index, boolean isSelected) {
98 setText( "" + value);
99 if (isSelected){
100 setFocus( true );
101 setSelected( true );
102 } else {
103 setFocus( false );
104 setSelected( false );
105 }
106 return this ;
107 }
108
109 // Returning the list focus component
110 public Component getListFocusComponent(List list) {
111 setText( "" );
112 setFocus( true );
113 setSelected( true );
114 return this ;
115 }
116 }
117 }
118
HelloList 运行效果图如下所示:
这段代码的运行效果是当选择了上方 List 中的选项后,点击“Move selected item down”按钮可以把该选项转移到下方 List 中。限于美工底子有限,UI 效果有点差,这里重点演示 MVC 效果。比如连续点击两次“Move selected item down”后运行效果图如下:
用户选择用户操作后(按了“Move selected item down”按钮),控制器被触发(按钮所添加的 ActionListener),激发了 ActionListener 的事件,引起业务对象自动使用 LWUIT 的 Model 数据进行更新(就是代码中把某项 remove 掉),业务对象更新数据,又会激发业务对象上的 DataChangedListener 的事件(监听于 myListModel 的 DataChangedListener 被激发),引起所有监听业务对象的 DataChangedListener 的事件(dataChanged 方法被调用),让 LWUIT 控件的 Model 得到更新(myListModel2 被更新),从而改变 LWUIT 控件显示的界面。
从代码中可以看出,ListModel 是 Model,List、ListCellRenderer 是 View,可以把继承自 ActionListener 的 类作为 Controller。有的朋友把自己写的一个封装信息的 JavaBean 当做 Model,把 List 当做 View,而把 ListCellRenderer 当做是 Controller,其实是不对的,是对 LWUIT MVC 的误解,虽然写出来的代码运行应该是没有问题的。比如下边一段代码:
1 /**
2 * Model
3 */
4 public class Person {
5 private String name;
6 private Image photo;
7 public Person(String name, Image photo) {
8 this .name = name;
9 this .photo = photo;
10 }
11 public String getName() {
12 return name;
13 }
14 public void setName(String name) {
15 this .name = name;
16 }
17 public Image getPhoto() {
18 return photo;
19 }
20 public void setPhoto(Image photo) {
21 this .photo = photo;
22 }
23 }
24 /**
25 * View
26 */
27 public List createList(Person[] persons, int orientation, ListCellRenderer renderer) {
28 List list = new List(persons);
29 list.setListCellRenderer(renderer);
30 list.setOrientation(orientation);
31 return list;
32 }
33
34 /**
35 * Controller
36 */
37 class Renderer extends Container implements ListCellRenderer {
38 public Label name = new Label( "" );
39 public Label pic = new Label( "" );
40 public Label focus = new Label( "" );
41 public Container cnt = new Container( new BoxLayout(BoxLayout.Y_AXIS));
42 public Renderer() {
43 setLayout( new BorderLayout());
44 addComponent(BorderLayout.WEST, pic);
45 name.getStyle().setBgTransparency( 0 );
46 cnt.addComponent(name);
47 addComponent(BorderLayout.CENTER, cnt);
48 // focus用于设置List选中时的效果,这里设置List的选中项具有绿色的边框
49 focus.getStyle().setBorder(Border.createLineBorder( 1 , 0x00ff00 ));
50 }
51 public Component getListCellRendererComponent(List list, Object value,
52 int index, boolean isSelected) {
53 Person person = (Person) value;
54 name.setText(person.getName());
55 pic.setIcon(person.getPhoto());
56 return this ;
57 }
58 public Component getListFocusComponent(List list) {
59 return focus;
60 }
61 }