MonoTouch中的MVC簡介

來源: InfoQ  發布時間: 2010-09-17 08:10  閱讀: 4371 次  推薦: 1   原文鏈接   [收藏]  
摘要:在這篇文章中,我們將要創建另外一個簡單的應用程序,不過這次要學習下如何使用Views(視圖)和View Controllers(視圖控制器)來創建一個具有多個界面的應用程序。特別地,我們將使用UINavigationController來在應用程序里的兩個界面間進行導航。

  在這篇文章中,我們將要創建另外一個簡單的應用程序,不過這次要學習下如何使用Views(視圖)和View Controllers(視圖控制器)來創建一個具有多個界面的應用程序。特別地,我們將使用UINavigationController來在應用程序里的兩個界面間進行導航。

  在開始構建應用程序之前,讓我們簡單熟悉下iPhone應用程序所用的這個重要設計模式。

  模型-視圖-控制器(MVC)模式

  Cocoa Touch使用了一種修改版本的MVC模式來處理GUI的顯示。MVC模式(自1979年以來)已經出現很長時間了,它皆在分離顯示用戶界面所需的大量任務,并處理用戶交互。

正如名稱所蘊含的,MVC具有三個主要部分,Model(模型)、View(視圖)和Controller(控制器):

  • 模型——模型是特定于領域的數據表現形式。比如說,我們正在創建一個任務列表應用程序。你可能會有一個Task對象的集合,書寫為List<Task>。你或許把這些數據保存在數據庫、XML文件,或者甚至從Web Service中得到,不過MVC不那么關心它們是在何處/如何來持久保存的(乃至它們是什么)。相反,它特別專注于如何顯示這些數據,并處理與用戶交互的。
  • 視圖——視圖代表了數據如何實際地顯示出來。在我們這個假設的任務應用程序中,會在一個網頁(以HTML的方式)中來顯示這些任務,也會在一個WPF頁面中(以XAML的方式)來顯示,或者在一個iPhone應用程序中顯示為UITableView 。如果用戶點擊某個任務,要刪除之,那么視圖通常會觸發一個事件,或對Controller(控制器)進行一個回調。
  • 控制器——控制器是模型和視圖間的粘合劑。控制器的目的就是獲取模型中的數據,告知視圖來顯示。控制器還偵聽著視圖的事件,在用戶選中一個任務來刪除的時候,控制著任務從模型中刪除。

  通過分離顯示數據、持久化數據和處理用戶交互的職責,MVC模式有助于創建易于理解的代碼。而且,它促進了視圖和模型的解耦,以便模型能被重用。例如,在你的應用程序中,有兩個界面,基于Web的和WPF的,那么你可以在兩者中都使用同樣的模型定義代碼。

  因而,在很多MVC框架中不管具體的工作方式如何,基本原理都大致如此的。然而,在Cocoa(及Cocoa Touch)中,還是或多或少有所不同,蘋果用MVC來代表Views(視圖)、View Controller(視圖控制器)和Models(模型);但是在不同的控件中,它們卻不是完全一致的,實現的方式也不太一樣。我們將在構建示例應用程序的時候了解更多細節。

  在MonoTouch中的視圖和視圖控制器

  我之前簡短地提到,在iPhone應用程序中,你只能顯示一個窗口。不過可以包含很多界面。要做到這點,你需要為每個界面都添加一個視圖和視圖控制器。

  視圖實際上包含了所有可視化元素,比如標簽、按鈕等等,而視圖控制器處理在視圖上的實際用戶交互(通過事件),并讓你在這些事件被觸發的時候運行相應的代碼。做一個粗略的比喻的話,這就是和ASP.NET或WPF有點類似的模型,在這些模型中,你通過HTML或XAML來定義用戶界面,在后置代碼中處理事件。

  在你導向另外一個頁面的時候,就把視圖控制器放到視圖控制器堆棧中。在這個要構建的應用程序中,我們將使用Navigation View Controller(導航視圖控制器,UINavigationController)來處理不同的界面,因為它提供了一種方式可以在界面之間非常容易地導航,通過這種基于層級模式的導航欄,讓你的用戶能夠藉由視圖控制器往后和往前進行導航。

  UINavigationController 在很多內置的iPhone應用程序都能看到。例如,在查看短信列表的時候,如果你點擊其中一個,頂部導航欄將在頂部顯示一個左箭頭按鈕,讓你可以回到顯示消息列表的視圖。

  具有多個界面的Hello World應用

  現在,在概念上了解了MVC的工作原理后,讓我們實際地創建一個應用程序來實踐下。

  首先,在MonoDevelop中新建一個MonoTouch iPhone解決方案,命名為Example_HelloWorld_2(如果你忘記如何操作可以參考一下第一篇文章)。

  接著,添加兩個視圖控制器(以及相關的視圖)來服務于我們將要執行導航的應用程序中的界面。要完成這個步驟,在項目上點擊右鍵,選擇“Add : New File”。

  在Interface Builder中打開.xib文件,添加一個標簽到HelloWorldScreen上,修改文本為“Hello World”,另外添加一個文本到HelloUniverseScreen上,修改文本為“Hello Universe”,如下圖所示:

  現在,讓我們添加一個Navigation Controller到Main Window上。方式是,在Interface Builder里打開MainWindow.xib,從Library Window中拖一個Navigation Controller到Document Window上:

  Navigation Controller具有如下幾個部分:

  • Navigation Controller(導航控制器)——這是控制器的主要部分,處理導航事件,把所有東西糅合在一起。
  • Navigation Bar(導航欄)——這是顯示在頂部的工具條,讓用戶能夠看到它處于導航層級的什么位置,并可以導航回去。
  • 視圖控制器——這個部分用來控制著視圖的顯示。
  • Navigation Item(導航條目)—— 就是顯示在導航欄上的部分,實際上就是用于導航的按鈕,也顯示相應的標題

  接下來,我們添加一個Table View到Navigation Controller上,以便能創建一個用于各個界面的鏈接列表。要完成這個步驟,從Library中拖一個UITableView到Navigation Controller里的View Controller上:

  改變一下導航欄的標題。在Navigation Controller上雙擊頂部欄,鍵入“Hello World Home!”:

