Caliburn.Micro开发框架介绍 -- Windows phone

2021-03-15 03:29

阅读:495

Caliburn.Micro开发框架介绍

         Caliburn是一套基于XAML的开发框架,它小巧而强大。利用它不但能提高开发效率,还可以提高XAML程序开发的可维护行、可扩展性和可测试性。Caliburn.Micro则是专门针对Windows phone开发的版本。

MVVM简介

         MVVM源于微软的软件开发模式,可以粗略的认为它是MVC模式的发展,原来Controller的职能被拆分,其中值转换器(Value Converter)和绑定器(binder)已经由框架实现,程序员可以更关注在逻辑实现上。MVVM的开发基于事件驱动,实现UI层和逻辑层的分离,从而使UI设计人员和程序员各施其职。MVVM中的View Model在Model和View之间扮演着值转换器的角色,把Model的数据交给View去绑定,把View的数据提交给Model;同时也要实现mediator设计模式,成为View和Model之间的逻辑协调者。

Caliburn.Micro简介

         Caliburn.Micro使用各种的配置和约定使得代码工作变得简洁。比如:你无需使用ViewModelLocator为某个View定位它的View Model,在Caliburn.Micro中只需要按照约定把View的名字加上后缀ViewModel,就是它的View Model的名字,如:MainPage和MainPageViewModel。

Caliburn.Micro自动把ViewModel绑定到View的DataContext。如果ViewModel的属性名和控件的名称相同,那么就会自动绑定上。如果该属性的值发生变化,控件的也能得到更新。

此外,Caliburn.Micro还为Windows phone的特性提供辅助,例如:tombstone的管理,应用程序生命周期和launcher。

当然,你也可以自定义各种约定。

准备工作

下载

Caliburn.Micro可以通过Visualstudio的NuGet工具获得,也可以在其官网下载发布包、源代码和例子。http://caliburnmicro.codeplex.com/

入口bootstrapper

Bootstrapper是Caliburn.Micro的入口,所有的ViewModel必须在这个类里注册,否则Caliburn.Micro无法为你的View和ViewModel建立关联。

如果需要自定义命名约定,也是在这个类里定义。

我们新建一个WP8工程,先删除默认创建的MainPage.xaml,创建Views目录,在Views目录下创建MainPage.xaml,创建ViewModels目录,在ViewModels下创建MainPageViewModel.cs类,修改WMAppManifest.xml中的起始页面为Views/MainPage.xaml。

在工程的根目录下创建bootstrapper.cs,其内容如下。

  1.  
    public class AppBootstrapper : PhoneBootstrapper
  2.  
    {
  3.  
    PhoneContainer container;
  4.  
     
  5.  
    protected override void Configure()
  6.  
    {
  7.  
    container = new PhoneContainer(RootFrame);
  8.  
     
  9.  
    container.RegisterPhoneServices();
  10.  
    //注册所有ViewModel
  11.  
    container.PerRequest();
  12.  
     
  13.  
    AddCustomConventions();
  14.  
    }
  15.  
     
  16.  
    static void AddCustomConventions()
  17.  
    {
  18.  
    //ellided 自定义命名约定
  19.  
    }
  20.  
     
  21.  
    protected override object GetInstance(Type service, string key)
  22.  
    {
  23.  
    return container.GetInstance(service, key);
  24.  
    }
  25.  
     
  26.  
    protected override IEnumerableobject> GetAllInstances(Type service)
  27.  
    {
  28.  
    return container.GetAllInstances(service);
  29.  
    }
  30.  
     
  31.  
    protected override void BuildUp(object instance)
  32.  
    {
  33.  
    container.BuildUp(instance);
  34.  
    }
  35.  
    }

 

初始化bootstrapper

         修改App.xml,初始化bootstrapper。

  1.  
    Application
  2.  
    x:Class="CSH.IntelliHIS.WP8.App"
  3.  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4.  
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  5.  
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
  6.  
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
  7.  
    xmlns:local="clr-namespace:CSH.IntelliHIS.WP8">
  8.  
     
  9.  
  10.  
    Application.Resources>
  11.  
    local:AppBootstrapper x:Key="bootstrapper" />
  12.  
    Application.Resources>
  13.  
     
  14.  
    Application>

 

         修改App.xml.cs,默认的初始化代码已经不需要了。

  1.  
    public partial class App : Application
  2.  
    {
  3.  
    public App()
  4.  
    {
  5.  
    InitializeComponent();
  6.  
    }
  7.  
    }

 

