zoukankan      html  css  js  c++  java
  • Caliburn.Micro开发框架介绍 -- Windows phone

    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<MainPageViewModel>();
    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 IEnumerable<object> 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.  
      <!--Application Resources-->
    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语法)

    <TextBlock x:Name="Name"/>

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

    行为(Actions)

    命令(Commands)

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

             View控件定义了名字。

    <Button Content="Show name"x:Name="ShowNameAction" />

             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.  
      <Button x:Name="ShowName">
    2.  
      <i:Interaction.Triggers>
    3.  
      <i:EventTrigger EventName="Click">
    4.  
      <cal:ActionMessage MethodName="ShowNameAction" />
    5.  
      </i:EventTrigger>
    6.  
      </i:Interaction.Triggers>
    7.  
      </Button>

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

    <Button cal:Message.Attach="[Event Click] = [Action ShowName]" />

    控制行为

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

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

    1.  
      <StackPanel>
    2.  
      <Button x:Name="ShowName" Content="Click me" />
    3.  
      <CheckBox x:Name="IsEnabled" Content="IsEnabled" />
    4.  
      </StackPanel>

    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.  
      <ListBox x:Name="Items">
    2.  
      <ListBox.ItemTemplate>
    3.  
      <DataTemplate>
    4.  
      <StackPanel>
    5.  
      <TextBlock Text="{Binding}" />
    6.  
      </StackPanel>
    7.  
      </DataTemplate>
    8.  
      </ListBox.ItemTemplate>
    9.  
      </ListBox>

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

    1.  
      private ObservableCollection<string> items;
    2.  
      public ObservableCollection<string> 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<MainPageViewModel>();
    7.  
      container.PerRequest<Page2ViewModel>();
    8.  
      //注册单一实例模式
    9.  
      container.Singleton<IVisitDataProvider, VisitDataProvider>();
    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<Page2ViewModel>()
    11.  
      .Navigate();
    12.  
      }
    13.  
      }

    注入导航参数

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

    例如:跳转时带上Name参数

    1.  
      public void GoToPage2()
    2.  
      {
    3.  
      navigationService.UriFor<Page2ViewModel>()
    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>的继承类,T是你要保存临时值的ViewModel。

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

    1.  
      public class MainPageModelStorage: StorageHandler<MainPageViewModel>
    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的依赖注入和属性注入机制能保证深度链接的正常打开。

             例如:

    <StackPanel>

             <TextBoxText="{Binding Name}" />

             <ButtonContent="Create secondary tile" x:Name="CreateTile" />

    </StackPanel>

             该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>,本例T就是SampleMessage,并需要接受消息接口IEventAggregator的注入。

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

    1.  
      public class MainPageViewModel: Screen, IHandle<SampleMessage>
    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<SecondPageViewModel>().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, IHandle<SampleMessage>
    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<MapsTask>(task =>
    11.  
      {
    12.  
      task.SearchTerm = "Milan";
    13.  
      });
    14.  
      }
    15.  
      }

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

    1.  
      public class MainPageViewModel: Screen, IHandle<TaskCompleted<PhoneNumberResult>>
    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)
    14.  
      {
    15.  
      eventAggregator.Unsubscribe(this);
    16.  
      base.OnDeactivate(close);
    17.  
      }
    18.  
      public void OpenContact()
    19.  
      {
    20.  
      eventAggregator.RequestTask<PhoneNumberChooserTask>();
    21.  
      }
    22.  
      public void Handle(TaskCompleted<PhoneNumberResult> message)
    23.  
      {
    24.  
      MessageBox.Show(message.Result.DisplayName);
    25.  
      }
    26.  
      }

             注意:由于移动应用生命周期的特殊性,ViewModel应该在OnActivate和OnDeactivate事件里订阅消息和取消订阅。

    用户服务

             用户自定义的服务类,可以通过依赖注入提供给使用方,前提是必须在Bootstrapper的Configure函数中注册。这样的服务类一般使用接口编程。

    例子,  先定义数据和服务类接口。

    1.  
      public interface IFeedService
    2.  
      {
    3.  
      Task<List<FeedItem>> GetNews(string url);
    4.  
      }
    5.  
      public class FeedItem
    6.  
      {
    7.  
      public string Title { get; set; }
    8.  
      public string Description { get; set; }
    9.  
      public Uri Url { get; set; }
    10.  
      }

             实现类

    1.  
      public class FeedService: IFeedService
    2.  
      {
    3.  
      public async Task<List<FeedItem>> GetNews(string url)
    4.  
      {
    5.  
      WebClient client = new WebClient();
    6.  
      string content = await client.DownloadStringTaskAsync(url);
    7.  
      XDocument doc = XDocument.Parse(content);
    8.  
      var result =
    9.  
      doc.Descendants("rss").Descendants("channel").Elements("item").Select(x => new
    10.  
      FeedItem
    11.  
      {
    12.  
      {
    13.  
      Title = x.Element("title").Value,
    14.  
      Description = x.Element("description").Value
    15.  
      }).ToList();
    16.  
      return result;
    17.  
      }
    18.  
      }

             在bootstrapper的Configure注册该类

    1.  
      protected override void Configure()
    2.  
      {
    3.  
      container = new PhoneContainer(RootFrame);
    4.  
      container.RegisterPhoneServices();
    5.  
      container.PerRequest<MainPageViewModel>();
    6.  
      container.PerRequest<IFeedService, FeedService>();
    7.  
      AddCustomConventions();
    8.  
      }

             然后使用类的构造函数就能使用依赖注入得到它的实例。

    1.  
      public class MainPageViewModel: Screen
    2.  
      {
    3.  
      private readonly IFeedService feedService;
    4.  
      private List<FeedItem> news;
    5.  
      public List<FeedItem> News
    6.  
      {
    7.  
      get { return news; }
    8.  
      set
    9.  
      {
    10.  
      news = value;
    11.  
      NotifyOfPropertyChange(() => News);
    12.  
      }
    13.  
      }
    14.  
      public MainPageViewModel(IFeedService feedService)
    15.  
      {
    16.  
      this.feedService = feedService;
    17.  
      }
    18.  
      public async void LoadWebsite()
    19.  
      {
    20.  
      News = await
    21.  
      feedService.GetNews("http://feeds.feedburner.com/qmatteoq_eng");
    22.  
      }
    23.  
      }

    用户类的单一实例

    Caliburn.Micro也支持把用户服务类注册为单一实例,每个使用者得到的都是同一个实例。利用这个特性,多个ViewModel可以共享数据。

    1.  
      protected override void Configure()
    2.  
      {
    3.  
      container = new PhoneContainer(RootFrame);
    4.  
      container.RegisterPhoneServices();
    5.  
      container.PerRequest<MainPageViewModel>();
    6.  
      container.PerRequest<DetailPageViewModel>();
    7.  
      container.PerRequest<IFeedService, FeedService>();
    8.  
      container.Singleton<DataService>();
    9.  
      AddCustomConventions();
    10.  
      }

    应用程序工具条(Application Bar)

             由于系统默认Applicationbar不是页面控件,不支持绑定。Caliburn.Micro提供了代替类Caliburn.Micro.BindableAppBar(可以通过NuGet获得)。

             View中引用。

    xmlns:bab=”clrnamespace:Caliburn.Micro.BindableAppBar;assembly=Caliburn.Micro.BindableAppBar”

    并在页面中添加该控件。

    1.  
      <Grid x:Name="LayoutRoot" Background="Transparent">
    2.  
      <Grid.RowDefinitions>
    3.  
      <RowDefinition Height="Auto"/>
    4.  
      <RowDefinition Height="*"/>
    5.  
      </Grid.RowDefinitions>
    6.  
       
    7.  
      <bab:BindableAppBar x:Name="AppBar">
    8.  
      <bab:BindableAppBarButton x:Name="AddItem"
    9.  
      Text="{Binding AddItemText}"
    10.  
      IconUri="{Binding Icon}"
    11.  
      Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
    12.  
      />
    13.  
      <bab:BindableAppBarMenuItem x:Name="RemoveItem"
    14.  
      Text="Remove"
    15.  
      />
    16.  
      </bab:BindableAppBar>
    17.  
      </Grid>

    自定义命名约定

             为了让按钮在Click事件发生时不可用,我们在bootstrapper中增加自定义命名约定。

    1.  
      static void AddCustomConventions()
    2.  
      {
    3.  
      ConventionManager.AddElementConvention<BindableAppBarButton>(
    4.  
      Control.IsEnabledProperty, "DataContext", "Click");
    5.  
      ConventionManager.AddElementConvention<BindableAppBarMenuItem>(
    6.  
      Control.IsEnabledProperty, "DataContext", "Click");
    7.  
      }

             该约定把IsEnabled属性绑定给Click事件。

             下面ViewModel能给自定义bar提供按钮文本、图标、可见状态和响应事件。

    1.  
      public class MainPageViewModel: Screen
    2.  
      {
    3.  
      private string addItemText;
    4.  
      public string AddItemText
    5.  
      {
    6.  
      get { return addItemText; }
    7.  
      set
    8.  
      {
    9.  
      {
    10.  
      addItemText = value;
    11.  
      NotifyOfPropertyChange(() => AddItemText);
    12.  
      }
    13.  
      }
    14.  
      private Uri icon;
    15.  
      public Uri Icon
    16.  
      {
    17.  
      get { return icon; }
    18.  
      set
    19.  
      {
    20.  
      icon = value;
    21.  
      NotifyOfPropertyChange(() => Icon);
    22.  
      }
    23.  
      }
    24.  
      private bool isVisible;
    25.  
      public bool IsVisible
    26.  
      {
    27.  
      get { return isVisible; }
    28.  
      set
    29.  
      {
    30.  
      isVisible = value;
    31.  
      NotifyOfPropertyChange(() => IsVisible);
    32.  
      }
    33.  
      }
    34.  
      public MainPageViewModel()
    35.  
      {
    36.  
      AddItemText = "Add";
    37.  
      Icon = new Uri("/Assets/AppBar/appbar.add.rest.png", UriKind.Relative);
    38.  
      IsVisible = false;
    39.  
      }
    40.  
      public void AddItem()
    41.  
      {
    42.  
      MessageBox.Show("Item added");
    43.  
      }
    44.  
      public void RemoveItem()
    45.  
      {
    46.  
      MessageBox.Show("Item removed");
    47.  
      }
    48.  
      }

    Pivot和Panorama

             Pivot和Panorama作为Windows phone的特色控件广泛的到使用,由于Panorama的选中事件有Bug,导致子页面的生命周期的事件不完整,所以重点介绍Pivot。

             Caliburn.Micro为Pivot提供了工具类,能把各个功能区分离成子页面,每个子页面都有自己的生命周期。

             为了能让Pivot集成多个页面作为子页面,首先需要在bootstrapper中注册这些ViewModel。

    Conductor类

             Conductor类作为多页面的管理者,Pivot主页面的ViewModel需要继承它。

             例如:Pivot主页面继承Conductor类,便拥有了Items属性,利用依赖注入得到子页面的实例,并把子页面添加到Items集成属性中,以便Conductor管理它们。

    1.  
      public class PivotViewModel: Conductor<IScreen>.Collection.OneActive
    2.  
      {
    3.  
      private readonly PivotItem1ViewModel item1;
    4.  
      private readonly PivotItem2ViewModel item2;
    5.  
      public PivotViewModel(PivotItem1ViewModel item1, PivotItem2ViewModel item2)
    6.  
      {
    7.  
      this.item1 = item1;
    8.  
      this.item2 = item2;
    9.  
      }
    10.  
      protected override void OnInitialize()
    11.  
      {
    12.  
      base.OnInitialize();
    13.  
      Items.Add(item1);
    14.  
      Items.Add(item2);
    15.  
      ActivateItem(item1);
    16.  
      }
    17.  
      }

             而View类中的Pivot控件仅需要指定其名称为Items即可。

    1.  
      <Grid x:Name="LayoutRoot" Background="Transparent">
    2.  
      <!--Pivot Control-->
    3.  
      <phone:Pivot Title="MY APPLICATION" x:Name="Items" SelectedItem="{Binding
    4.  
      ActiveItem, Mode=TwoWay}">
    5.  
      <phone:Pivot.HeaderTemplate>
    6.  
      <DataTemplate>
    7.  
      <TextBlock Text="{Binding DisplayName}" />
    8.  
      </DataTemplate>
    9.  
      </phone:Pivot.HeaderTemplate>
    10.  
      </phone:Pivot>
    11.  
      </Grid>

             子页面需要继承Screen类,其DisplayName属性作为Title显示在Pivot上。

    1.  
      public class PivotItem1ViewModel: Screen
    2.  
      {
    3.  
      public PivotItem1ViewModel()
    4.  
      {
    5.  
      DisplayName = "First pivot";
    6.  
      }
    7.  
      }

    延迟加载(Lazy Loading)

             在构造函数或初始化函数中加载数据会带来很糟糕的用户体验。Pivot的子页面也具有完整的生命周期事件,可以在ViewModel的OnActivate()事件中加载数据。

             例子,Pivot子页面1负责读取RSS,我们先定义数据和数据操作类,

    1.  
      public class FeedItem
    2.  
      {
    3.  
      public string Title { get; set; }
    4.  
      public string Description { get; set; }
    5.  
      }
    6.  
      public static class RssParser
    7.  
      {
    8.  
      public static IEnumerable<FeedItem> ParseXml(string content)
    9.  
      {
    10.  
      XDocument doc = XDocument.Parse(content);
    11.  
      var result =
    12.  
      doc.Descendants("rss").Descendants("channel").Elements("item").Select(x => new
    13.  
      FeedItem
    14.  
      {
    15.  
      Title = x.Element("title").Value,
    16.  
      Description = x.Element("description").Value
    17.  
      });
    18.  
      return result;
    19.  
      }
    20.  
      }

             再定义View中的绑定行为。

    1.  
      <ListBox x:Name="FeedItems">
    2.  
      <ListBox.ItemTemplate>
    3.  
      <DataTemplate>
    4.  
      <StackPanel>
    5.  
      <TextBlock Text="{Binding Path=Title}" />
    6.  
      </StackPanel>
    7.  
      </DataTemplate>
    8.  
      </ListBox.ItemTemplate>
    9.  
      </ListBox>

             ViewModel类的OnActivate()事件里使用异步方法下载并解析数据,然后更新绑定源。

    1.  
      public class PivotItem1ViewModel: Screen
    2.  
      {
    3.  
      public PivotItem1ViewModel()
    4.  
      {
    5.  
      DisplayName = "Pivot 1";
    6.  
      }
    7.  
      protected override async void OnActivate()
    8.  
      {
    9.  
      base.OnActivate();
    10.  
      WebClient client = new WebClient();
    11.  
      string result = await
    12.  
      client.DownloadStringTaskAsync("http://feeds.feedburner.com/qmatteoq_eng");
    13.  
      IEnumerable<FeedItem> feedItems = RssParser.ParseXml(result);
    14.  
      FeedItems = feedItems.ToList();
    15.  
      }
    16.  
      private List<FeedItem> _feedItems;
    17.  
      public List<FeedItem> FeedItems
    18.  
      {
    19.  
      get { return _feedItems; }
    20.  
      set
    21.  
      {
    22.  
      _feedItems = value;
    23.  
      NotifyOfPropertyChange(() => FeedItems);
    24.  
      }
    25.  
      }
    26.  
      }

    出处:https://blog.csdn.net/hankersyan/article/details/13860725

  • 相关阅读:
    java异常
    Map集和
    获取每个字符出现的次数
    从1-33号球中选取6个红球,且红球数字最多重复不超过3个 从1-16号球中选取一个篮球 由红球和蓝球共同组成一个双色球号码,且红球在左边(按照升序排列),篮球在右边。
    gitlab介绍及使用
    Maven使用介绍
    IDEA集成开发环境安装git,修改代码后文件变色代表的含义
    大数据相关
    开源镜像站汇总
    MySQL配置文件详解
  • 原文地址:https://www.cnblogs.com/mq0036/p/12443240.html
Copyright © 2011-2022 走看看