compressionWindows Phone のリストボックスでは、上下にスクロールしたのち端まで行くと一瞬圧縮されます。アプリケーションによってはこれをトリガーにしているアプリケーションもあります。

Windows Phone 7.5(Mango)では、この瞬間を(直接ではないですが)イベントとして取得する事ができます。

■Mangoにはスクロール圧縮が状態定義されている!

からくりを一言でいうと、Windows Phone 7.5(Mango)では、ScrollViewerのVisualStateに"Scrolling"や"NotScrolling"以外に、以下のVisualStateが定義されているのです。

  • VerticalCompression VisualStateGroup
    • CompressionTop
    • CompressionBottom
    • NoVerticalCompression
  • HorizontalCompression VisualStateグループ
    • CompressionLeft
    • CompressionRight
    • NoHorizontalCompression

つまり、このVisualStateGroupの変化を取得し、その時の状態を確認してみればスクロールエンドの状態を取得する事ができるのです。

そして、ListboxはVisualTreeを紐解けば(中の構造を開いてみると)ScrollViewer が使われています。ですから、Listboxのスクロールエンドの状態ももちろん取り出すことができます。

■処理の流れ

手順はこんな感じ

  1. VisualStateが把握できるよう再定義
  2. ListboxからScrollViewerを取り出す
  3. ScrollViewerからVisualStateGroupを取り出す
  4. VisualStateGroupの変化に対するイベントを取得(CurrentStateChanging)
  5. VisualStateの状態を見て必要なState(例 CompressionTop)に合わせて処理を実行する

という感じ。

 

■実装してみよう

まずは新しくプロジェクトを作ってみます。DataBound Application が簡単でしょうね。

① VisualStateの再定義

そして、MainPage.Xaml の17行目あたりに以下のVisualStateの再定義コードを追加(1) 複雑に見えますが、デフォルトのScrollViewerとの定義の違いは青い部分だけ。

<phone:PhoneApplicationPage.Resources>
    <Style TargetType="ScrollViewer">
        <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
        <Setter Property="HorizontalScrollBarVisibility" Value="Disabled"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="BorderBrush" Value="Transparent"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ScrollViewer">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="ScrollStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="00:00:00.5"/>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Scrolling">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="VerticalScrollBar"/>
                                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="HorizontalScrollBar"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="NotScrolling"/>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="VerticalCompression">
                                <VisualState x:Name="NoVerticalCompression"/>
                                <VisualState x:Name="CompressionTop"/>
                                <VisualState x:Name="CompressionBottom"/>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="HorizontalCompression">
                                <VisualState x:Name="NoHorizontalCompression"/>
                                <VisualState x:Name="CompressionLeft"/>
                                <VisualState x:Name="CompressionRight"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid Margin="{TemplateBinding Padding}">
                            <ScrollContentPresenter x:Name="ScrollContentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}"/>
                            <ScrollBar x:Name="VerticalScrollBar" HorizontalAlignment="Right" Height="Auto" IsHitTestVisible="False" IsTabStop="False" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Opacity="0" Orientation="Vertical" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{TemplateBinding VerticalOffset}" ViewportSize="{TemplateBinding ViewportHeight}" VerticalAlignment="Stretch" Width="5"/>
                            <ScrollBar x:Name="HorizontalScrollBar" HorizontalAlignment="Stretch" Height="5" IsHitTestVisible="False" IsTabStop="False" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Opacity="0" Orientation="Horizontal" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{TemplateBinding HorizontalOffset}" ViewportSize="{TemplateBinding ViewportWidth}" VerticalAlignment="Bottom" Width="Auto"/>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</phone:PhoneApplicationPage.Resources>

 

② VisualStateの変化をハンドリング

続けて、MainPage.xaml.cs に、ListboxからScrollViewerを取り出し、その中のVisualStateの変化をハンドリングする関数を追加

private void ListBoxCompressionHandling(ListBox targetlistbox)
{
    VisualStateGroup vgroup = new VisualStateGroup();

    // ListBox の初めに定義されている ScrollViewerを取り出す
    ScrollViewer ListboxScrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(targetlistbox, 0);

    // Visual State はコントロールテンプレートの常に最上位に定義されている
    FrameworkElement element = (FrameworkElement)VisualTreeHelper.GetChild(ListboxScrollViewer, 0);
    // Visual State を取り出しその中から 縦横Compression のVisualStateを取り出す
    foreach (VisualStateGroup group in VisualStateManager.GetVisualStateGroups(element))
        if (group.Name == "VerticalCompression") vgroup = group;

    //縦横Compressionの状態が変わった時のイベントハンドラ
    vgroup.CurrentStateChanging += new EventHandler<VisualStateChangedEventArgs>(ScrollViewer_CurrentStateChanging);
}

③ イベント発生時の処理はご自由に♪

void ScrollViewer_CurrentStateChanging(object sender, VisualStateChangedEventArgs e)
{
    ApplicationTitle.Text = e.NewState.Name;

//switch (e.NewState.Name)
//{
//    case "CompressionTop":
//        break;
//    case "CompressionBottom":
//        break;
//    case "NoVerticalCompression":
//        break;
//    default:
//        break;
//}


}

 

④ Listboxとの紐づけ

最後に、おなじく、MainPage.xaml.cs の中の MainPage_Loaded イベントの中で、リストボックスをハンドリングするように、作成した関数を呼ぶ。

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{

    ListBoxCompressionHandling(MainListBox);
}

 

■まとめ

以上で完了です。Xamlの定義と、ListBoxCompressionHandling 関数だけコピペして使えば、簡単に利用できますし、ScrollViewerで使えば、縦以外に横のスクロールエンドも定義できます。