【WinRT】カスタムコントロールをつかったセマンティックズームの実装


 以前、カスタムコントロールを使用した「アプリバーボタンの実装方法」についてご紹介しました。今回は、Windows ストア アプリなら是非使ってみたい「セマンティックズーム」をカスタムコントロールで実装するポイントについてご紹介したいと思います。

 WinRTのセマンティックズームコントロールに使用できるのは、GridViewとListViewのみとなっており、カスタマイズしたグリッドビューや他のコントロールを配置しようとすると意外と面倒です。
上図のタイムライン型の掲示板アプリを例に説明していきます。

[1] カスタムコントロールをつくる

【 CustomSizeGridView.cs 】

セマンティックズームコントロールに使用する場合には、「ListViewBase」「GridView」「ListView」を継承したカスタムコントロールをつくります。DefaultStyleKey を設定して、「Themes/Generic.xaml」というリソースディクショナリーにスタイルを定義することで、プラットフォームが自動的にスタイルを参照してくれます。

public class CustomSizeGridView : GridView
{
    public CustomSizeGridView()
    {
        this.DefaultStyleKey = typeof(CustomSizeGridView);
    }
    :
    :
}

【 Themes/Generic.xaml 】

下記の掲示板アプリの例では、投稿用のパネルを出すためのボタン「AddCommentAppBarBtn」「AddImageAppBarBtn」と、通常非表示の投稿用パネル「AddPanel」をデフォルトのGridViewのテンプレートに追加しています。
また、セマンティックズームを実装するときに利用する「ScrollViewer」の名称を設定しています。

<Style TargetType=”local:CustomSizeGridView”>
 <Setter Property=”Template”>
  <Setter.Value>
   <ControlTemplate TargetType=”local:CustomSizeGridView”>
    <Border BorderBrush=”{TemplateBinding BorderBrush}” BorderThickness=”{TemplateBinding BorderThickness}” Background=”{TemplateBinding Background}”>
     <ScrollViewer x:Name=”ScrollViewer” ・・・ 省略>
      <StackPanel Orientation=”Horizontal” Margin=”{TemplateBinding Padding}”>
       <StackPanel >
        <local:CustomAppBarButton x:Name=”AddCommentAppBarBtn” />
        <local:CustomAppBarButton x:Name=”AddImageAppBarBtn” />

       </StackPanel>
       <Grid x:Name=”AddPanel” Visibility=”Collapsed” />
       <ItemsPresenter ・・・省略/>
      </StackPanel>
     </ScrollViewer>
    </Border>
   </ControlTemplate>
  </Setter.Value>
 </Setter>
</Style>

[2] 幅と高さの違うグリッドビュー

【 Page.xaml 】

グリッドビューのコンテンツによってアイテムの高さと幅を変えるためには、カスタムコントロールの「ItemsPanel」にVariableSizedWrapGridを使います。

<local:CustomSizeGridView>
  <local:CustomSizeGridView.ItemsPanel>
    <ItemsPanelTemplate>
     <VariableSizedWrapGrid />
    </ItemsPanelTemplate>
  </local:CustomSizeGridView.ItemsPanel>
</local:CustomSizeGridView>

【 CustomSizeGridView.cs 】

アイテムに使用するモデル(下の例はCommentクラス)に高さと幅を判別する数値などを持っておき、VariableSizedWrapGridのColumnSpanPropertyやRowSpanPropertyにセットすることで高さと幅を設定できます。

protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
   var comment = item as Comment;
   if (comment != null)
   {
     element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty,comment.ColumnSpan);
     element.SetValue(VariableSizedWrapGrid.RowSpanProperty,comment.RowSpan);

     base.PrepareContainerForItemOverride(element, item);
   }
}

[3] カスタムコントロールに値を渡す

【 CustomSizeGridView.cs 】

カスタムコントロールに値を渡したいときにはプロパティを設定します。

public int TopicId
{
    get { return (int)GetValue(TopicIdProperty); }
    set { SetValue(TopicIdProperty, value); }
}
public static readonly DependencyProperty TopicIdProperty =
    DependencyProperty.Register(
         ”TopicId“,
         typeof(int),
         typeof(CustomSizeGridView),
         new PropertyMetadata(null));