命名约定(naming convention)

         命名约定让Caliburn.Micro能自动关联起View和View Model,如果我们运行工程,浏览MainPage页面,则MainPageViewModel自动被实例化。

接下来就可以看看Caliburn.Micro是如何扮演值转换器的角色,看它是如何在View和ViewModel之间传递和转换值,以便View绑定这些值。

我们在ViewModel里增加一个叫Name的属性(Property)。

  1.  
    public class MainPageViewModel: PropertyChangedBase
  2.  
    {
  3.  
    public MainPageViewModel()
  4.  
    {
  5.  
    Name = "Matteo";
  6.  
    }
  7.  
    private string name;
  8.  
    public string Name
  9.  
    {
  10.  
    get { return name; }
  11.  
    set
  12.  
    {
  13.  
    name = value;
  14.  
    NotifyOfPropertyChange(() => Name);
  15.  
    }
  16.  
    }
  17.  
    }

 

         在View里增加一个文本控件,使用和这个属性相同的名字,值就会自动绑定上去。(Caliburn.Micro也支持原有的binding语法)

x:Name="Name"/>

         注意,ViewModel继承了PropertyChangedBase类,在Name属性被修改的时候,调用NotifyOfPropertyChange方法发出通知,这使得Name属性被修改时,View里的绑定控件TextBlock能自动地更新。

行为(Actions)

命令(Commands)

         Caliburn.Micro使用一种叫做行为的机制,使得ViewModel响应View的事件。它很简单。

         View控件定义了名字。

         ViewModel的方法只要使用相同名字就会得到调用。

public void ShowNameAction()

{

         MessageBox.Show("Clicked");

}

         当然,也支持自定义调用方法。需先引用

xmlns:i=”clrnamespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity”xmlns:cal=”clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro”

View里这样定义

  1.  
  2.  
  3.  
    "Click">
  4.  
    "ShowNameAction" />
  5.  
  6.  
  7.  

 

Caliburn.Micro也有简写方式,但Blend不支持。使用Message.Attach,在Event和Action关键字后使用你想要的事件名和方法名。

控制行为

         Caliburn.Micro有一个约定,如果一个控件绑定在一个属性上,则另一个属性能很容易控制它的命令能否被执行,只需要这个新属性的名字是那个绑定属性的名字加上Can关键字。例如:一个Button的名字和ViewModel的属性名字叫Execute,则ViewModel里叫CanExecute的属性能控制该Button能否被点击。

         还有更复杂一点的情况,一个控件控制另一个控件能否被执行。例如:CheckBox控制Button能否被激活。

  1.  
  2.  
  3.  
    "IsEnabled" Content="IsEnabled" />
  4.  

 

CheckBox绑定到isEnabled,Button绑定到ShowName,CheckBox值改变的同时通知Button的控制属性CanShowName,从而实现关联控制。

  1.  
    public class MainPageViewModel: PropertyChangedBase
  2.  
    {
  3.  
    private bool isEnabled;
  4.  
    public bool IsEnabled
  5.  
    {
  6.  
    get { return isEnabled; }
  7.  
    set
  8.  
    {
  9.  
    isEnabled = value;
  10.  
    NotifyOfPropertyChange(() => IsEnabled);
  11.  
    NotifyOfPropertyChange(() => CanShowName);
  12.  
    }
  13.  
    }
  14.  
    public bool CanShowName
  15.  
    {
  16.  
    get { return IsEnabled; }
  17.  
    }
  18.  
    public void ShowName()
  19.  
    {
  20.  
    MessageBox.Show("Clicked");
  21.  
    }
  22.  
    }

 

集合(Collections)

         View的列表控件和ViewModel的集合属性如果同名也能实现绑定,Selected关键字前缀加上属性名的单数形式就能实现选中控制。

         例如:ListBox控件使用复数形式的Items名称

  1.  
    "Items">
  2.  
  3.  
  4.  
  5.  
    "{Binding}" />
  6.  
  7.  
  8.  
  9.  

 

