Monday, October 21, 2013

Managing Data Among Multiple Forms (Part 3)

Part 1 here
Part 2 here

Firstly, let me apologise for having taken so long to finish this three-part series.  Parts 1 and 2 showed how you CAN manage data among multiple forms but this part 3 will show how you SHOULD do it.  That’s rather important I’d say, so let’s get into it.

The first and most important point to note is that forms are objects like any other, so moving data between forms is done just like it is for any other objects.  How do you usually pass data into an object?  You either set a property or else call a method and pass an argument.  How do you usually get data out of an object?  You either get a property or else call a method and get the return value.  That’s exactly how you pass data into and get data out of a form because forms are objects.

The second point to note is that, generally speaking, a control should only be accessed by the form it is on.  While it’s legal to access a control from outside its form, good practice dictates that you should not do so.  With the first point in mind, that means that getting data from a control on a different form means getting data from the other form and the other form getting it from the control.  Likewise, passing data to a control on another form means passing data to the other form and the other form passing it to the control.

This can be demonstrated fairly easily by displaying a list of records in one form and editing the selected record in another form.  To build such an example, start by creating a new Windows Forms Application project.  To Form1, add a DataGridView, a Button and a BindingSource.  Now add the following code to populate the grid with a few records at startup.

C#

  1. private void Form1_Load(object sender, EventArgs e)
  2. {
  3.     var table = new DataTable();
  4.  
  5.     table.Columns.Add("ID", typeof(int));
  6.     table.Columns.Add("Name", typeof(string));
  7.  
  8.     table.Rows.Add(1, "Peter");
  9.     table.Rows.Add(2, "Paul");
  10.     table.Rows.Add(3, "Mary");
  11.  
  12.     this.bindingSource1.DataSource = table;
  13.     this.dataGridView1.DataSource = this.bindingSource1;
  14. }

VB

  1. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  2.     Dim table As New DataTable
  3.  
  4.     With table.Columns
  5.         .Add("ID", GetType(Integer))
  6.         .Add("Name", GetType(String))
  7.     End With
  8.  
  9.     With table.Rows
  10.         .Add(1, "Peter")
  11.         .Add(2, "Paul")
  12.         .Add(3, "Mary")
  13.     End With
  14.  
  15.     Me.BindingSource1.DataSource = table
  16.     Me.DataGridView1.DataSource = Me.BindingSource1
  17. End Sub

Add a second form to the project and add a TextBox and a Button to that form.  What we’re going to do is click the Button in Form1 to open Form2, get the record selected in the DataGridView in Form1 and edit its Name field in the TextBox in Form2.  Now remember, Form2 cannot access the DataGridView in Form1 and Form1 cannot access the TextBox in Form2.  How to move the data back and forth?  The answer is that Form1 will set a property of Form2 to pass the initial data in and get the same property to get the final data out while, internally, that property of Form2 will access the TextBox.  Which property?  Well, one that you define yourself.

C#

  1. public string TextBoxText
  2. {
  3.     get
  4.     {
  5.         return this.textBox1.Text;
  6.     }
  7.     set
  8.     {
  9.         this.textBox1.Text = value;
  10.     }
  11. }

VB

  1. Public Property TextBoxText As String
  2.     Get
  3.         Return Me.TextBox1.Text
  4.     End Get
  5.     Set(value As String)
  6.         Me.TextBox1.Text = value
  7.     End Set
  8. End Property

Back in Form1, we need to handle the Click event of the Button, open Form2 and pass it the Name from the record selected in the DataGridView.

C#

  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3.     using (var dialogue = new Form2())
  4.     {
  5.         var selectedRecord = (DataRowView)this.bindingSource1.Current;
  6.  
  7.         // Pass the initial data into the dialogue.
  8.         dialogue.TextBoxText = (string)selectedRecord["Name"];
  9.  
  10.         // Display the modal dialogue.
  11.         if (dialogue.ShowDialog() == DialogResult.OK)
  12.         {
  13.             // The user clicked OK so get the final data from the dialogue.
  14.             selectedRecord["Name"] = dialogue.TextBoxText;
  15.             this.bindingSource1.EndEdit();
  16.         }
  17.     }
  18. }