我必須使用Table View來包含Navigation Items嗎?

不用,你可以放任何東西到View Controller中。我們將在后面看到,在你導航到一個新界面的時候,你是調用NavigationController.PushViewController方法,并把要去的界面的View Controller傳遞給它。在用戶點擊按鈕的時候,我們能輕易地實現它。

  現在,我們獲得了所需的Navigation Controller以及相關的Table View,還需要讓兩者都可被后置代碼訪問。需要讓Navigation Controller在代碼中可訪問,以便我們能把View Controllers傳給它;也需要讓Table View在代碼中可訪問,以便我們能用要導航到的界面的名稱來填充它。

  要實現這個步驟,要為它們創建Outlets,正如我們在第一篇文章所做的那樣的。我們把Navigation Controller取名為mainNavigationController,把Table View取名為mainNavTableView。要確保在AppDelegate中創建它們。在你完成后,Connection Inspector應該看上去如下所示:

  接著,需要設置在應用程序啟動的時候顯示Navigation Controller。還記得之前在Main.cs中注釋掉的  Window.AddSubview代碼嗎?對,這就是我們現在要使用的代碼。我們把那行代碼改為如下:

 
If you have defined a view, add it here:
window.AddSubview(
this.mainNavigationController.View);

  AddSubView 很像WPF、ASP.NET等中的AddControl語句。通過把它傳遞給mainNavigationController對象的View屬性,我們就可告知窗口去顯示這個Navigation Controller的界面。