ViewModel的属性同名Items实现绑定。

  1.  
    private ObservableCollectionstring> items;
  2.  
    public ObservableCollectionstring> Items
  3.  
    {
  4.  
    get { return items; }
  5.  
    set
  6.  
    {
  7.  
    items = value;
  8.  
    NotifyOfPropertyChange(() => Items);
  9.  
    }
  10.  
    }

 

         SelectedItem实现选中控制。(注意:Selected+单数形式Item)

  1.  
    private string selectedItem;
  2.  
    public string SelectedItem
  3.  
    {
  4.  
    get { return selectedItem; }
  5.  
    set
  6.  
    {
  7.  
    selectedItem = value;
  8.  
    NotifyOfPropertyChange(() => SelectedItem);
  9.  
    MessageBox.Show(value);
  10.  
    }
  11.  
    }

 

依赖注入(Dependency Injection)

         Caliburn.Micro有依赖注入的功能。用户类要在依赖注入时被使用到,就要在Bootstrapper的Configure函数向依赖注入容器注册,Caliburn.Micro既提供每次创建新实例的模式,也提供单一实例模式。同时Caliburn.Micro会自动注册一些系统工具类。

  1.  
    protected override void Configure()
  2.  
    {
  3.  
    container = new PhoneContainer(RootFrame);
  4.  
    container.RegisterPhoneServices();
  5.  
    //注册,非单一实例模式
  6.  
    container.PerRequest();
  7.  
    container.PerRequest();
  8.  
    //注册单一实例模式
  9.  
    container.Singleton();
  10.  
    AddCustomConventions();
  11.  
    }

 

在ViewModel被实例化时,如果其构造函数带有某种类型接口为参数,则依赖注入容器会提供它们的实例。例子如下。

导航(Navigation)

         Windows Phone使用NavigationService来完成页面间跳转,ViewModel如果要跳转页面,应利用依赖注入得到它的实例。

 

  1.  
    public class MainPageViewModel : PropertyChangedBase
  2.  
    {
  3.  
    private readonly INavigationService navigationService;
  4.  
    public MainPageViewModel(INavigationService navigationService)
  5.  
    {
  6.  
    this.navigationService = navigationService;
  7.  
    }
  8.  
    public void GoToPage2()
  9.  
    {
  10.  
    navigationService.UriFor()
  11.  
    .Navigate();
  12.  
    }
  13.  
    }

 

注入导航参数

         如果导航时带有参数,Caliburn.Micro会自动把参数值注入到同名属性。

例如:跳转时带上Name参数

  1.  
    public void GoToPage2()
  2.  
    {
  3.  
    navigationService.UriFor()
  4.  
    .WithParam(x => x.Name, "Matteo")
  5.  
    .Navigate();
  6.  
    }

其导航字符串为/Page2View.xaml?Name=Matteo,则Page2的同名属性Name在实例化时就会被注入值。如果有控件绑定了该属性,则导航到该页面时就能显示出值。

  1.  
    public class Page2ViewModel: PropertyChangedBase
  2.  
    {
  3.  
    private string name;
  4.  
    public string Name
  5.  
    {
  6.  
    get { return name; }
  7.  
    set
  8.  
    {
  9.  
    name = value;
  10.  
    NotifyOfPropertyChange(() => Name);
  11.  
    }
  12.  
    }
  13.  
    }

墓碑(Tombstoning)

         应用程序被切换至后台时,如果有值需要暂存,Caliburn.Micro提供了很简洁的解决方法,这比普通XAML程序处理生命周期里各个事件要容易的多。我们只需要创建一个StorageHandler的继承类,T是你要保存临时值的ViewModel。

         例如,在程序切换至后台时,需要暂存View里名为Name的文本框的值,即暂存MainPageViewModel的Name属性。

  1.  
    public class MainPageModelStorage: StorageHandlerMainPageViewModel>
  2.  
    {
  3.  
    public override void Configure()
  4.  
    {
  5.  
    Property(x => x.Name)
  6.  
    .InPhoneState();
  7.  
    }
  8.  
    }

         InPhoneState()函数把值暂存在内存中,程序退出后就不存在。相对应的InAppSettings()则会持久保存。

