WP8: 一个在ListBox 中ItemTemplate中放入ScrollViewer导致SelectedChangd不能触发的状况,及其解决办法

在做Windows Phone App开发中,有一项内容是使用ListBox并且将TextBlock放到ItemTemplate中,参考以下代码:

  1: <ListBox x:Name="MyListBox" HorizontalAlignment="Left" SelectionChanged="MyListBox_SelectionChanged" >
  2:     <ListBox.ItemTemplate>
  3:         <DataTemplate>
  4:             <StackPanel Orientation="Horizontal" Background="Blue" Height="80"  Margin="15,0,0,7">
  5:                 <TextBlock Text="{Binding SomeContent}" FontSize="25"/>
  6:             </StackPanel>
  7:         </DataTemplate>
  8:     </ListBox.ItemTemplate>
  9: </ListBox>

在这个示例中,我创建了一个ListBox,并且把TextBlock放到了ItemTemplate中。在把TextBlock填充完之前,都是好的。

当我把StackPanel固定高度后,TextBlock不能显示全部内容。不过如果不这样,这个列表里面的内容就会显示很乱。所以,我决定在TextBlock旁边加一个ScrollViewer,这样用户就可以上下滑动显示内容。

  1: <ListBox x:Name="MyListBox" HorizontalAlignment="Left" SelectionChanged="MyListBox_SelectionChanged" >
  2:     <ListBox.ItemTemplate>
  3:         <DataTemplate>
  4:             <StackPanel Orientation="Horizontal" Background="Blue" Height="80">
  5:                 <ScrollViewer HorizontalScrollBarVisibility="Hidden">
  6:                     <TextBlock Text="{Binding SomeContent}" FontSize="25"/>
  7:                 </ScrollViewer>
  8:             </StackPanel>
  9:         </DataTemplate>
  10:     </ListBox.ItemTemplate>
  11: </ListBox>

不过,这样做过之后,问题就会出现了!SelectionChanged事件没有办法探测到事件改编,虽然TextBlock中的文字可以滑动。这样会导致一个后果,就是我们没有办法与之交互,例如没有办法通过点击跳转到另外页面。

为了追根究底发生了什么,我放了一个Tap事件到StackPanel里面,这里面含有一个ScrollViewer。调试的结果如下,无论哪一个ListItem被点击了,ListBox中SelectedIndex一直被标为-1. 这样导致的后果就是,ScrollViewer探知哪一个ListBox被选中的方法被禁止了。像是Windows Store App开发中的 Gestures, manipulations, and interactions,Windows Phone对ScrollViewer的交互做了定义,这有时候会让开发人员感到困惑,因为它不能给我们一个正确的反馈。

不过,这里有一个短小的代码片段可以解决这个问题,

  1: <ListBox x:Name="MyListBox" HorizontalAlignment="Left" SelectionChanged="MyListBox_SelectionChanged" >
  2:     <ListBox.ItemTemplate>
  3:         <DataTemplate>
  4:             <StackPanel Orientation="Horizontal" Background="Blue" Height="80" Tap="StackPanel_Tap">
  5:                 <ScrollViewer HorizontalScrollBarVisibility="Hidden">
  6:                     <TextBlock Text="{Binding Name}" FontSize="25"/>
  7:                 </ScrollViewer>
  8:             </StackPanel>
  9:         </DataTemplate>
  10:     </ListBox.ItemTemplate>
  11: </ListBox>

在这个XAML代码后面,我定义了一个FindParent方法去得到某一特定类型的第一个父类。虽然很简单,但是很有效:

  1: private void StackPanel_Tap(object sender, GestureEventArgs e)
  2: {
  3:     var listitem = FindParent(sender as StackPanel, typeof(ListBoxItem)) as ListBoxItem;
  4:     listitem.IsSelected = true;            
  5: }
  6:  
  7: private DependencyObject FindParent(DependencyObject child, Type type)
  8: {
  9:     var parent = VisualTreeHelper.GetParent(child);
  10:  
  11:     if (parent != null && !type.IsInstanceOfType(parent))
  12:         return FindParent(parent, type);
  13:     else
  14:         return parent;
  15: }

最终,ListBox和ScrollViewer中的SelectionChanged工作正常了!

English Version: https://blogs.msdn.com/b/lighthouse/archive/2013/10/21/windows-phone-8-app-scrollviewer-issue-inside-item-template-with-selectedchanged-in-listbox.aspx