現在讓我們來運行一下應用程序,會看到下圖所示的樣子:

  這樣Navigation Controller就可顯示出來了,不過還沒有任何鏈接指向其他界面。為了設置鏈接,必須用數據來填充Table View。這就需要創建一個UITableViewDataSource 對象,把它綁定給Table View的DataSource屬性。在傳統的.NET編程中,你可以綁定任何實現了IEnumerable 接口的對象到DataSource屬性上,并設定一些數據綁定參數(比如需要顯示那些字段),這樣就實現了巧妙的數據綁定。在Cocoa中,工作方式稍微不同,正如我們看到的,在綁定上的對象需要創建新條目的時候,DataSource本身都會被調用,DataSource實際負責它們的創建。

  之前,我們實現了DataSource,現在來創建將要真正使用的條目。創建一個名為NavItem的類。在項目上點右鍵,選擇“Add : New File”,再選擇“General : Empty Class”,命名為“NavItem”,如下圖:

  現在,把如下代碼寫到里面:

 

usingSystem;
usingMonoTouch.UIKit;
namespaceExample_HelloWorld_2
{

//==============================================
///<summary>
///
///</summary>
publicclassNavItem
{

//===============================================
#region-=declarations=-
///<summary>
///Thenameofthenavitem,showsupasthelabel
///</summary>
publicstringName
{

get{returnthis._name;}
set{this._name=value;}
}
protectedstring_name;

///<summary>
///TheUIViewControllerthatthenavitemopens.Usethispropertyifyou
///wantedtoearlyinstantiatethecontrollerwhenthenavtableisbuiltout,
///otherwisejustsettheTypepropertyanditwilllazy-instantiatewhenthe
///navitemisclickedon.
///</summary>
publicUIViewControllerController
{

get{returnthis._controller;}
set{this._controller=value;}
}
protectedUIViewController_controller;

///<summary>
///TheTypeoftheUIViewController.SetthistothetypeandleavetheController
///propertyemptytolazy-instantiatetheViewControllerwhenthenavitemis
///clicked.
///</summary>
publicTypeControllerType
{

get{returnthis._controllerType;}
set{this._controllerType=value;}
}
protectedType_controllerType;

///<summary>
///alistoftheconstructorargs(ifneccesary)forthecontroller.usethisin
///conjunctionwithControllerTypeiflazy-creatingcontrollers.
///</summary>
publicobject[]ControllerConstructorArgs
{

get{returnthis._controllerConstructorArgs;}
set
{
this._controllerConstructorArgs=value;
this._controllerConstructorTypes=newType[this._controllerConstructorArgs.Length];
for(inti=0;i<this._controllerConstructorArgs.Length;i++)
{

this._controllerConstructorTypes[i]=this._controllerConstructorArgs[i].GetType();
}
}
}
protectedobject[]_controllerConstructorArgs
=newobject[]{
};

///<summary>
///Thetypesofconstructorargs.
///</summary>
publicType[]ControllerConstructorTypes
{

get{returnthis._controllerConstructorTypes;}
}
protectedType[]_controllerConstructorTypes
=Type.EmptyTypes;
#endregion
//========================================================
#region-=constructors=-
publicNavItem()
{
}
publicNavItem(stringname):
this()
{

this._name=name;
}
publicNavItem(stringname,UIViewControllercontroller):
this(name)
{

this._controller=controller;
}
publicNavItem(stringname,TypecontrollerType):
this(name)
{

this._controllerType=controllerType;
}
publicNavItem(stringname,TypecontrollerType,

object[]controllerConstructorArgs):this(name,controllerType)
{

this.ControllerConstructorArgs=controllerConstructorArgs;
}

#endregion
//=======================================================
}
}

  這個類非常簡單。我們首先來看一下其中的屬性:

  • Name——打算在Navigation Table中顯示的界面名稱。
  • Controller——界面對應的實際UIViewController
  • ControllerType——界面對應的UIVeiwController的類型,這里只是存儲著這個控制器的類型,并在需要的時候才來創建它,從而實現UIViewController的后期實例化目標。
  • ControllerConstructorArgs ——如果你的UIViewController具有任何構造參數,并且你希望傳遞它的話,就在這個屬性上設置。在我們的例子中,不需要用到這個屬性,所以現在可以忽略它,不過我在這里還是列出,因為它對于需要后期創建的類是很有用的。
  • ControllerConstructorTypes ——這是一個只讀屬性,讀取從ControllerConstructorArgs設置的類型,其用于實例化控件。

  類的剩余部分就是一些基本的構造器。

  現在,我們編寫好了NavItem,就可以來為Navigation Table View創建一個能實際使用的DataSource。創建一個名為NavTableViewDataSource的新類。做法和已經編好的NavItem的類似。

  現在,把下面代碼寫入:

 

