Caliburn.Micro開發框架介紹 -- Windows phone

Caliburn.Micro開發框架介紹

         Caliburn是一套基於XAML的開發框架,它小巧而強大。利用它不但能提升開發效率,還能夠提升XAML程序開發的可維護行、可擴展性和可測試性。Caliburn.Micro則是專門針對Windows phone開發的版本。html

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。shell

Caliburn.Micro自動把ViewModel綁定到View的DataContext。若是ViewModel的屬性名和控件的名稱相同,那麼就會自動綁定上。若是該屬性的值發生變化,控件的也能獲得更新。編程

此外,Caliburn.Micro還爲Windows phone的特性提供輔助,例如:tombstone的管理,應用程序生命週期和launcher。bootstrap

固然,你也能夠自定義各類約定。設計模式

準備工做

下載

Caliburn.Micro能夠經過Visualstudio的NuGet工具得到,也能夠在其官網下載發佈包、源代碼和例子。http://caliburnmicro.codeplex.com/app

入口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。async

在工程的根目錄下建立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

相關文章
相關標籤/搜索