深度链接(Deep Links)

         Windows phone支持跳过首页的实例化,通过URL链接直接打开应用程序里的某个页面,并可携带参数。Caliburn.Micro的依赖注入和属性注入机制能保证深度链接的正常打开。

         例如:

        

        

         该ViewModel的按钮事件动态创建一个Tile,点击该Tile打开深度链接,这里的链接使用了首页。

  1.  
    public class MainPageViewModel: PropertyChangedBase
  2.  
    {
  3.  
    private string name;
  4.  
    public string Name
  5.  
    {
  6.  
    get { return name; }
  7.  
    set
  8.  
    {
  9.  
    name = value;
  10.  
    NotifyOfPropertyChange(() => Name);
  11.  
    }
  12.  
    }
  13.  
    public void CreateTile()
  14.  
    {
  15.  
    ShellTileData tile = new StandardTileData
  16.  
    {
  17.  
    Title = "Test",
  18.  
    };
  19.  
    ShellTile.Create(new Uri("/Views/MainPage.xaml?Name=Matteo",
  20.  
    UriKind.Relative), tile);
  21.  
    }
  22.  
    }

生命周期的事件

         ViewModel并非WP8页面的继承类,为了能在ViewModel里响应页面基类PhoneApplicationPage生命周期的事件,Caliburn.Micro介绍了Screen类。当一个应用初次打开,依次会触发下列事件。

  1.  
    public class MainPageViewModel: Screen
  2.  
    {
  3.  
    protected override void OnViewAttached(object view, object context)
  4.  
    {
  5.  
    base.OnViewAttached(view, context);
  6.  
    Debug.WriteLine("OnViewAttached:ViewModel和View建立关联时被调用");
  7.  
    }
  8.  
    protected override void OnInitialize()
  9.  
    {
  10.  
    base.OnInitialize();
  11.  
    Debug.WriteLine("OnInitialize:初始化结束");
  12.  
    }
  13.  
    protected override void OnViewReady(object view)
  14.  
    {
  15.  
    base.OnViewReady(view);
  16.  
    Debug.WriteLine("OnViewReady:初始化结束,准备绘制");
  17.  
    }
  18.  
    protected override void OnViewLoaded(object view)
  19.  
    {
  20.  
    base.OnViewLoaded(view);
  21.  
    Debug.WriteLine("OnViewLoaded:页面和子控件全部初始化完成");
  22.  
    }
  23.  
    protected override void OnActivate()
  24.  
    {
  25.  
    base.OnActivate();
  26.  
    Debug.WriteLine("OnActivate:切换成为当前窗口");
  27.  
    }
  28.  
    protected override void OnDeactivate(bool close)
  29.  
    {
  30.  
    base.OnDeactivate(close);
  31.  
    Debug.WriteLine("OnDeactivate:切换至后台");
  32.  
    }
  33.  
    }

         Screen的OnActivate和OnDeactivate事件是最常使用的事件,它们分别对应了页面OnNavigatedTo和OnNavigatedFrom事件。值得注意的是,ViewModel加载数据应尽量避免在构造函数和初始化函数中实行,而应该在OnActivate中。

消息传递(Messaging)

         Caliburn.Micro为应用程序内已经打开的多个ViewModel之间提供消息传递的功能。

例子:从页面1打开页面2,点击页面2的SendMessage Button向页面1发送一个消息,回退到页面1就会看到这个值显示在文本框里。

如下定义的消息类

  1.  
    public class SampleMessage
  2.  
    {
  3.  
    public string Name { get; set; }
  4.  
    public SampleMessage(string name)
  5.  
    {
  6.  
    Name = name;
  7.  
    }
  8.  
    }

消息接收者需要实现接口IHandle,本例T就是SampleMessage,并需要接受消息接口IEventAggregator的注入。