usingSystem;
usingSystem.Collections.Generic;
usingMonoTouch.UIKit;
usingMonoTouch.Foundation;
namespaceExample_HelloWorld_2
{

//=====================================================
//
//ThedatasourceforourNavigationTableView
//
publicclassNavTableViewDataSource:UITableViewDataSource
{

///<summary>
///ThecollectionofNavigationItemsthatwebindtoourNavigationTable
///</summary>
publicList<NavItem>NavItems
{

get{returnthis._navItems;}
set{this._navItems=value;}
}
protectedList
<NavItem>_navItems;
///<summary>
///Constructor
///</summary>
publicNavTableViewDataSource(List<NavItem>navItems)
{

this._navItems=navItems;
}

///<summary>
///CalledbytheTableViewtodeterminehowmancellstocreateforthatparticularsection.
///</summary>
publicoverrideintRowsInSection(UITableViewtableView,intsection)
{
returnthis._navItems.Count;
}

///<summary>
///CalledbytheTableViewtoactuallybuildeachcell.
///</summary>
publicoverrideUITableViewCellGetCell(UITableViewtableView,NSIndexPathindexPath)
{

//----declarevars
stringcellIdentifier="SimpleCellTemplate";
//----trytograbacellobjectfromtheinternalqueue
varcell=tableView.DequeueReusableCell(cellIdentifier);
//----iftherewasn'tanyavailable,justcreateanewone
if(cell==null)
{
cell
=newUITableViewCell(UITableViewCellStyle.Default,cellIdentifier);
}

//----setthecellproperties
cell.TextLabel.Text=this._navItems[indexPath.Row].Name;
cell.Accessory
=UITableViewCellAccessory.DisclosureIndicator;
//----returnthecell
returncell;
}
}

//=====================================================
}

  快速瀏覽一下代碼。第一部分是我們的List<NavItem>集合。就是一個NavItem對象的集合。接著會看到一個基本的構造器,使用傳入的NavItems參數來初始化NavTableViewDataSource

  接著,我們重寫了RowsInSection方法。Table Views能具有多個分段,在每個分段上都可以放置條目。RowsInSection 基于section參數傳遞進來的分段索引來返回條目的數量。在我們的例子中,只具有一個分段,那么我們就返回NavItem集合的Count屬性。

  最后一個方法是GetCell,這里就是數據綁定實際發生的地方。這個方法被UITableView在構建每行數據的時候所調用。你可以利用這個方法來構建出Table中的每行數據,以顯示出你期望的內容。

  此處,我們所做的第一件事情就是通過DequeueReusableCell 方法從TableView 中得到UITableViewCell 對象。TableView 保持著一個UITableViewCell 對象的內部對象池,其基于CellIdentifiers來進行查找。它讓你可以為UITableViewCell 創建自定義模板(只用創建一次),并重用這個模板,而不是GetCell每次被調用的時候都重復創建模板,這樣就提高了性能。我們第一次調用DequeueReusableCell,它不會返回任何東西,那么就要創建一個新的UITableViewCell。之后的每次調用,UITableViewCell已經存在,就只需直接重用它就行。

  我們使用Default的單元格樣式(cell style),其只為我們提供了很少的自定義選項,所以接下來的事情就是把TextLabel.Text 屬性設置為NavItemName 屬性值。接著,我們設置Accessory 屬性來使用DisclosureIndicator,其只是一個顯示在Navigation Item右邊的簡單箭頭。

  現在,我們已經得到了創建好的UITableViewDataSource ,是時候使用它了。在MonoDevelop中打開Main.cs,把如下的代碼行添加到AppDelegate 類中:

 
