「Windows Phone Advent Calendar "ひとり" 2011」第27日目です。延長戦。
ご存知でしょうか?iOSにはUIPageControllerと呼ばれるコントローラーが存在しています。 UIPageControllerは複数のページ要素(UIViewController)を持ち、フリックでページ要素を切り替えることが出来ます。
下図は、ページ要素が2枚あり、強調されて表示されている円からは、アクティブになっている(現在表示されている)ページが2つめであることが分かります。
このUIPageControllerをWindows Phoneで表現してみましょう。先ほどフリックでページ要素を切り替えると言いました。この特徴はPivotそのものなので、PivotコントロールベースでUIPageContollerもどきを作ってみましょう。
まずはプロジェクトを作成します。Windows Phone ピボット アプリケーションを選択します。プロジェクト名は「PageControllerTest」としましょう。
次にExpression BlendでMainPage.xamlを編集します。
Pviotコントロールを上げて、画面下部にItemsControlを置きます。*1ItemsControlは、オブジェクトへのデータバインディングで得られるリストを表示することが出来ます。
UIPageControllerと同様にページ数を表す円は横向きに追加していくようにしたいので、ItemsControl.ItemsPanelのStyleを編集します。また外観を円にしたいのでItemsControl.ItemTemplateのStyleを編集します。MainPage.xamlは以下のようになります。
|xml| <phone:PhoneApplicationPage x:Class="PageControllerTest.MainPage" ・・・省略・・・ shell:SystemTray.IsVisible="True">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="48"/>
</Grid.RowDefinitions>
<controls:Pivot Title="マイ アプリケーション" Name="pivotMain" Margin="0">
・・・省略・・・
</controls:Pivot>
<Grid x:Name="gridPageController" Margin="0" Grid.Row="1">
<ItemsControl Name="pageController" ItemsSource="{Binding PivotItems}"
HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="12" Height="12" Margin="15,0,15,0"
Stroke="{Binding StrokeBrush}"
StrokeThickness="1">
<Ellipse.Fill>
<SolidColorBrush Color="{Binding FillColor}"/>
</Ellipse.Fill>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Grid>
</phone:PhoneApplicationPage> ||<
Expression Blendで編集した後のスクリーンショットです。IsSelectedが更新されるとStrokeBrushとFillColorも更新されるようにしています。
次に表示するアイテムを定義します。アクティブな(現在表示されている)PivotItemかどうかをプロパティに持つViewModelを「PivotItemViewModel」とします。
|cs| using System; using System.ComponentModel; using System.Windows.Media;
namespace PageControllerTest.ViewModels {
public class PivotItemViewModel : INotifyPropertyChanged {
// INotifyPropertyChangedの実装
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool IsSelectedVal;
/// <summary>
/// 現在アクティブな(現在表示されている)表示されている
/// </summary>
public bool IsSelected {
get { return IsSelectedVal; }
set {
if (value != IsSelectedVal) {
IsSelectedVal = value;
NotifyPropertyChanged("IsSelected");
NotifyPropertyChanged("StrokeBrush");
NotifyPropertyChanged("FillColor");
}
}
}
/// <summary>
/// 円の枠線のブラシ
/// </summary>
public Brush StrokeBrush {
get {
var key = IsSelectedVal ? "PhoneAccentBrush" : "PhoneDisabledBrush";
return (Brush)App.Current.Resources[key];
}
}
/// <summary>
/// 円の塗りつぶしの色
/// </summary>
public Color FillColor {
get {
var key = IsSelectedVal ? "PhoneForegroundColor" : "PhoneDisabledColor";
return (Color)App.Current.Resources[key];
}
}
}
} ||<
PivotコントロールのSelectedIndexの変更を見張るViewModelを作成します。名前は「PivotPageControllerViewModel」としています。コンストラクタではPivotコントロールを受けて、先ほどのPivotItemViewModelをPivotItemの個数分生成します。
|cs| using System.Collections.Generic; using System.Linq; using System.Windows.Controls; using Microsoft.Phone.Controls;
namespace PageControllerTest.ViewModels {
public class PivotPageControllerViewModel {
public PivotPageControllerViewModel(Pivot pivot) {
PivotItems = new List<PivotItemViewModel>();
_pivot = pivot;
pivot.SelectionChanged += Pivot_SelectionChanged;
for (int i = 0; i < pivot.Items.Count; i++) {
PivotItems.Add(new PivotItemViewModel());
}
PivotItems[_pivot.SelectedIndex].IsSelected = true;
}
private Pivot _pivot;
public List<PivotItemViewModel> PivotItems { get; set; }
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e) {
var selected = PivotItems.SingleOrDefault(p => p.IsSelected);
if (selected != null) {
selected.IsSelected = false;
}
PivotItems[_pivot.SelectedIndex].IsSelected = true;
}
}
} ||<
Windows Phone ピボットアプリケーション テンプレートを使用してプロジェクトを作成すると、リストをロードしてListBoxに設定するように作られています。既存のコードはそのままにして、オーバーライドしたOnNavigatedToメソッドで、PageControllerViewModelインスタンスを生成し、ItemsControl型のpageControllerに設定しています。
|cs| using System.Windows; using Microsoft.Phone.Controls; using PageControllerTest.ViewModels;
namespace PageControllerTest {
public partial class MainPage : PhoneApplicationPage {
// コンストラクター
public MainPage() {
InitializeComponent();
// ListBox コントロールのデータ コンテキストをサンプル データに設定します
DataContext = App.ViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
// ViewModel Items のデータを読み込みます
private void MainPage_Loaded(object sender, RoutedEventArgs e) {
if (!App.ViewModel.IsDataLoaded) {
App.ViewModel.LoadData();
}
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) {
var viewModel = new PageControllerViewModel(pivotMain);
this.pageController.DataContext = viewModel;
}
}
} ||<
綺麗にバインディング出来ていると、このように2番目のPivotItemを表示している時に、円の2番目がアクセントカラーで表示されるようになります。
Pivotコントロールだけでは、PivotItemがいくつあるのかユーザーは直感的に理解できません。たとえば漫画などユーザーに「あと何ページで終わり」を直感的に知ってもらいたい場合に、このUIPageControllerもどきのTipsが使えるのではないでしょうか。
*1:ListBoxを使わない理由としては、選択不可能なリストコントロールとして扱いたいからです。