VB

  1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  2.     Using dialogue As New Form2
  3.         Dim selectedRecord = DirectCast(Me.BindingSource1.Current, DataRowView)
  4.  
  5.         'Pass the initial data into the dialogue.
  6.         dialogue.TextBoxText = CStr(selectedRecord("Name"))
  7.  
  8.         'Display the modal dialogue.
  9.         If dialogue.ShowDialog() = DialogResult.OK Then
  10.             'The user clicked OK so get the final data from the dialogue.
  11.             selectedRecord("Name") = dialogue.TextBoxText
  12.             Me.BindingSource1.EndEdit()
  13.         End If
  14.     End Using
  15. End Sub

All that’s left to do now is for Form2 to return a result of OK when the user clicks the Button.

C#

  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3.     this.DialogResult = DialogResult.OK;
  4. }

VB

  1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  2.     Me.DialogResult = DialogResult.OK
  3. End Sub

Run the project now and Form1 will appear displaying the three records.  Select one of the records and click the Button.  You will see Form2 open with the Name field value from the selected record in the TextBox.  Try editing the name and then click the Close button on the title bar of Form2.  The dialogue will close and you’ll see that the Name field of the selected record remains unchanged.  That’s because the DialogResult returned by ShowDialog was Cancel rather than OK.

Click the Button on Form1 again and this time, after editing the name in the TextBox, click the Button on Form2.  This time, you’ll see that Form2 closes and the Name field of the selected record is updated to the value that you entered in the TextBox.  Congratulations!  You just passed data between two forms the right way.

That’s nice and all but what if, in our example, you wanted to do something back in Form1 without closing Form2?  As it stands, ShowDialog returning is Form1’s notification that it should get some data from Form2 and update its own DataGridView.  If we don’t call ShowDialog though, it can’t return and we can’t use it as a notification.  What to do?  Well, how are you usually notified that something has happened in .NET code?  You handle an event.

If you want to learn all the details about custom events, I suggest that you check out my blog post here.  I’m going to do it quick and dirty here because the point of this post is how to handle the event and use that notification rather than the details of how to generate it in the first place.

In Form2, we need to add an event that will notify anyone listening that the text in the TextBox has changed and, instead of closing the form when the Button is clicked, we need to raise that event.

C#

  1. public event EventHandler TextBoxTextChanged;
  2.  
  3. private void button1_Click(object sender, EventArgs e)
  4. {
  5.     if (this.TextBoxTextChanged != null)
  6.     {
  7.         this.TextBoxTextChanged(this, EventArgs.Empty);
  8.     }
  9. }

VB

  1. Public Event TextBoxTextChanged As EventHandler
  2.  
  3. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  4.     RaiseEvent TextBoxTextChanged(Me, EventArgs.Empty)
  5. End Sub

We now need to handle that event in Form1.  When it’s raised, we need to do as before and get the data from Form2 to update the selected record.

C#

  1. private Form2 dialogue;
  2. private DataRowView selectedRecord;
  3.  
  4. private void button1_Click(object sender, EventArgs e)
  5. {
  6.     if (this.dialogue == null || this.dialogue.IsDisposed)
  7.     {
  8.         this.selectedRecord = (DataRowView)this.bindingSource1.Current;
  9.  
  10.         this.dialogue = new Form2();
  11.         this.dialogue.TextBoxTextChanged += dialogue_TextBoxTextChanged;
  12.         this.dialogue.FormClosed += dialogue_FormClosed;
  13.  
  14.         // Pass the initial data into the dialogue.
  15.         this.dialogue.TextBoxText = (string)this.selectedRecord["Name"];
  16.  
  17.         this.dialogue.Show();
  18.     }
  19.  
  20.     this.dialogue.Activate();
  21. }
  22.  
  23. private void dialogue_TextBoxTextChanged(object sender, EventArgs e)
  24. {
  25.     // Get the final data from the dialogue.
  26.     this.selectedRecord["Name"] = dialogue.TextBoxText;
  27.     this.bindingSource1.EndEdit();
  28. }
  29.  
  30. private void dialogue_FormClosed(object sender, FormClosedEventArgs e)
  31. {
  32.     // Remove event handlers when the form closes.
  33.     this.dialogue.TextBoxTextChanged -= dialogue_TextBoxTextChanged;
  34.     this.dialogue.FormClosed -= dialogue_FormClosed;
  35. }