protected List<NavItem> _navItems = new List<NavItem> ();

  它將保存我們的NavItem對象。

  接下來,添加如下代碼到FinishedLaunching 方法中,在Window.MakeKeyAndVisible()之后:

 
//---- create our list of items in the nav  
this._navItems.Add (new NavItem ("Hello World", typeof(HelloWorldScreen)));
this._navItems.Add (new NavItem ("Hello Universe", typeof(HelloUniverseScreen)));
//---- configure our datasource 
this.mainNavTableView.DataSource = new NavTableViewDataSource (this._navItems);

  在這里我們做的所有這些事情,就是創建兩個NavItem對象,并把它們添加到_navItems集合中。接著,我們創建一個NavTableViewDataSource 對象,把它綁定到Navigation Table View。

  把之前代碼加入后,我們的AppDelegate類看上去如下所示:

 
//ThenameAppDelegateisreferencedintheMainWindow.xibfile.
publicpartialclassAppDelegate:UIApplicationDelegate
{
protectedList
<NavItem>_navItems=newList<NavItem>();
//ThismethodisinvokedwhentheapplicationhasloadeditsUIanditsreadytorun
publicoverrideboolFinishedLaunching(UIApplicationapp,NSDictionaryoptions)
{

//Ifyouhavedefinedaview,addithere:
window.AddSubview(this.mainNavigationController.View);
window.MakeKeyAndVisible();

//----createourlistofitemsinthenav
this._navItems.Add(newNavItem("HelloWorld",typeof(HelloWorldScreen)));
this._navItems.Add(newNavItem("HelloUniverse",typeof(HelloUniverseScreen)));
//----configureourdatasource
this.mainNavTableView.DataSource=newNavTableViewDataSource(this._navItems);
returntrue;
}

//ThismethodisrequirediniPhoneOS3.0
publicoverridevoidOnActivated(UIApplicationapplication)
{
}
}

  如果你現在運行應用程序,你將看到如下所示的樣子:

  我們現在擁有了構建好的導航條目,不過在點擊它們的時候不會發生任何事情。在你點擊一個條目的時候,UITableView 會引發一個事件,不過需要我們傳遞給它一個特別的類,叫作UITableViewDelegate ,它是檢測這些事件實際處理類。要實現這個步驟,就在項目中創建一個新類,命名為“NavTableDelegate”,并寫入如下代碼:

 
