原文地址:http://www.michaelridland.com/xamarin/rolling-mvvm-xamarin-forms/
实现自己的Mvvm for Xamarin.Forms
更新:我已经采取了这个位置的最好的部分,并放入一个框架,这是一个超简单的Mvvm框架的Xamarin.Forms,它放在了github和nuget。
在这个博客中,我给出了一个如何在Xamarin.Forms中实现自己的Mvvm的例子。 这个Mvvm是基于约定的,在这种情况下,我使用了Page / PageModel命名,而不是Model / ViewModel命名,但是如果你喜欢,你可以将其改为View / ViewModel。 这个博客写的内容基于github上可用的示例应用程序。
本例中的约定。
- 页面必须有一个相应的PageModel,命名重要,所以QuotePageModel必须有一个QuotePage
- 一个页面可以有一个没有参数的Init方法
- 页面可以有一个PageModel属性,它是视图模型
- 页面上的BindingContext将自动设置为Model
- 一个模型可以有一个接收一个对象的Init方法
- 一个模型可以具有一个ReverseInit方法,它也可以使用一个对象,当一个模型被放在一个对象上时被调用
让我们看一个页面
public class QuotePage : ContentPage
{
//按下时自动弹出
public QuotePageModel PageModel { get; set; }
public QuotePage ()
{
}
//出现时自动执行
public void Init()
{
Title = "Quote";
}
}
和相应的PageModel
public class QuotePageModel : BasePageModel
{
IDatabaseService _databaseService;
public Quote Quote { get; set; }
//数据库服务自动注入。
public QuotePageModel (IDatabaseService databaseService)
{
_databaseService = databaseService;
}
//当Model出现的时候执行
public void Init(object data)
{
Quote = data as Quote;
if (Quote == null)
Quote = new Quote ();
}
public Command Done
{
get {
return new Command (() => {
//This pops the current Page
PopPageModel();
});
}
}
}
Navigating Models
您可以使用PushPageModel方法和PopPageModel方法从视图模型中推送和弹出页面。
例如。PushPageModel<QuotePageModel>(quote);
但在您可以做到这一点之前,您需要实现和注册一个IRootNavigation服务。 在我的情况下,我使用了一个ContainerPage并将其注册为NavigationHandler
//注册根导航
var containerPage = new RootContainerPage();
TinyIoC.TinyIoCContainer.Current.Register <IRootNavigation>(containerPage);
public class RootContainerPage : MasterDetailPage, IRootNavigation
{
ContentPage _menuPage;
NavigationPage _contactNavPage, _quotesNavPage;
public RootContainerPage ()
{
_contactNavPage = new NavigationPage (BasePageModel.ResolvePageModel<ContactsRootPageModel> (null));
_quotesNavPage = new NavigationPage (BasePageModel.ResolvePageModel<QuotesRootPageModel> (null));
Detail = _contactNavPage;
_menuPage = new ContentPage ();
_menuPage.Title = "Menu";
var listView = new ListView();
listView.ItemsSource = new string[] { "Contacts", "Quotes" };
listView.ItemSelected += (sender, args) =>
{
if ((string)args.SelectedItem == "Contacts")
Detail = _contactNavPage;
if ((string)args.SelectedItem == "Quotes")
Detail = _quotesNavPage;
IsPresented = false;
};
_menuPage.Content = listView;
Master = new NavigationPage(_menuPage) { Title = "Menu" };
}
public void PushPage (Page page, BasePageModel model)
{
((NavigationPage)Detail).PushAsync (page);
}
public void PopPage ()
{
((NavigationPage)Detail).PopAsync ();
}
}
Implementing Property Changed
你会注意到我不需要实现属性更改事件,而是使用一个名为Fody / PropertyChanged的开源项目实现。 您可以从nuget安装。
控制反转/ TinyIOC
ViewPage构造函数的依赖将自动注入。 使用TinyIOC注册依赖项。
//注册数据库服务
TinyIoC.TinyIoCContainer.Current.Register <IDatabaseService,DatabaseService>();
神奇的地方在哪里?
如果你想知道神奇的实现在哪里发生,那么看看示例应用程序中的BasePageModel.cs。
平台依赖关系
此应用程序利用SQLite的平台依赖关系。
单元测试
如果我们遵循惯例,那么模型是松耦合的,容易测试。
您可以将模拟的依赖关系传递给模型并进行测试。 在这里看到一个例子:
[TestFixture]
public class ContactPageModelTests
{
[Test]
public static void CreateNewContact()
{
var container = A.Fake<IRootNavigation> ();
TinyIoC.TinyIoCContainer.Current.Register<IRootNavigation> (container);
var db = new DatabaseService (new SQLiteFactory());
var vm = new ContactPageModel (db);
vm.Init (null);
//保存到数据库
vm.Contact.Name = "Peter";
vm.Contact.Phone = "9087";
vm.Done.Execute (null);
Assert.IsTrue (vm.Contact.ContactId > 0);
//从数据库取出
var savedContact
= db.Conn.Table<Contact> ().Where ((c) => c.ContactId == vm.Contact.ContactId).FirstOrDefault ();
Assert.AreEqual ("Peter", savedContact.Name);
Assert.AreEqual ("9087", savedContact.Phone);
A.CallTo (() => container.PopPage ()).MustHaveHappened ();
}
}
请去看看github上的代码。
https://github.com/rid00z/XamarinFormsQuoteApp
Thanks
Michael