VB

  1. Private WithEvents dialogue As Form2
  2. Private selectedRecord As DataRowView
  3.  
  4. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  5.     If Me.dialogue Is Nothing OrElse Me.dialogue.IsDisposed Then
  6.         Me.selectedRecord = DirectCast(Me.BindingSource1.Current, DataRowView)
  7.  
  8.         Me.dialogue = New Form2()
  9.  
  10.         'Pass the initial data into the dialogue.
  11.         Me.dialogue.TextBoxText = CStr(selectedRecord("Name"))
  12.  
  13.         Me.dialogue.Show()
  14.     End If
  15.  
  16.     Me.dialogue.Activate()
  17. End Sub
  18.  
  19. Private Sub dialogue_TextBoxTextChanged(sender As Object, e As EventArgs) Handles dialogue.TextBoxTextChanged
  20.     'Get the final data from the dialogue.
  21.     Me.selectedRecord("Name") = dialogue.TextBoxText
  22.     Me.BindingSource1.EndEdit()
  23. End Sub

If you run the project again you will see that you can open the dialogue and edit the selected record multiple times without closing the dialogue.  If you close the dialogue and select another record then you can open a new dialogue and edit that as well.

This is a slightly contrived example but hopefully you get the idea.  If you want to update a control in a form then only do it in that form.  If you need to push and/or pull data between forms then you do so by getting or setting properties and/or calling methods of that form.  If you need to notify a form that data is available to get then you do so with an event.

Happy trails!

Post compiled using Windows Live Writer with Paste as Visual Studio Code plug-in.

Thursday, October 17, 2013

Turnstile Feather Transition with a Pivot Control on Windows Phone 8

I’ve been intending to make my first foray into Windows Phone development for a while now, but I had decided to put it off until I actually owned a Windows Phone device.  I recently completed the 24 month contract on my previous phone (a Nokia E7 running Symbian Belle, which I was actually pretty happy with) and upgraded to a Nokia Lumia 925.

I’m very happy with the phone and I really like the Windows Phone OS… and I’m now a Windows Phone developer.  I’m yet to actually publish my first app but I wanted to share with you an interesting issue I encountered and the solution I came up with.  I couldn’t find any other information specific to this issue so I thought that this might be useful.

I had several pages in place to list, display, add and edit several entities so I decided I would look at adding page transitions then rather than going back and retrofitting them to every page at the end.  I decided to use the TurnstileTransition on the display, add and edit pages and the TurnstileFeatherTransition on the list pages, both from the Windows Phone Toolkit.

Several of my list pages contained just a single LongListSelector.  They worked fine simply by setting the TurnstileFeatherEffect.FeatheringIndex attached property for the page elements as you would expect.  One of the pages contained several LongListSelectors inside a Pivot though, and that page didn’t quite work as it should.  Let’s build a test app so that you can see what I mean.

Start by creating a new Windows Phone Pivot App project.  We’re going to use NuGet to add the Windows Phone Toolkit package so if, like me, you have configured VS not to immediately save Windows projects, you’ll have to immediately save your project.  For some reason NuGet won’t add packages to projects in a temporary location.

Once the project is saved, select Manage NuGet Packages from the Project menu.  In the dialogue, select the ‘nuget.org’ item in the Online section and then search for windows phone toolkit.  Install the package and you now have page transitions at you disposal.

We next need to enable transitions for all pages in the app.  To do that, open the code file behind App.xaml, i.e. App.xaml.cs or App.xaml.vb.  In the InitializePhoneApplication method, find the line that reads like this:

C#

  1. RootFrame = new PhoneApplicationFrame();

VB

  1. RootFrame = New PhoneApplicationFrame()

and change it to this:

C#

  1. RootFrame = new TransitionFrame();

VB

  1. RootFrame = New TransitionFrame()

If we’re going to have page transitions then we need to have at least two pages to navigate between.  Our project gave us MainPage.xaml for free but we need to add a second page.  From the Project menu, select Add New Item and then add a new Windows Phone Portrait Page.

