These last days I have been working on a simple WP8 app that uses TvDb.com to keep track of the next/upcoming series episode to watch.
I made extensively use of the LongListSelector combined with a ContextMenu from the WP8 Toolkit found at CodePlex. I want to be able to short tap (navigate) and long tap (context menu). The DataContext supplied is a Dictionary hence the Key, Value and KeyValuePAir stuff present in the code.
For the xaml I used code like this to make sure my C# code would be able to know which episode to mark as watched when a user long taps a list item (note: I removed all non essential attributes)
1: <phone:PivotItem Header="upcoming">
2: <phone:LongListSelector ItemsSource="{Binding UpcomingEpisodes}" >
3: <phone:LongListSelector.ItemTemplate>
4: <DataTemplate>
5: <StackPanel Tag="{Binding Value.Id}" Tap="Upcoming_Tap">
6: <toolkit:ContextMenuService.ContextMenu>
7: <toolkit:ContextMenu DataContext="{Binding Value.Id}" >
8: <toolkit:MenuItem Header="mark as watched" Click="UpcomingWatched_Click"/>
9: </toolkit:ContextMenu>
10: </toolkit:ContextMenuService.ContextMenu>
11: <TextBlock Text="{Binding Key.SeriesName}" />
12: <StackPanel Orientation="Horizontal">
13: <TextBlock Text="{Binding Value.EpisodeAndSeason}" />
14: <TextBlock Text="{Binding Value.EpisodeName}" />
15: </StackPanel>
16: </StackPanel>
17: </DataTemplate>
18: </phone:LongListSelector.ItemTemplate>
19: </phone:LongListSelector>
note: I removed all non essential attributes.
The C# code is quite simple:
a) For the short tap I use:
1: private void Upcoming_Tap(object sender, System.Windows.Input.GestureEventArgs e)
2: {
3: if (sender is FrameworkElement && (sender as FrameworkElement).Tag != null)
4: {
5: Int32 id = Int32.Parse((sender as FrameworkElement).Tag.ToString());
6:
7: // etc
8: }
9: }
b) For the long tap I use:
1: private void UpcomingWatched_Click(object sender, RoutedEventArgs e)
2: {
3: if (sender is FrameworkElement && (sender as FrameworkElement).DataContext != null)
4: {
5: KeyValuePair<Serie, Episode> dc = (KeyValuePair<Serie, Episode>)((sender as FrameworkElement).DataContext);
6:
7: //etc
8: }
9: }
note: My DataContext is a KeyValuePair so I need to do some typecasting here.
The problem is that after marking a couple of episodes as read, the DataContext of the ContextMenu is not update correctly anymore and I keep marking things watched I do not see in my LongListSelector.
After using Google for two days and found a ‘çomplex’ workaround I did not get working at the one following links ‘we-secretly-have-changed’ or ‘dlaa’ I stumbled across an article at codeproject that led to the solution (I did not get the codeproject code to work in my project but searching for it at msdn did).
I modified my code a tiny bit at three places.
a) I added a
x:name=”UpcomingItem”
attribute to the topmost StackPanel element that defines an LongListSelector Item.
b) I changed the binding of the ContextMenu from”
{binding Value.Id}
into
{Binding ElementName=UpcomingItem},
effectively binding the ContextMenu to it’s parent StackPanel named UpcomingItem (so NOT to it’s DataContext anymore).
1: <phone:PivotItem Header="upcoming">
2: <phone:LongListSelector ItemsSource="{Binding UpcomingEpisodes}" >
3: <phone:LongListSelector.ItemTemplate>
4: <DataTemplate>
5: <StackPanel Tag="{Binding Value.Id}" Tap="Upcoming_Tap" x:Name="UpcomingItem">
6: <toolkit:ContextMenuService.ContextMenu>
7: <toolkit:ContextMenu DataContext="{Binding ElementName=UpcomingItem}" >
8: <toolkit:MenuItem Header="mark as watched" Click="UpcomingWatched_Click"/>
9: </toolkit:ContextMenu>
10: </toolkit:ContextMenuService.ContextMenu>
11: <TextBlock Text="{Binding Key.SeriesName}" />
12: <StackPanel Orientation="Horizontal">
13: <TextBlock Text="{Binding Value.EpisodeAndSeason}" />
14: <TextBlock Text="{Binding Value.EpisodeName}" />
15: </StackPanel>
16: </StackPanel>
17: </DataTemplate>
18: </phone:LongListSelector.ItemTemplate>
19: </phone:LongListSelector>
note: the phone:PivotItem has nothing to do with the problem described in this post.
c) Finally in the C# code I had to modify the retrieval of the DataContext dc variable as the (sender as FrameworkElement).DataContext is now a StackPanel object instead of the KeyValuePair of the original code.
So I changed the line 5 of the C# code piece above to read:
1: StackPanel sp = (StackPanel)(sender as FrameworkElement).DataContext;
2: KeyValuePair<Serie, Episode> dc = (KeyValuePair<Serie, Episode>)(sp.DataContext);
Finally the ContextMenu nicely works on the LongListSelector Item when long tapped, even when the underlying DataSource is updated.