usingMonoTouch.Foundation;
usingMonoTouch.UIKit;
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Reflection;
namespaceExample_HelloWorld_2
{

//=======================================================
//
//ThisclassreceivesnotificationsthathappenontheUITableView
//
publicclassNavTableDelegate:UITableViewDelegate
{

//----declarevars
UINavigationController_navigationController;
List
<NavItem>_navItems;
//======================================================
///<summary>
///Constructor
///</summary>
publicNavTableDelegate(UINavigationControllernavigationController,
List
<NavItem>navItems)
{

this._navigationController=navigationController;
this._navItems=navItems;
}
 
//=========================================================
///<summary>
///Iscalledwhenarowisselected
///</summary>
publicoverridevoidRowSelected(UITableViewtableView,NSIndexPathindexPath)
{

//----getareferencetothenavitem
NavItemnavItem=this._navItems[indexPath.Row];
//----ifthenavitemhasapropercontroller,pushitontotheNavigationController
//NOTE:wecouldalsoraiseaneventhere,tolooselycouplethis,butisn'tneccessary,
//becausewe'llonlyeverusethisthisway
if(navItem.Controller!=null)
{

this._navigationController.PushViewController(navItem.Controller,true);
//----showthenavbar(wedon'tshowitonthehomepage)
this._navigationController.NavigationBarHidden=false;
}
else
{
if(navItem.ControllerType!=null)
{

//----
ConstructorInfoctor=null;
//----ifthenavitemhasconstructoraguments
if(navItem.ControllerConstructorArgs.Length>0)
{

//----lookfortheconstructor
ctor=navItem.ControllerType.GetConstructor(navItem.ControllerConstructorTypes);
}
else
{
//----searchforthedefaultconstructor
ctor=navItem.ControllerType.GetConstructor(System.Type.EmptyTypes);
}

//----ifwefoundtheconstructor
if(ctor!=null)
{

//----
UIViewControllerinstance=null;
if(navItem.ControllerConstructorArgs.Length>0)
{

//----instancetheviewcontroller
instance=ctor.Invoke(navItem.ControllerConstructorArgs)asUIViewController;
}
else
{
//----instancetheviewcontroller
instance=ctor.Invoke(null)asUIViewController;
}

if(instance!=null)
{

//----savetheobject
navItem.Controller=instance;
//----pushtheviewcontrollerontothestack
this._navigationController.PushViewController(navItem.Controller,true);
}
else
{
Console.WriteLine(
"instanceofviewcontrollernotcreated");
}
}
else
{
Console.WriteLine(
"constructornotfound");
}
}
}
}

//==============================================
}
//==============================================
}

  這個類的第一部分是針對UINavigationControllerNavItem 對象的集合的一對聲明,下面的構造器會需要用到它們。在下面的方法——RowSelected中我們將看到,為什么需要它。

  RowSelected 在用戶點擊某行的時候UITableView 會調用它,并會返回給我們一個UITableView 的引用,以及用戶點擊條目的NSIndexPath 。首先,我們要根據NSIndexPath 來找到相應的NavItem 。接著,我們把NavItemUIViewController 傳遞給NavigationController。如果Controller 是空的,那么我們就會基于它的類型進行實例化。

  最后的兩個操作,就是我們為什么需要NavItem 集合和NavigationController引用的原因。

  現在,我們有了UITableViewDelegate,就可以來組合在一起。返回到Main.cs文件中,在AppDelegate 類中添加如下代碼行到設置DataSource 屬性的后面:

 
this.mainNavTableView.Delegate = new
NavTableDelegate (this.mainNavigationController, this._navItems);

  這樣就創建了一個新的NavTableDelegate 類,以及指向Navigation Controller 和NavItems集合的引用,且會告知mainNavTable 使用它來處理事件。

  Main.cs文件中的AppDelegate 類將會如下面代碼所示:

 
 //ThenameAppDelegateisreferencedintheMainWindow.xibfile.
publicpartialclassAppDelegate:UIApplicationDelegate
{
protectedList
<NavItem>_navItems=newList<NavItem>();
//ThismethodisinvokedwhentheapplicationhasloadeditsUIanditsreadytorun
publicoverrideboolFinishedLaunching(UIApplicationapp,NSDictionaryoptions)
{

//Ifyouhavedefinedaview,addithere:
window.AddSubview(this.mainNavigationController.View);
window.MakeKeyAndVisible();

//----createourlistofitemsinthenav
this._navItems.Add(newNavItem("HelloWorld",typeof(HelloWorldScreen)));
this._navItems.Add(newNavItem("HelloUniverse",typeof(HelloUniverseScreen)));
//----configureourdatasource
this.mainNavTableView.DataSource=newNavTableViewDataSource(this._navItems);
this.mainNavTableView.Delegate=
newNavTableDelegate(this.mainNavigationController,this._navItems);
returntrue;
}

//ThismethodisrequirediniPhoneOS3.0
publicoverridevoidOnActivated(UIApplicationapplication)
{
}
}

  現在,我們運行一下應用程序,看一下會發生什么,點擊“Hello World”你將看到如下的效果:

  注意,我們會自動地在頂部得到一個“Hello World Home”按鈕,這樣就能讓我們返回到主界面上。點擊“Hello Universe”將得到如下界面:

  恭喜你!你現在應該已經對MonoTouch iPhone應用程序中多個界面是如何工作的有了一個基本的概念,以及對UINavigationController 的工作原理有了一定了解。

  示例代碼

  查看英文原文:An Intro to the Model-View-Controller in MonoTouch

1
0
 
標簽:.NET MVC MonoTouch
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()