WP7.5 でリストボックスのスクロールエンドで圧縮されるときのイベントを取る

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で使えば、縦以外に横のスクロールエンドも定義できます。