9
votes

I wrote an app for Windows Phone 7, recently I've upgraded it to Windows Phone 8 and I plan on adding some features. Unfortunately, I've run into a problem immediately after the upgrade. The main part of the app is a Panorama control that is databound. On SelectionChanged I am fetching the data for the new PanoramaItem + 1 (preselecting data so it's there when the person eventually goes to the item). That worked fine in WP7 but the SelectionChanged event doesn't fire with WP8.

I've reproduced the issue with a new WP8 app that wasn't upgraded and it's also isolated to databound controls. If I statically add PanoramaItems the SelectionChanged event fires fine.

Am I missing something or is this just a straight up bug in WP8? Any recommended work-arounds?

I have a GitHub repo with a static sample and a databound sample to show what works and what doesn't work. https://github.com/bthubbard/DatabindingIssues

2

2 Answers

18
votes

The Panorama control in WP8 has a known databinding bug. The symptoms of the bug are that SelectionChanged doesn't fire, SelectedIndex & SelectedItem aren't reliable and that back navigation into a page with Panorama resets the panorama selected item.

For example, the following code sample will never fire the MessageBox and SelectedIndex & SelectedItem won't indicate the correct expected values.

<phone:Panorama x:Name="panorama"
                ItemsSource="{Binding}" 
                SelectionChanged="Panorama_SelectionChanged_1">
    <phone:Panorama.HeaderTemplate>
        <DataTemplate>
            <ContentControl Content="{Binding Name}" />
        </DataTemplate>
    </phone:Panorama.HeaderTemplate>
    <phone:Panorama.ItemTemplate>
        <DataTemplate>
            <ContentControl Content="{Binding Name}" />
        </DataTemplate>
    </phone:Panorama.ItemTemplate>
</phone:Panorama>
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = new ObservableCollection<Cow>()
                           {
                               new Cow("Foo"),
                               new Cow("Bar"),
                               new Cow("Baz")
                           };
}

private void Panorama_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
    MessageBox.Show("Panorama_SelectionChanged_1: " + panorama.SelectedIndex);
}

public class Cow
{
    public Cow(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}

One obvious fix will be to manually initialize PanoramaItems in code-behind.

Another solution would be to change our collection from typed to untyped, and add the following code snippet to our bounded data class. So let's change our code from ObservableCollection<Cow> to ObservableCollection<object> and add some code to the Cow class:

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = new ObservableCollection<object>()
                           {
                               new Cow("Foo"),
                               new Cow("Bar"),
                               new Cow("Baz")
                           };
}

public class Cow
{
    public Cow(string name)
    {
        Name = name;
    }

    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        if ((obj != null) && (obj.GetType() == typeof(PanoramaItem)))
        {
            var thePanoItem = (PanoramaItem)obj;

            return base.Equals(thePanoItem.Header);
        }
        else
        {
            return base.Equals(obj);
        }
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

Now, when we run this code snippet we can see SelectionChanged fires as expected with the correct SelectedIndex values:

Panorama firing the SelecitonChanged event with the correct SelectedIndexPanorama firing the SelecitonChanged event with the correct SelectedIndex

2
votes

Just a minor tip for anyone who has the ViewModel in a Portable Class Library - I put this code in the base class for my viewmodels:

if (Equals(obj.GetType().Name, "PanoramaItem"))
{
    var datacontextProperty = obj.GetType().GetRuntimeProperty("DataContext");
    var datacontext = datacontextProperty.GetValue(obj);
    return Equals(datacontext, this);
}

This solved the problem for me. As for the comment from @Sopuli - I definitely still have this problem on the WP8 devices I have tested. (Nokia Lumia 920, WP8.0.10517.150)


A VB.NET version:

Public Overrides Function Equals(obj As Object) As Boolean
    If Equals(obj.GetType.Name, "PanoramaItem") Then
        Dim datacontextProperty = System.Reflection.RuntimeReflectionExtensions.GetRuntimeProperty(obj.GetType, "DataContext")
        Dim datacontext = datacontextProperty.GetValue(obj)
        Return Equals(datacontext, Me)
    Else
        Return MyBase.Equals(obj)
    End If
End Function