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,其內容如下。
-
public class AppBootstrapper : PhoneBootstrapper
-
{
-
PhoneContainer container;
-
-
protected override void Configure()
-
{
-
container = new PhoneContainer(RootFrame);
-
-
container.RegisterPhoneServices();
-
//注冊所有ViewModel
-
container.PerRequest<MainPageViewModel>();
-
-
AddCustomConventions();
-
}
-
-
static void AddCustomConventions()
-
{
-
//ellided 自定義命名約定
-
}
-
-
protected override object GetInstance(Type service, string key)
-
{
-
return container.GetInstance(service, key);
-
}
-
-
protected override IEnumerable<object> GetAllInstances(Type service)
-
{
-
return container.GetAllInstances(service);
-
}
-
-
protected override void BuildUp(object instance)
-
{
-
container.BuildUp(instance);
-
}
-
}
初始化bootstrapper
修改App.xml,初始化bootstrapper。
-
<Application
-
x:Class= "CSH.IntelliHIS.WP8.App"
-
xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x= "http://schemas.microsoft.com/winfx/2006/xaml"
-
xmlns:phone= "clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
-
xmlns:shell= "clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
-
xmlns:local= "clr-namespace:CSH.IntelliHIS.WP8">
-
-
<!--Application Resources-->
-
<Application.Resources>
-
<local:AppBootstrapper x:Key="bootstrapper" />
-
</Application.Resources>
-
-
</Application>
修改App.xml.cs,默認的初始化代碼已經不需要了。
-
public partial class App : Application
-
{
-
public App()
-
{
-
InitializeComponent();
-
}
-
}
命名約定(naming convention)
命名約定讓Caliburn.Micro能自動關聯起View和View Model,如果我們運行工程,瀏覽MainPage頁面,則MainPageViewModel自動被實例化。
接下來就可以看看Caliburn.Micro是如何扮演值轉換器的角色,看它是如何在View和ViewModel之間傳遞和轉換值,以便View綁定這些值。
我們在ViewModel里增加一個叫Name的屬性(Property)。
-
public class MainPageViewModel: PropertyChangedBase
-
{
-
public MainPageViewModel()
-
{
-
Name = "Matteo";
-
}
-
private string name;
-
public string Name
-
{
-
get { return name; }
-
set
-
{
-
name = value;
-
NotifyOfPropertyChange(() => Name);
-
}
-
}
-
}
在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里這樣定義
-
<Button x:Name= "ShowName">
-
<i:Interaction.Triggers>
-
<i:EventTrigger EventName= "Click">
-
<cal:ActionMessage MethodName= "ShowNameAction" />
-
</i:EventTrigger>
-
</i:Interaction.Triggers>
-
</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能否被激活。
-
<StackPanel>
-
<Button x:Name= "ShowName" Content= "Click me" />
-
<CheckBox x:Name= "IsEnabled" Content= "IsEnabled" />
-
</StackPanel>
CheckBox綁定到isEnabled,Button綁定到ShowName,CheckBox值改變的同時通知Button的控制屬性CanShowName,從而實現關聯控制。
-
public class MainPageViewModel: PropertyChangedBase
-
{
-
private bool isEnabled;
-
public bool IsEnabled
-
{
-
get { return isEnabled; }
-
set
-
{
-
isEnabled = value;
-
NotifyOfPropertyChange(() => IsEnabled);
-
NotifyOfPropertyChange(() => CanShowName);
-
}
-
}
-
public bool CanShowName
-
{
-
get { return IsEnabled; }
-
}
-
public void ShowName()
-
{
-
MessageBox.Show( "Clicked");
-
}
-
}
集合(Collections)
View的列表控件和ViewModel的集合屬性如果同名也能實現綁定,Selected關鍵字前綴加上屬性名的單數形式就能實現選中控制。
例如:ListBox控件使用復數形式的Items名稱
-
<ListBox x:Name= "Items">
-
<ListBox.ItemTemplate>
-
<DataTemplate>
-
<StackPanel>
-
<TextBlock Text= "{Binding}" />
-
</StackPanel>
-
</DataTemplate>
-
</ListBox.ItemTemplate>
-
</ListBox>
ViewModel的屬性同名Items實現綁定。
-
private ObservableCollection< string> items;
-
public ObservableCollection< string> Items
-
{
-
get { return items; }
-
set
-
{
-
items = value;
-
NotifyOfPropertyChange(() => Items);
-
}
-
}
SelectedItem實現選中控制。(注意:Selected+單數形式Item)
-
private string selectedItem;
-
public string SelectedItem
-
{
-
get { return selectedItem; }
-
set
-
{
-
selectedItem = value;
-
NotifyOfPropertyChange(() => SelectedItem);
-
MessageBox.Show( value);
-
}
-
}
依賴注入(Dependency Injection)
Caliburn.Micro有依賴注入的功能。用戶類要在依賴注入時被使用到,就要在Bootstrapper的Configure函數向依賴注入容器注冊,Caliburn.Micro既提供每次創建新實例的模式,也提供單一實例模式。同時Caliburn.Micro會自動注冊一些系統工具類。
-
protected override void Configure()
-
{
-
container = new PhoneContainer(RootFrame);
-
container.RegisterPhoneServices();
-
//注冊,非單一實例模式
-
container.PerRequest<MainPageViewModel>();
-
container.PerRequest<Page2ViewModel>();
-
//注冊單一實例模式
-
container.Singleton<IVisitDataProvider, VisitDataProvider>();
-
AddCustomConventions();
-
}
在ViewModel被實例化時,如果其構造函數帶有某種類型接口為參數,則依賴注入容器會提供它們的實例。例子如下。
導航(Navigation)
Windows Phone使用NavigationService來完成頁面間跳轉,ViewModel如果要跳轉頁面,應利用依賴注入得到它的實例。
-
public class MainPageViewModel : PropertyChangedBase
-
{
-
private readonly INavigationService navigationService;
-
public MainPageViewModel(INavigationService navigationService)
-
{
-
this.navigationService = navigationService;
-
}
-
public void GoToPage2()
-
{
-
navigationService.UriFor<Page2ViewModel>()
-
.Navigate();
-
}
-
}
注入導航參數
如果導航時帶有參數,Caliburn.Micro會自動把參數值注入到同名屬性。
例如:跳轉時帶上Name參數
-
public void GoToPage2()
-
{
-
navigationService.UriFor<Page2ViewModel>()
-
.WithParam(x => x.Name, "Matteo")
-
.Navigate();
-
}
其導航字符串為/Page2View.xaml?Name=Matteo,則Page2的同名屬性Name在實例化時就會被注入值。如果有控件綁定了該屬性,則導航到該頁面時就能顯示出值。
-
public class Page2ViewModel: PropertyChangedBase
-
{
-
private string name;
-
public string Name
-
{
-
get { return name; }
-
set
-
{
-
name = value;
-
NotifyOfPropertyChange(() => Name);
-
}
-
}
-
}
墓碑(Tombstoning)
應用程序被切換至后台時,如果有值需要暫存,Caliburn.Micro提供了很簡潔的解決方法,這比普通XAML程序處理生命周期里各個事件要容易的多。我們只需要創建一個StorageHandler<T>的繼承類,T是你要保存臨時值的ViewModel。
例如,在程序切換至后台時,需要暫存View里名為Name的文本框的值,即暫存MainPageViewModel的Name屬性。
-
public class MainPageModelStorage: StorageHandler< MainPageViewModel>
-
{
-
public override void Configure()
-
{
-
Property(x => x.Name)
-
.InPhoneState();
-
}
-
}
InPhoneState()函數把值暫存在內存中,程序退出后就不存在。相對應的InAppSettings()則會持久保存。
深度鏈接(Deep Links)
Windows phone支持跳過首頁的實例化,通過URL鏈接直接打開應用程序里的某個頁面,並可攜帶參數。Caliburn.Micro的依賴注入和屬性注入機制能保證深度鏈接的正常打開。
例如:
<StackPanel>
<TextBoxText="{Binding Name}" />
<ButtonContent="Create secondary tile" x:Name="CreateTile" />
</StackPanel>
該ViewModel的按鈕事件動態創建一個Tile,點擊該Tile打開深度鏈接,這里的鏈接使用了首頁。
-
public class MainPageViewModel: PropertyChangedBase
-
{
-
private string name;
-
public string Name
-
{
-
get { return name; }
-
set
-
{
-
name = value;
-
NotifyOfPropertyChange(() => Name);
-
}
-
}
-
public void CreateTile()
-
{
-
ShellTileData tile = new StandardTileData
-
{
-
Title = "Test",
-
};
-
ShellTile.Create( new Uri( "/Views/MainPage.xaml?Name=Matteo",
-
UriKind.Relative), tile);
-
}
-
}
生命周期的事件
ViewModel並非WP8頁面的繼承類,為了能在ViewModel里響應頁面基類PhoneApplicationPage生命周期的事件,Caliburn.Micro介紹了Screen類。當一個應用初次打開,依次會觸發下列事件。
-
public class MainPageViewModel: Screen
-
{
-
protected override void OnViewAttached(object view, object context)
-
{
-
base.OnViewAttached(view, context);
-
Debug.WriteLine( "OnViewAttached:ViewModel和View建立關聯時被調用");
-
}
-
protected override void OnInitialize()
-
{
-
base.OnInitialize();
-
Debug.WriteLine( "OnInitialize:初始化結束");
-
}
-
protected override void OnViewReady(object view)
-
{
-
base.OnViewReady(view);
-
Debug.WriteLine( "OnViewReady:初始化結束,准備繪制");
-
}
-
protected override void OnViewLoaded(object view)
-
{
-
base.OnViewLoaded(view);
-
Debug.WriteLine( "OnViewLoaded:頁面和子控件全部初始化完成");
-
}
-
protected override void OnActivate()
-
{
-
base.OnActivate();
-
Debug.WriteLine( "OnActivate:切換成為當前窗口");
-
}
-
protected override void OnDeactivate(bool close)
-
{
-
base.OnDeactivate(close);
-
Debug.WriteLine( "OnDeactivate:切換至后台");
-
}
-
}
Screen的OnActivate和OnDeactivate事件是最常使用的事件,它們分別對應了頁面OnNavigatedTo和OnNavigatedFrom事件。值得注意的是,ViewModel加載數據應盡量避免在構造函數和初始化函數中實行,而應該在OnActivate中。
消息傳遞(Messaging)
Caliburn.Micro為應用程序內已經打開的多個ViewModel之間提供消息傳遞的功能。
例子:從頁面1打開頁面2,點擊頁面2的SendMessage Button向頁面1發送一個消息,回退到頁面1就會看到這個值顯示在文本框里。
如下定義的消息類
-
public class SampleMessage
-
{
-
public string Name { get; set; }
-
public SampleMessage(string name)
-
{
-
Name = name;
-
}
-
}
消息接收者需要實現接口IHandle<T>,本例T就是SampleMessage,並需要接受消息接口IEventAggregator的注入。
注意:消息接收者需要調用IEventAggregator的Subscribe函數訂閱消息。
-
public class MainPageViewModel: Screen, IHandle< SampleMessage>
-
{
-
private readonly IEventAggregator eventAggregator;
-
private readonly INavigationService navigationService;
-
-
private string name;
-
public string Name
-
{
-
get { return name; }
-
set
-
{
-
name = value;
-
NotifyOfPropertyChange(() => Name);
-
}
-
}
-
-
public MainPageViewModel(IEventAggregator eventAggregator, INavigationService
-
navigationService)
-
{
-
this.eventAggregator = eventAggregator;
-
this.navigationService = navigationService;
-
eventAggregator.Subscribe( this);
-
}
-
-
public void GoToPage2()
-
{
-
navigationService.UriFor<SecondPageViewModel>().Navigate();
-
}
-
-
public void Handle(SampleMessage message)
-
{
-
Name = message.Name;
-
}
-
}
消息發送者只需要接受IEventAggregator的注入,並用它的Publish方法發送消息。
-
public class SecondPageViewModel: Screen
-
{
-
private readonly IEventAggregator eventAggregator;
-
public SecondPageViewModel(IEventAggregator eventAggregator)
-
{
-
this.eventAggregator = eventAggregator;
-
}
-
public void SendMessage()
-
{
-
eventAggregator.Publish( new SampleMessage( "Matteo"));
-
}
-
}
View和ViewModel之間的通訊
View類只要得到IEventAggregator的實例也能在Views和ViewModels之間接收或發送消息。
此時我們需要改造Bootstrapper類,要得到其container變量,就可以調用其GetAllInstances或GetInstance得到IEventAggregator。
例如:
-
public class Bootstrapper : PhoneBootstrapper
-
{
-
// 把原來的私有變量container改造成公共屬性
-
public PhoneContainer container { get; set; }
-
//其他方法不變
-
//……
-
}
MainPage的View類通過bootstrapper中Container的GetAllInstances方法得到IEventAggregator實例,並可以訂閱或發送消息。
-
public partial class MainPage : PhoneApplicationPage, IHandle< SampleMessage>
-
{
-
private IEventAggregator eventAggregator;
-
// Constructor
-
public MainPage()
-
{
-
InitializeComponent();
-
Bootstrapper bootstrapper = Application.Current.Resources[ "bootstrapper"]
-
as Bootstrapper;
-
IEventAggregator eventAggregator =
-
bootstrapper.container.GetAllInstances( typeof
-
(IEventAggregator)).FirstOrDefault() as IEventAggregator;
-
this.eventAggregator = eventAggregator;
-
eventAggregator.Subscribe( this);
-
}
-
public void Handle(SampleMessage message)
-
{
-
MessageBox.Show(message.Name);
-
}
-
}
Launcher與Chooser
Caliburn.Micro借用IEventAggregator消息類還提供了調用Launcher和Chooser的功能。
例子:調用Launcher打開地圖程序。
-
public class MainPageViewModel: Screen
-
{
-
public MainPageViewModel(IEventAggregator eventAggregator)
-
{
-
this.eventAggregator = eventAggregator;
-
eventAggregator.Subscribe( this);
-
}
-
public void LaunchMap()
-
{
-
eventAggregator.RequestTask<MapsTask>(task =>
-
{
-
task.SearchTerm = "Milan";
-
});
-
}
-
}
Chooser由於需要接收返回值,需要實現IHandle<TaskCompleted<T>>接口。
-
public class MainPageViewModel: Screen, IHandle< TaskCompleted< PhoneNumberResult>>
-
{
-
private readonly IEventAggregator eventAggregator;
-
public MainPageViewModel(IEventAggregator eventAggregator)
-
{
-
this.eventAggregator = eventAggregator;
-
}
-
protected override void OnActivate()
-
{
-
eventAggregator.Subscribe( this);
-
base.OnActivate();
-
}
-
protected override void OnDeactivate(bool close)
-
{
-
eventAggregator.Unsubscribe( this);
-
base.OnDeactivate(close);
-
}
-
public void OpenContact()
-
{
-
eventAggregator.RequestTask<PhoneNumberChooserTask>();
-
}
-
public void Handle(TaskCompleted<PhoneNumberResult> message)
-
{
-
MessageBox.Show(message.Result.DisplayName);
-
}
-
}
注意:由於移動應用生命周期的特殊性,ViewModel應該在OnActivate和OnDeactivate事件里訂閱消息和取消訂閱。
用戶服務
用戶自定義的服務類,可以通過依賴注入提供給使用方,前提是必須在Bootstrapper的Configure函數中注冊。這樣的服務類一般使用接口編程。
例子, 先定義數據和服務類接口。
-
public interface IFeedService
-
{
-
Task<List<FeedItem>> GetNews( string url);
-
}
-
public class FeedItem
-
{
-
public string Title { get; set; }
-
public string Description { get; set; }
-
public Uri Url { get; set; }
-
}
實現類
-
public class FeedService: IFeedService
-
{
-
public async Task<List<FeedItem>> GetNews( string url)
-
{
-
WebClient client = new WebClient();
-
string content = await client.DownloadStringTaskAsync(url);
-
XDocument doc = XDocument.Parse(content);
-
var result =
-
doc.Descendants( "rss").Descendants( "channel").Elements( "item").Select(x => new
-
FeedItem
-
{
-
{
-
Title = x.Element( "title").Value,
-
Description = x.Element( "description").Value
-
}).ToList();
-
return result;
-
}
-
}
在bootstrapper的Configure注冊該類
-
protected override void Configure()
-
{
-
container = new PhoneContainer(RootFrame);
-
container.RegisterPhoneServices();
-
container.PerRequest<MainPageViewModel>();
-
container.PerRequest<IFeedService, FeedService>();
-
AddCustomConventions();
-
}
然后使用類的構造函數就能使用依賴注入得到它的實例。
-
public class MainPageViewModel: Screen
-
{
-
private readonly IFeedService feedService;
-
private List<FeedItem> news;
-
public List<FeedItem> News
-
{
-
get { return news; }
-
set
-
{
-
news = value;
-
NotifyOfPropertyChange(() => News);
-
}
-
}
-
public MainPageViewModel(IFeedService feedService)
-
{
-
this.feedService = feedService;
-
}
-
public async void LoadWebsite()
-
{
-
News = await
-
feedService.GetNews( "http://feeds.feedburner.com/qmatteoq_eng");
-
}
-
}
用戶類的單一實例
Caliburn.Micro也支持把用戶服務類注冊為單一實例,每個使用者得到的都是同一個實例。利用這個特性,多個ViewModel可以共享數據。
-
protected override void Configure()
-
{
-
container = new PhoneContainer(RootFrame);
-
container.RegisterPhoneServices();
-
container.PerRequest<MainPageViewModel>();
-
container.PerRequest<DetailPageViewModel>();
-
container.PerRequest<IFeedService, FeedService>();
-
container.Singleton<DataService>();
-
AddCustomConventions();
-
}
應用程序工具條(Application Bar)
由於系統默認Applicationbar不是頁面控件,不支持綁定。Caliburn.Micro提供了代替類Caliburn.Micro.BindableAppBar(可以通過NuGet獲得)。
View中引用。
xmlns:bab=”clrnamespace:Caliburn.Micro.BindableAppBar;assembly=Caliburn.Micro.BindableAppBar”
並在頁面中添加該控件。
-
<Grid x:Name= "LayoutRoot" Background= "Transparent">
-
<Grid.RowDefinitions>
-
<RowDefinition Height= "Auto"/>
-
<RowDefinition Height= "*"/>
-
</Grid.RowDefinitions>
-
-
<bab:BindableAppBar x:Name= "AppBar">
-
<bab:BindableAppBarButton x:Name= "AddItem"
-
Text= "{Binding AddItemText}"
-
IconUri= "{Binding Icon}"
-
Visibility= "{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
-
/>
-
<bab:BindableAppBarMenuItem x:Name= "RemoveItem"
-
Text= "Remove"
-
/>
-
</bab:BindableAppBar>
-
</Grid>
自定義命名約定
為了讓按鈕在Click事件發生時不可用,我們在bootstrapper中增加自定義命名約定。
-
static void AddCustomConventions()
-
{
-
ConventionManager.AddElementConvention<BindableAppBarButton>(
-
Control.IsEnabledProperty, "DataContext", "Click");
-
ConventionManager.AddElementConvention<BindableAppBarMenuItem>(
-
Control.IsEnabledProperty, "DataContext", "Click");
-
}
該約定把IsEnabled屬性綁定給Click事件。
下面ViewModel能給自定義bar提供按鈕文本、圖標、可見狀態和響應事件。
-
public class MainPageViewModel: Screen
-
{
-
private string addItemText;
-
public string AddItemText
-
{
-
get { return addItemText; }
-
set
-
{
-
{
-
addItemText = value;
-
NotifyOfPropertyChange(() => AddItemText);
-
}
-
}
-
private Uri icon;
-
public Uri Icon
-
{
-
get { return icon; }
-
set
-
{
-
icon = value;
-
NotifyOfPropertyChange(() => Icon);
-
}
-
}
-
private bool isVisible;
-
public bool IsVisible
-
{
-
get { return isVisible; }
-
set
-
{
-
isVisible = value;
-
NotifyOfPropertyChange(() => IsVisible);
-
}
-
}
-
public MainPageViewModel()
-
{
-
AddItemText = "Add";
-
Icon = new Uri( "/Assets/AppBar/appbar.add.rest.png", UriKind.Relative);
-
IsVisible = false;
-
}
-
public void AddItem()
-
{
-
MessageBox.Show( "Item added");
-
}
-
public void RemoveItem()
-
{
-
MessageBox.Show( "Item removed");
-
}
-
}
Pivot和Panorama
Pivot和Panorama作為Windows phone的特色控件廣泛的到使用,由於Panorama的選中事件有Bug,導致子頁面的生命周期的事件不完整,所以重點介紹Pivot。
Caliburn.Micro為Pivot提供了工具類,能把各個功能區分離成子頁面,每個子頁面都有自己的生命周期。
為了能讓Pivot集成多個頁面作為子頁面,首先需要在bootstrapper中注冊這些ViewModel。
Conductor類
Conductor類作為多頁面的管理者,Pivot主頁面的ViewModel需要繼承它。
例如:Pivot主頁面繼承Conductor類,便擁有了Items屬性,利用依賴注入得到子頁面的實例,並把子頁面添加到Items集成屬性中,以便Conductor管理它們。
-
public class PivotViewModel: Conductor< IScreen>. Collection. OneActive
-
{
-
private readonly PivotItem1ViewModel item1;
-
private readonly PivotItem2ViewModel item2;
-
public PivotViewModel(PivotItem1ViewModel item1, PivotItem2ViewModel item2)
-
{
-
this.item1 = item1;
-
this.item2 = item2;
-
}
-
protected override void OnInitialize()
-
{
-
base.OnInitialize();
-
Items.Add(item1);
-
Items.Add(item2);
-
ActivateItem(item1);
-
}
-
}
而View類中的Pivot控件僅需要指定其名稱為Items即可。
-
<Grid x:Name= "LayoutRoot" Background= "Transparent">
-
<!--Pivot Control-->
-
<phone:Pivot Title= "MY APPLICATION" x:Name="Items" SelectedItem= "{Binding
-
ActiveItem, Mode=TwoWay}">
-
<phone:Pivot.HeaderTemplate>
-
<DataTemplate>
-
<TextBlock Text= "{Binding DisplayName}" />
-
</DataTemplate>
-
</phone:Pivot.HeaderTemplate>
-
</phone:Pivot>
-
</Grid>
子頁面需要繼承Screen類,其DisplayName屬性作為Title顯示在Pivot上。
-
public class PivotItem1ViewModel: Screen
-
{
-
public PivotItem1ViewModel()
-
{
-
DisplayName = "First pivot";
-
}
-
}
延遲加載(Lazy Loading)
在構造函數或初始化函數中加載數據會帶來很糟糕的用戶體驗。Pivot的子頁面也具有完整的生命周期事件,可以在ViewModel的OnActivate()事件中加載數據。
例子,Pivot子頁面1負責讀取RSS,我們先定義數據和數據操作類,
-
public class FeedItem
-
{
-
public string Title { get; set; }
-
public string Description { get; set; }
-
}
-
public static class RssParser
-
{
-
public static IEnumerable<FeedItem> ParseXml(string content)
-
{
-
XDocument doc = XDocument.Parse(content);
-
var result =
-
doc.Descendants( "rss").Descendants( "channel").Elements( "item").Select(x => new
-
FeedItem
-
{
-
Title = x.Element( "title").Value,
-
Description = x.Element( "description").Value
-
});
-
return result;
-
}
-
}
再定義View中的綁定行為。
-
<ListBox x:Name= "FeedItems">
-
<ListBox.ItemTemplate>
-
<DataTemplate>
-
<StackPanel>
-
<TextBlock Text= "{Binding Path=Title}" />
-
</StackPanel>
-
</DataTemplate>
-
</ListBox.ItemTemplate>
-
</ListBox>
ViewModel類的OnActivate()事件里使用異步方法下載並解析數據,然后更新綁定源。
-
public class PivotItem1ViewModel: Screen
-
{
-
public PivotItem1ViewModel()
-
{
-
DisplayName = "Pivot 1";
-
}
-
protected override async void OnActivate()
-
{
-
base.OnActivate();
-
WebClient client = new WebClient();
-
string result = await
-
client.DownloadStringTaskAsync( "http://feeds.feedburner.com/qmatteoq_eng");
-
IEnumerable<FeedItem> feedItems = RssParser.ParseXml(result);
-
FeedItems = feedItems.ToList();
-
}
-
private List<FeedItem> _feedItems;
-
public List<FeedItem> FeedItems
-
{
-
get { return _feedItems; }
-
set
-
{
-
_feedItems = value;
-
NotifyOfPropertyChange(() => FeedItems);
-
}
-
}
-
}
出處:https://blog.csdn.net/hankersyan/article/details/13860725