受け取った値から他のプロパティを設定したい場合などには、コールバックメソッドを指定します。

public static readonly DependencyProperty ProgressValueProperty =
     DependencyProperty.Register(
         ”ProgressValue”,
         typeof(string),
         typeof(CustomSizeGridView),
         new PropertyMetadata(default(string),
            (o, args) => ((CustomSizeGridView)o).PropertyChangedCallback()

         ));
//セット対象のプロパティ 
public static readonly DependencyProperty DivisionProperty =
     DependencyProperty.Register(
         ”Division”,
         typeof(Visibility),
         typeof(CustomSizeGridView),
         new PropertyMetadata(null));
//コールバック
private void PropertyChangedCallback()
{
    SetValue(DivisionProperty, Visibility.Collapsed);//セットする 

    :
    :
}

[4] カスタムコントロールでイベントを取得する

【 CustomSizeGridView.cs 】

テンプレートが適用されると、OnApplyTemplateメソッドが呼び出されるため、メソッド内でGetTemplateChildを使うと、カスタムコントロール内の要素の参照を取得できます。カスタムコントロール内の要素にイベントを登録したい場合には、このメソッドを使用してイベントを追加できます。下の例では、ボタンにClickイベントを登録することで、Clickで投稿用のパネル(AddPanel)が表示されるようにしています。
なおGetTemplateChildを使用する場合、[1]のThemes/Generic.xamlで指定した要素にNameを設定しておく必要があります。

private CustomAppBarButton AddCommentAppBarBtn;
private CustomAppBarButton AddImageAppBarBtn;
private Grid AddPanel;
private ScrollViewer ScrollViewer;

protected override void OnApplyTemplate()
{
    this.AddCommentAppBarBtn =
         GetTemplateChild(“AddCommentAppBarBtn”) as CustomAppBarButton;
    this.AddImageAppBarBtn =
         GetTemplateChild(“AddImageAppBarBtn”) as CustomAppBarButton;
    this.AddPanel = GetTemplateChild(“AddPanel”) as Grid;
    this.ScrollViewer = GetTemplateChild(“ScrollViewer”) as ScrollViewer;


    this.AddCommentAppBarBtn.Click += AddCommentAppBarBtn_Click;
    this.AddImageAppBarBtn.Click += AddImageAppBarBtn_Click;

    base.OnApplyTemplate();
}

[5] セマンティックズームのスクロールを実装する

下の例では、セマンティックズームのスクロールの実装を以下の流れでおこなっています。

① SemanticZoomOutViewにおいてアイテムのタップイベントを登録する

掲示板のようにアイテムが変化する場合には、VariableSizedWrapGridのLoadedイベントでVariableSizedWrapGridを確保し、SemanticZoomのViewChangeStartedイベントでSemanticZoomOutViewのアイテムタップイベントを登録するという流れをとっています。

② アイテムのタップイベントでカスタムコントロールに対象となるGridViewItemを渡す

SemanticZoomOutViewのアイテムタップイベントで、①で確保したVariableSizedWrapGridのChildrenから、対象となるアイテム(GridViewItem)を探してカスタムコントロールのイベントに渡します。

【 CustomSizeGridView.cs 】

TipicGridViewWrapGrid.ChildrenのGridViewItemからスクロールのターゲットとなるアイテムを見つけます。
下の例では、基準となるコントロールは位置が不変の部品(下の例はメニュー部分のAddPanel)を選びます。
下記のScrollViewerは、[1]のスタイルで名称を設定し、[4]で参照を取得したScrollViewerコントロールです。

public void SemanticZoomScrollIntoView(GridViewItem image)
{
    GeneralTransform offsetTransform = image.TransformToVisual(AddPanel);

    var offset = offsetTransform.TransformPoint(new Point());
    ScrollViewer.ScrollToHorizontalOffset(offset.X);

}


↓ ↓ ↓ ↓

まとめ

せっかくセマンティックズームというModern UIならではの、便利な操作感があるのでいろんな場面で使いたいのに、GridViewとListViewしか入らないのは正直残念です。「グループを俯瞰して見る」という使い方だけ守って、カスタムコントロールを使って自由な表現ができればオリジナリティも出しやすいかなと感じています。