A classic goofup!

Posted: March 19, 2011 in Windows Phone 7
Tags:

A recent Windows Phone 7 app submission came back with failed certification report. WHAT?? — but our code was perfect and we made sure all app certification guidelines were followed! Well yes, and every developer says so; we had a little laughable Silverlight goofup that was caught, just because the WP7 testing team was diligent.

So, here are two screens with similar scenarios. Both of these use a Listbox that is bound to an Observable object collection behind the scenes:

News Item Feed

Panoramic Home Page

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Error Description:

After the corresponding XAML page loads, click on the first item. This takes you to a details/some other page; hit back & select the first item again. Do you expect to be navigated like the first time? The answer is yes, right? The problem is that the first screen works; but the second does not!! Hmm .. how come? So, some investigation on Silverlight Data binding & event handlers revealed the differences in how the two XAML pages came together.

Page 1:

Data that was bound to the Listbox was fetched/refreshed on every page load; so listbox items were bound every time to the UI. Also, the listbox had a “SelectionChanged” event wired up to handle what to do when the user makes a selection. This worked because every time the listbox rebound itself to the underlying data, the past selection was blown away!

Page 2:

This was actually part of a panoramic view; however, the underlying technique was the same — listbox bound to an observable collection. So, why did this not work? Two reasons:

By default, the panoramic view templates set us up to use a ViewModel pattern, and that totally makes sense. In this case, the home panoramic view did not have dynamic data; and accordingly, we did not have any need to rebind the listbox every time. This was what was happening in the constructor:

 
  public HomePanorama() 
  { 
     InitializeComponent(); 
     this.DataContext = App.PanoramaViewModel;
     this.Loaded += new RoutedEventHandler(MainPage_Loaded);  
  }  

  private void MainPage_Loaded(object sender, RoutedEventArgs e) 
  {
     if (!App.PanoramaViewModel.IsDataLoaded) 
      {
          // Load data for the ViewModel Items. 
          App.PanoramaViewModel.LoadData(); 
      } 
   } 

And here was the main culprit! The listbox also had a “SelectionChanged” event wired up to handle user user clicks. So, on first page load after app launches, we had fresh data & the event fired as usual. On navigation back to the page though, there was no refresh of the underlying data. Also, this was a page served from the BackStack; not a fresh instantiation ! This meant that the XAML page’s Constructor was not fired; and control directly went to Page_Load. I found a very nice descriptive post on the same topic here:

http://devlicio.us/blogs/derik_whittaker/archive/2010/11/30/how-to-determine-if-a-view-is-loaded-off-of-the-backstack-in-wp7.aspx

So, the XAML page, on Back navigation, is being served up from the BackStack.  I believe unless the WP7 runtime sees allocated memory of close to 1MB, garbage collection does not kick in. So, the original Listbox on the XAML page is probably being brought right back from the BackStack memory. And, guess what? We never swapped the underlying dataset, since ViewModels for the Panoramic Home page are instantiated once on App launch; so the UI Listbox happily maintained a “Selected” object from the past selection! And the “SelectionChanged” event, true to its name, does not fire unless there is a change in selection. So, the user hitting the same listbox item got stuck the second time round. DUH !!!

Workaround:

There was a simple way to fix the issue at hand, that being no events fired. Everything else was as planned and rebinding of ViewModel data was unnecessary. So, the simple workaround was to use the “MouseLeftButtonUp” event wiring instead of the “SelectionChanged” on the listbox .. this event fires every time we have the user tap on a link, irrespective of whether we had a past selection or not! There is of course some need to check for null references as below:

 
   <ListBox x:Name="SomeMenu" ItemsSource="{Binding Items}" MouseLeftButtonUp="MenuItem_Selected" >
   ..... 
   </ListBox> 

   private void MenuItem_Selected(object sender, MouseButtonEventArgs e) 
   { 
      if (this.SomeMenu.SelectedItem != null) 
      { 
           SomeItemViewModel menuItem = (SomeItemViewModel)this.SomeMenu.SelectedItem; 
           
           if (menuItem != null) 
           { 
             // Do Stuff. 
           } 
       } 
   } 

Hope this was of some interest & useful if some other poor soul encounters this silly awkward behavior. Please drop comments if you have other ideas of getting around the issue.

Adios!

UPDATE:

So, in the process of finalizing another WP7 app, I realized that there is a downside to the above approach. The core problem still remains .. on a page where data is loaded into a listbox from a ViewModel binding that is not refreshed on every page visit, the regular “SelectionChanged” event handler will have the stupid issue of not firing on subsequent tries after the event fires for the first time. Instead, we hook up the “MouseLeftButtonUp” event handler to do the processing we wanted. The mouse-up event maps to when the user lifts his/her finger from the touch surface. This does add a little more processing on every mouse-up; but mostly works in firing the needed events every time.

However, if you are using a Listbox or a WrapPanel from the SL Toolkit, this has a downside. What if the user was simply swiping through the listbox items or horizontally scrolling through the WrapPanel tiles? Every flick will raise the mouse-up event and your handler will fire, potentially taking the user to a different XAML page!! Crappy, right? So, I had a simple workaround. Once I know that the user has actually made a selection and my handler is taking appropriate action, I clear the “SelectedItem” off from the Listbox binding. This way flicks or scrolls do fire the mouse-up; but we do not take action unless there is a selected item. Here is the code:

 
   private void some_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 
   { 
      if (this.ListBox.SelectedItem != null)  
      { 
          SomeObj selectedItem = (SomeObj)this.ListBox.SelectedItem; 
 
          if (selectedItem != null) 
         { 
            // Do stuff 
          } 

       // Clear the selection so that gestures do not raise this by mistake.  
       this.ListBox.SelectedItem = null; 
      }  
   } 

This may not be the best approach; but works & clears Marketplace certification. I will be very curious to know if others are getting around this in some other way. Please drop comments. Thanks!

About these ads
Comments
  1. [...] back to the page, you must clear the SelectedItem. Interestingly I just spotted a blog post by a WP7 developer who’s application was rejected during the marketplace submission process for forgetting to do just this! protected override void [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s