Windows Phone 8 App: ScrollViewer Issue inside Item Template with SelectedChanged in ListBox

While working with Windows Phone App Development, one of the commonest tasks is to use ListBox and put TextBlock inside ItemTemplate, considering the XAML below:

    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>

In this sample, I created a ListBox and nest a TextBlock inside ItemTemplate, which works fine until we need to put more words into the TextBlock.

Once I fix the height of the StackPanel, TextBlock won’t display all content, but if not, the list displays content muzzily. So I decide to add ScrollViewer for TextBlock, so that users could scroll up and down like:

    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>

But after that, problems come with this change. The SelectionChanged event can’t detect the selected list item changing, though the text in TextBlock could be scrolled, we can’t perform interaction with click listbox items like redirect to other page or other selection actions.

To judge what happened, I put a tap event for StackPanel which contains the ScrollViewer, the debug result turns out that, no matter which list item is clicked, the SelectedIndex of ListBox is always “-1”. ScrollViewer blocks the manipulation for ListBox to know which list item is selected. Quite like the Gestures, manipulations, and interactions in Windows Store apps, Windows Phone defines touch interactions for ScrollViewer, which sometimes confuses the developers as it does not give the result we wanted.

One simplest solution for this issue, is to get the parent ListItem for StackPanel tapped, and set the right selected item for ListBox, the final XAML like:

    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>

In code behind, I defined a FindParent function to get the first parent of a specific type for any of control derived from DependencyObject, it’s an easy implementation, but works efficiently:

    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: }

Finally, both SelectionChanged event of ListBox and ScrollViewer work smoothly as we expected.

- Jazzen