Search code examples
silverlightwindows-phonesilverlight-toolkit

Windows Phone Toolkit Context Menu Items have wrong object bound to them when an item is removed and then added


I just encountered a serious problem with Context Menu which I cannot resolve for hours.

To reproduce the problem I created a brand new Panorama app with the app templates for Windows Phone 8 in Visual Studio 2012. I installed Windows Phone toolkit via nugget and add context menu in the data template of the first long list selector which is bound to Items

<StackPanel Margin="0,-6,0,12">
    <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
    <toolkit:ContextMenuService.ContextMenu>
        <toolkit:ContextMenu>
            <toolkit:MenuItem Header="{Binding LineOne}" Click="MenuItem_Click_1" Tag="{Binding}">
            </toolkit:MenuItem>
        </toolkit:ContextMenu>
    </toolkit:ContextMenuService.ContextMenu>
</StackPanel>

I set the header to the LineOne property for easier debugging. I attached the following event:

private void MenuItem_Click_1(object sender, RoutedEventArgs e)
{
    var itemViewModel = (ItemViewModel)((MenuItem)sender).Tag;
    App.ViewModel.Items.Remove(itemViewModel);
    App.ViewModel.Items.Add(new ItemViewModel { LineOne = "Test", LineTwo = "Test", LineThree = "Test" });
}

I run the app and use the context menu to remove the first item. The first item disappears and a new item named Test appears at the bottom of the list as expected. If I hold this new item the menu item is bound to "runtime one" (the item that was deleted).

This was the simplest code I could get to reproduce the error but in my real app I have pretty much the same problem with more meaningful code for adding and deleting in different methods and even different pages. I had a command bound but since the databinding is wrong the command is run in the wrong view model with the wrong parameter.

Any idea why this is happening?


Solution

  • The LongListSelector is a virtualized control, which means it instantiates the DataTemplate you specify a fixed number of times (20, I think), and then reuses those Item containers as you scroll down the list, simply moving them around and rebinding their DataContext's. That way, you can have extremely large lists without needing the whole lot to be in the Visual Tree.

    The ContextMenu code, at http://phone.codeplex.com/SourceControl/changeset/view/80797#1335947 has the following lines in the OpenPopup() function;

    if (ReadLocalValue(DataContextProperty) == DependencyProperty.UnsetValue)
    {
        DependencyObject dataContextSource = Owner ?? _rootVisual;
        SetBinding(DataContextProperty, new Binding("DataContext") { Source = dataContextSource });
    }
    

    You'll see that there is a bug here because when the virtualized container rebinds the DataContext, it will not get updated on the _rootVisual, since it's assumed the existing DataContext is the right one. The fix would be to change that check to be:

    if (ReadLocalValue(DataContextProperty) == DependencyProperty.UnsetValue ||
        (DataContext != dataContextSource.DataContext))
    {
        DependencyObject dataContextSource = Owner ?? _rootVisual;
        SetBinding(DataContextProperty, new Binding("DataContext") { Source = dataContextSource });
    }