Now that we have a second page to navigate to, let’s add the Turnstile transition to it.  First, we’ll need to import the ‘toolkit’ namespace, so add the following attribute to the main <phone: PhoneApplicationPage> element:

XAML

  1. xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"

You can now add the following elements inside the main element:

XAML

  1. <toolkit:TransitionService.NavigationInTransition>
  2.     <toolkit:NavigationInTransition>
  3.         <toolkit:NavigationInTransition.Backward>
  4.             <toolkit:TurnstileTransition Mode="BackwardIn"/>
  5.         </toolkit:NavigationInTransition.Backward>
  6.         <toolkit:NavigationInTransition.Forward>
  7.             <toolkit:TurnstileTransition Mode="ForwardIn"/>
  8.         </toolkit:NavigationInTransition.Forward>
  9.     </toolkit:NavigationInTransition>
  10. </toolkit:TransitionService.NavigationInTransition>
  11. <toolkit:TransitionService.NavigationOutTransition>
  12.     <toolkit:NavigationOutTransition>
  13.         <toolkit:NavigationOutTransition.Backward>
  14.             <toolkit:TurnstileTransition Mode="BackwardOut"/>
  15.         </toolkit:NavigationOutTransition.Backward>
  16.         <toolkit:NavigationOutTransition.Forward>
  17.             <toolkit:TurnstileTransition Mode="ForwardOut"/>
  18.         </toolkit:NavigationOutTransition.Forward>
  19.     </toolkit:NavigationOutTransition>
  20. </toolkit:TransitionService.NavigationOutTransition>

Notice the use of the TurnstileTransition.  That’s now going to provide a nice effect whenever we navigate to or from that page.

We need to do something similar, although not exactly the same, in MainPage.xaml.  The ‘toolkit’ namespace is imported the same way and the transitions follow the same pattern but, this time, we use the TurnstileFeatherTransition.

XAML

  1. <toolkit:TransitionService.NavigationInTransition>
  2.     <toolkit:NavigationInTransition>
  3.         <toolkit:NavigationInTransition.Backward>
  4.             <toolkit:TurnstileFeatherTransition Mode="BackwardIn"/>
  5.         </toolkit:NavigationInTransition.Backward>
  6.         <toolkit:NavigationInTransition.Forward>
  7.             <toolkit:TurnstileFeatherTransition Mode="ForwardIn"/>
  8.         </toolkit:NavigationInTransition.Forward>
  9.     </toolkit:NavigationInTransition>
  10. </toolkit:TransitionService.NavigationInTransition>
  11. <toolkit:TransitionService.NavigationOutTransition>
  12.     <toolkit:NavigationOutTransition>
  13.         <toolkit:NavigationOutTransition.Backward>
  14.             <toolkit:TurnstileFeatherTransition Mode="BackwardOut"/>
  15.         </toolkit:NavigationOutTransition.Backward>
  16.         <toolkit:NavigationOutTransition.Forward>
  17.             <toolkit:TurnstileFeatherTransition Mode="ForwardOut"/>
  18.         </toolkit:NavigationOutTransition.Forward>
  19.     </toolkit:NavigationOutTransition>
  20. </toolkit:TransitionService.NavigationOutTransition>

When using TurnstileFeatherTransition, we have to specify the order in which elements are animated.  To do that, we add the TurnstileFeatherEffect.FeatheringIndex attached property to each of the main elements.  Our MainPage.xaml contains a Pivot with two PivotItems that each contain a LongListSelector.  We need to specify that the Pivot is animated first, followed by the LongListSelectors.

Obviously only the contents of one PivotItem will be displayed at a time, so only one LongListSelector will ever be animated.  My first thought was just to set the indexes in sequence, i.e. 0 for the Pivot, 1 for the first LongListSelector, 2 for the second LongListSelector and so on.