注意:消息接收者需要调用IEventAggregator的Subscribe函数订阅消息。

  1.  
    public class MainPageViewModel: Screen, IHandleSampleMessage>
  2.  
    {
  3.  
    private readonly IEventAggregator eventAggregator;
  4.  
    private readonly INavigationService navigationService;
  5.  
     
  6.  
    private string name;
  7.  
    public string Name
  8.  
    {
  9.  
    get { return name; }
  10.  
    set
  11.  
    {
  12.  
    name = value;
  13.  
    NotifyOfPropertyChange(() => Name);
  14.  
    }
  15.  
    }
  16.  
     
  17.  
    public MainPageViewModel(IEventAggregator eventAggregator, INavigationService
  18.  
    navigationService)
  19.  
    {
  20.  
    this.eventAggregator = eventAggregator;
  21.  
    this.navigationService = navigationService;
  22.  
    eventAggregator.Subscribe(this);
  23.  
    }
  24.  
     
  25.  
    public void GoToPage2()
  26.  
    {
  27.  
    navigationService.UriFor().Navigate();
  28.  
    }
  29.  
     
  30.  
    public void Handle(SampleMessage message)
  31.  
    {
  32.  
    Name = message.Name;
  33.  
    }
  34.  
    }

         消息发送者只需要接受IEventAggregator的注入,并用它的Publish方法发送消息。

  1.  
    public class SecondPageViewModel: Screen
  2.  
    {
  3.  
    private readonly IEventAggregator eventAggregator;
  4.  
    public SecondPageViewModel(IEventAggregator eventAggregator)
  5.  
    {
  6.  
    this.eventAggregator = eventAggregator;
  7.  
    }
  8.  
    public void SendMessage()
  9.  
    {
  10.  
    eventAggregator.Publish(new SampleMessage("Matteo"));
  11.  
    }
  12.  
    }

 

View和ViewModel之间的通讯

         View类只要得到IEventAggregator的实例也能在Views和ViewModels之间接收或发送消息。

         此时我们需要改造Bootstrapper类,要得到其container变量,就可以调用其GetAllInstances或GetInstance得到IEventAggregator。

         例如:

  1.  
    public class Bootstrapper : PhoneBootstrapper
  2.  
    {
  3.  
    // 把原来的私有变量container改造成公共属性
  4.  
    public PhoneContainer container { get; set; }
  5.  
    //其他方法不变
  6.  
    //……
  7.  
    }


 

         MainPage的View类通过bootstrapper中Container的GetAllInstances方法得到IEventAggregator实例,并可以订阅或发送消息。

  1.  
    public partial class MainPage : PhoneApplicationPage, IHandleSampleMessage>
  2.  
    {
  3.  
    private IEventAggregator eventAggregator;
  4.  
    // Constructor
  5.  
    public MainPage()
  6.  
    {
  7.  
    InitializeComponent();
  8.  
    Bootstrapper bootstrapper = Application.Current.Resources["bootstrapper"]
  9.  
    as Bootstrapper;
  10.  
    IEventAggregator eventAggregator =
  11.  
    bootstrapper.container.GetAllInstances(typeof
  12.  
    (IEventAggregator)).FirstOrDefault() as IEventAggregator;
  13.  
    this.eventAggregator = eventAggregator;
  14.  
    eventAggregator.Subscribe(this);
  15.  
    }
  16.  
    public void Handle(SampleMessage message)
  17.  
    {
  18.  
    MessageBox.Show(message.Name);
  19.  
    }
  20.  
    }

 

Launcher与Chooser

         Caliburn.Micro借用IEventAggregator消息类还提供了调用Launcher和Chooser的功能。

         例子:调用Launcher打开地图程序。

  1.  
    public class MainPageViewModel: Screen
  2.  
    {
  3.  
    public MainPageViewModel(IEventAggregator eventAggregator)
  4.  
    {
  5.  
    this.eventAggregator = eventAggregator;
  6.  
    eventAggregator.Subscribe(this);
  7.  
    }
  8.  
    public void LaunchMap()
  9.  
    {
  10.  
    eventAggregator.RequestTask(task =>
  11.  
    {
  12.  
    task.SearchTerm = "Milan";
  13.  
    });
  14.  
    }
  15.  
    }

 

         Chooser由于需要接收返回值,需要实现IHandle>接口。

  1.  
    public class MainPageViewModel: Screen, IHandleTaskCompletedPhoneNumberResult>>
  2.  
    {
  3.  
    private readonly IEventAggregator eventAggregator;
  4.  
    public MainPageViewModel(IEventAggregator eventAggregator)
  5.  
    {
  6.  
    this.eventAggregator = eventAggregator;
  7.  
    }
  8.  
    protected override void OnActivate()
  9.  
    {
  10.  
    eventAggregator.Subscribe(this);
  11.  
    base.OnActivate();
  12.  
    }
  13.  
    protected override void OnDeactivate(bool close)


评论


亲,登录后才可以留言!