XAML

  1. <!--Pivot Control-->
  2. <phone:Pivot Title="MY APPLICATION" toolkit:TurnstileFeatherEffect.FeatheringIndex="0">
  3.     <!--Pivot item one-->
  4.     <phone:PivotItem Header="first">
  5.         <!--Double line list with text wrapping-->
  6.         <phone:LongListSelector x:Name="List1"
  7.                                 Margin="0,0,-12,0"
  8.                                 ItemsSource="{Binding Items}"
  9.                                 toolkit:TurnstileFeatherEffect.FeatheringIndex="1"
  10.                                 SelectionChanged="List1_SelectionChanged">
  11.             <phone:LongListSelector.ItemTemplate>
  12.                 <DataTemplate>
  13.                     <StackPanel Margin="0,0,0,17">
  14.                         <TextBlock Text="{Binding LineOne}"
  15.                                    TextWrapping="Wrap"
  16.                                    Style="{StaticResource PhoneTextExtraLargeStyle}"/>
  17.                         <TextBlock Text="{Binding LineTwo}"
  18.                                    TextWrapping="Wrap"
  19.                                    Margin="12,-6,12,0"
  20.                                    Style="{StaticResource PhoneTextSubtleStyle}"/>
  21.                     </StackPanel>
  22.                 </DataTemplate>
  23.             </phone:LongListSelector.ItemTemplate>
  24.         </phone:LongListSelector>
  25.     </phone:PivotItem>
  26.  
  27.     <!--Pivot item two-->
  28.     <phone:PivotItem Header="second">
  29.         <!--Double line list no text wrapping-->
  30.         <phone:LongListSelector x:Name="List2"
  31.                                 Margin="0,0,-12,0"
  32.                                 ItemsSource="{Binding Items}"
  33.                                 toolkit:TurnstileFeatherEffect.FeatheringIndex="2"
  34.                                 SelectionChanged="List2_SelectionChanged">
  35.             <phone:LongListSelector.ItemTemplate>
  36.                     <DataTemplate>
  37.                         <StackPanel Margin="0,0,0,17">
  38.                             <TextBlock Text="{Binding LineOne}"
  39.                                        TextWrapping="NoWrap"
  40.                                        Margin="12,0,0,0"
  41.                                        Style="{StaticResource PhoneTextExtraLargeStyle}"/>
  42.                             <TextBlock Text="{Binding LineThree}"
  43.                                        TextWrapping="NoWrap"
  44.                                        Margin="12,-6,0,0"
  45.                                        Style="{StaticResource PhoneTextSubtleStyle}"/>
  46.                         </StackPanel>
  47.                     </DataTemplate>
  48.             </phone:LongListSelector.ItemTemplate>
  49.         </phone:LongListSelector>
  50.     </phone:PivotItem>
  51. </phone:Pivot>

We can then handle the SelectionChanged event of each LongListSelector and navigate to the second page to see our transition in action.

C#

  1. private void List1_SelectionChanged(object sender, SelectionChangedEventArgs e)
  2. {
  3.     NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
  4. }
  5.  
  6. private void List2_SelectionChanged(object sender, SelectionChangedEventArgs e)
  7. {
  8.     NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
  9. }

VB

  1. Private Sub List1_SelectionChanged(ByVal sender As Object, ByVal e As SelectionChangedEventArgs)
  2.     NavigationService.Navigate(New Uri("/Page1.xaml", UriKind.Relative))
  3. End Sub
  4.  
  5. Private Sub List2_SelectionChanged(ByVal sender As Object, ByVal e As SelectionChangedEventArgs)
  6.     NavigationService.Navigate(New Uri("/Page1.xaml", UriKind.Relative))
  7. End Sub

If you now run the project and select an item from the first list, you’ll see the intended page transition in all its glory.  The items on the page flip out one after the other in a smooth series and then the next page flips in.  Hit the Back button and the second page flips back and the first page appears again, one item at a time.  All seems wonderful.

Now, select the second list on the pivot page and try the same thing.  Something is not quite right.  You’ll notice that the page title and list headers flip out first, as they should, but then there’s a little bit of a delay before the list items start to flip out.  They still feather as they should but that little delay detracts from the overall UX.  What to do?

Given that the first list animates correctly and the second doesn’t, I wondered whether setting the index of both lists to 1 would do the trick.  That didn’t help, nor did any other combination of static values.  It occurred to me that maybe changing the indexes as the selected PivotItem changes might fix the issue.  As only one list can be visible, and therefore animated, at a time, I figured that it would make sense that only the selected list have a valid index.  An element that doesn’t get animated has an index of –1, so I decided to not set the values in XAML but rather do it dynamically in code instead.

XAML

  1. <!--Pivot Control-->
  2. <phone:Pivot x:Name="MainPivot"
  3.              Title="MY APPLICATION"
  4.              toolkit:TurnstileFeatherEffect.FeatheringIndex="0"
  5.              SelectionChanged="MainPivot_OnSelectionChanged">
  6.     <!--Pivot item one-->
  7.     <phone:PivotItem x:Name="Item1" Header="first">
  8.         <!--Double line list with text wrapping-->
  9.         <phone:LongListSelector x:Name="List1"
  10.                                 Margin="0,0,-12,0"
  11.                                 ItemsSource="{Binding Items}"
  12.                                 SelectionChanged="List1_SelectionChanged">
  13.             <phone:LongListSelector.ItemTemplate>
  14.                 <DataTemplate>
  15.                     <StackPanel Margin="0,0,0,17">
  16.                         <TextBlock Text="{Binding LineOne}"
  17.                                    TextWrapping="Wrap"
  18.                                    Style="{StaticResource PhoneTextExtraLargeStyle}"/>
  19.                         <TextBlock Text="{Binding LineTwo}"
  20.                                    TextWrapping="Wrap"
  21.                                    Margin="12,-6,12,0"
  22.                                    Style="{StaticResource PhoneTextSubtleStyle}"/>
  23.                     </StackPanel>
  24.                 </DataTemplate>
  25.                                     </phone:LongListSelector.ItemTemplate>
  26.         </phone:LongListSelector>
  27.     </phone:PivotItem>
  28.  
  29.     <!--Pivot item two-->
  30.     <phone:PivotItem x:Name="Item2" Header="second">
  31.         <!--Double line list no text wrapping-->
  32.         <phone:LongListSelector x:Name="List2"
  33.                                 Margin="0,0,-12,0"
  34.                                 ItemsSource="{Binding Items}"
  35.                                 SelectionChanged="List2_SelectionChanged">
  36.             <phone:LongListSelector.ItemTemplate>
  37.                 <DataTemplate>
  38.                     <StackPanel Margin="0,0,0,17">
  39.                         <TextBlock Text="{Binding LineOne}"
  40.                                    TextWrapping="NoWrap"
  41.                                    Margin="12,0,0,0"
  42.                                    Style="{StaticResource PhoneTextExtraLargeStyle}"/>
  43.                         <TextBlock Text="{Binding LineThree}"
  44.                                    TextWrapping="NoWrap"
  45.                                    Margin="12,-6,0,0"
  46.                                    Style="{StaticResource PhoneTextSubtleStyle}"/>
  47.                     </StackPanel>
  48.                 </DataTemplate>
  49.             </phone:LongListSelector.ItemTemplate>
  50.         </phone:LongListSelector>
  51.     </phone:PivotItem>
  52. </phone:Pivot>

C#

  1. private void MainPivot_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  2. {
  3.     var listsByItem = new Dictionary<PivotItem, LongListSelector> {{Item1, List1}, {Item2, List2}};
  4.  
  5.     foreach (PivotItem item in MainPivot.Items)
  6.     {
  7.         TurnstileFeatherEffect.SetFeatheringIndex(listsByItem[item], item == MainPivot.SelectedItem ? 1 : -1);
  8.     }                                                              
  9. }

VB

  1. Private Sub MainPivot_OnSelectionChanged(ByVal sender As Object, ByVal e As SelectionChangedEventArgs)
  2.     Dim listsByItem As New Dictionary(Of PivotItem, LongListSelector) From {{Item1, List1},
  3.                                                                             {Item2, List2}}
  4.  
  5.     For Each item As PivotItem In MainPivot.Items
  6.         TurnstileFeatherEffect.SetFeatheringIndex(listsByItem(item), If(item Is MainPivot.SelectedItem, 1, -1))
  7.     Next
  8. End Sub

With that code, every time the selection in the Pivot changes, the indexes of all the lists get reset.  If a PivotItem is selected then the LongListSelector it contains will have its index set to 1, otherwise the list index will be set to –1.  Run the project now and you’ll see that the animation is as intended for both lists.  My original project has four PivotItems and they all animate correctly so I’m assuming that it will work for an arbitrary number.

If anyone has a better solution then I’d love to hear about it but this seems to do the job well and without too much effort.