Hamish tweeted at me about a bug published on the Connect system;
Now, naturally I’m sure that nothing is being ‘brushed off’ here but I thought that I recognised the scenario and the last time I looked at it I wasn’t at all sure that I’d figured out any way to work around it so I thought I’d write a little about it here. There’s more of a write-up on it up in the forums as well and again with a possible workaround.
It’s an “issue” that you can see in certain applications in the Windows 8 Store. I’m not picking on applications but if you take a look at an application like “BBC News Mobile”
and we tap onto a story like “Australia’s Good Life Comes At a Price” then what happens is first;
and then;
So, what happens is that just for a moment the first story flashes onto the screen and then the story that we actually selected pops onto the screen. There’s a slight flicker.
Similarly, if I’m using an application like “Sky News” I get a similar effect although I struggled to capture a screenshot of it happening because it was so quick.
I wrote a little about this in this post in the section called “Step 5” but I thought I’d pull it out into something separate here.
Experimenting
I thought I’d try an experiment. In a .NET Windows Store app, I set up a little data source;
class DataItem { public string Name { get; set; } public UInt32 Argb { get; set; } } class Data : BindableBase { static Data() { _colors = new DataItem[] { new DataItem() { Name = "red", Argb = 0xFFFF0000 }, new DataItem() { Name = "green", Argb = 0xFF00FF00 }, new DataItem() { Name = "blue", Argb = 0xFF0000FF } }; } public DataItem[] Colors { get { Debug.WriteLine("Colours property getter"); return (_colors); } } public DataItem CurrentItem { get { Debug.WriteLine("CurrentItem property getter returning {0}", this._currentItem != null ? this._currentItem.Name : "null"); return (this._currentItem); } set { base.SetProperty(ref this._currentItem, value); Debug.WriteLine("CurrentItem property setter changing value to {0}", this._currentItem != null ? this._currentItem.Name : "null"); } } DataItem _currentItem; static DataItem[] _colors; }
And defined a single instance of that Data class in my App.xaml file;
<Application x:Class="App27.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App27"> <Application.Resources> <ResourceDictionary> <local:Data x:Key="data" /> <local:ArgbToBrushConverter x:Key="argbToBrushConverter" /> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Common/StandardStyles.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>
and wrote a page which would use a GridView to display the colours from my data-set as rectangles masquerading as buttons that could be clicked on;
<Page x:Class="App27.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App27" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <GridView DataContext="{StaticResource data}" ItemsSource="{Binding Colors}" SelectionMode="Single" SelectedItem="{Binding CurrentItem,Mode=TwoWay}"> <GridView.ItemTemplate> <DataTemplate> <Grid> <Button Template="{x:Null}" Click="NavigateToFlipViewPage"> <Grid Width="250" Height="250"> <Rectangle Fill="{Binding Argb,Converter={StaticResource argbToBrushConverter}}" /> </Grid> </Button> </Grid> </DataTemplate> </GridView.ItemTemplate> </GridView> </Grid> </Page>
And that’s all fine and, as you might expect if I select a button in my GridView;
then I can see in my tracing code;
Colours property getter
CurrentItem property getter returning nullCurrentItem property setter changing value to blue
which feels like a reasonable thing going on.
Adding a Second Page with a FlipView
I added another page which contained a FlipView;
<common:LayoutAwarePage x:Name="pageRoot" x:Class="App27.FlipViewPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App27" xmlns:common="using:App27.Common" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Style="{StaticResource LayoutRootStyle}" DataContext="{StaticResource data}"> <Grid.RowDefinitions> <RowDefinition Height="140" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <!-- Back button and page title --> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}" /> <TextBlock x:Name="pageTitle" Grid.Column="1" Text="2nd page" Style="{StaticResource PageHeaderTextStyle}" /> </Grid> <FlipView x:Name="flipView" ItemsSource="{Binding Colors}" SelectedItem="{Binding CurrentItem}" Grid.Row="1" Grid.Column="1" SelectionChanged="SelectionChanged"> <FlipView.ItemTemplate> <DataTemplate> <Grid> <Rectangle Fill="{Binding Argb,Converter={StaticResource argbToBrushConverter}}" /> <TextBlock FontSize="24" Text="{Binding Name}" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid> </DataTemplate> </FlipView.ItemTemplate> </FlipView> </Grid> </common:LayoutAwarePage>
which leaves the FlipView binding to exactly the same data as the GridView ( the data sticking around because it’s defined as a resource in App.xaml hence making it effectively global ) and the FlipView’s SelectedItem is also bound to the CurrentItem but only in a one-way manner which means that data should flow from the data object to the UI control but nothing should flow in the other direction.
A bit of code to complete that class;
public sealed partial class FlipViewPage : App27.Common.LayoutAwarePage { public FlipViewPage() { this.InitializeComponent(); } private void SelectionChanged(object sender, SelectionChangedEventArgs e) { DataItem addedItem = null; DataItem removedItem = null; if ((e.AddedItems != null) && (e.AddedItems.Count > 0)) { addedItem = (DataItem)e.AddedItems[0]; } if ((e.RemovedItems != null) && (e.RemovedItems.Count > 0)) { removedItem = (DataItem)e.RemovedItems[0]; } Debug.WriteLine("FlipView SelectionChanged from {0} to {1}", removedItem != null ? removedItem.Name : "null", addedItem != null ? addedItem.Name : "null"); } }
And I modified the first page such that when someone clicks on a particular item, it causes a navigation to the 2nd page. When I run this code, select the colour ‘blue’ in the GridView and then navigate across to the FlipView page I see;
Colours property getter
CurrentItem property getter returning nullCurrentItem property setter changing value to blue
Colours property getter
FlipView SelectionChanged from null to red
FlipView SelectionChanged from red to null
FlipView SelectionChanged from null to red
and I found the SelectionChanged event firing 3 times a little odd especially to go from null to red to null to red again. It feels like it’s doing too much work.
I also found it odd that the FlipView doesn’t seem to be asking for the CurrentItem from my Data object which I expected that it would do once it got hold of its DataContext and realised that the SelectedItem was data-bound to the CurrentItem. But that seems to not happen and, ultimately, the FlipView remains on item 0 from the data set;
which is the wrong item in my case because my CurrentItem in the data is set to the blue item but the control isn’t asking for it.
Replacing the FlipView on the Second Page
I wondered what would happen if I replaced the FlipView on my second page with a different control such as a second GridView or a ListView. If I put a GridView onto my second page by copying the GridView from my first page but changing it just slightly as below on the 2nd page;
<GridView DataContext="{StaticResource data}" ItemsSource="{Binding Colors}" SelectionMode="Single" SelectedItem="{Binding CurrentItem}" Grid.Column="1" Grid.Row="1" SelectionChanged="SelectionChanged">
so I’m changing it such that it does one way binding on its SelectedItem and I’m changing it to fire a SelectionChanged event which drops into the same handler as I had for the FlipView which does that bit of diagnostic tracing. If I make a selection (say ‘green’) on the main page and then navigate to the second page what I see is;
and what I see in my tracing is;
Colours property getter
CurrentItem property getter returning nullCurrentItem property setter changing value to green
Colours property getter
CurrentItem property getter returning green
FlipView SelectionChanged from null to green
and this makes perfect sense to me with the last 3 diagnostic lines relating to the navigation to the 2nd page, the access of the Colours property to get the items source for the GridView and then the access of the CurrentItem value to get the value for the SelectedItem and then the SelectionChanged event firing.
Note that the word ‘FlipView’ appears in that tracing because I borrowed the same SelectionChanged handler and didn’t change the diagnostic wording.
That works for me. What about if I use a ListView on my second page rather than a FlipView? Replacing that GridView with a ListView;
<ListView ItemsSource="{Binding Colors}" SelectionMode="Single" SelectedItem="{Binding CurrentItem}" Grid.Column="1" Grid.Row="1" SelectionChanged="SelectionChanged"> <ListView.ItemTemplate> <DataTemplate> <Grid Width="250" Height="250"> <Rectangle Fill="{Binding Argb,Converter={StaticResource argbToBrushConverter}}" /> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView>
What I see as I select an item (say ‘green’) on my first page and then navigate across to the second page what I see is;
and so once again the control on the second page has correctly picked up the value of SelectedItem at the point where the control appears in the UI and the tracing I get is;
Colours property getter
CurrentItem property getter returning nullCurrentItem property setter changing value to green
Colours property getter
CurrentItem property getter returning green
FlipView SelectionChanged from null to green
and so, again, it appears to be ‘correct’ in that it lines up with my expectations ( which aren’t always correct ).
ListView and GridView Agree – FlipView Is Different
Ok, so it feels like the FlipView is out of sync with the ListView and the GridView in that those controls seem be open to the idea that their SelectedItem can be set before the control appears on the screen (including the way that I have done it here which is to declaratively set up the DataContext and then bind the ItemsSource and the SelectedItem – specifically with a one way binding on that SelectedItem).
FlipView and Two-Way Binding
In my example, I had no reason to use two-way binding on the controls on the 2nd page such that they bind their SelectedItem to the value of CurrentItem from my data model in a two-way manner.
Quick recap: one way binding means “from data to the control” whereas two-way binding adds “also from the control back to the data” and so that would mean that when you changed the SelectedItem using the control, the new value would be written into the value of the CurrentItem property.
But I wondered what would happen if I did use two-way binding so I tried it out.
<FlipView x:Name="flipView" ItemsSource="{Binding Colors}" SelectedItem="{Binding CurrentItem,Mode=TwoWay}" Grid.Row="1" Grid.Column="1" SelectionChanged="SelectionChanged">
Now, when I select an item on my main page (say ‘green’) and then move to my second page I see;
which is interesting because it’s correct but if I take a look at the tracing output then I see;
Colours property getter
CurrentItem property getter returning nullCurrentItem property setter changing value to green
Colours property getter
FlipView SelectionChanged from null to red
FlipView SelectionChanged from red to null
FlipView SelectionChanged from null to red
CurrentItem property getter returning green
FlipView SelectionChanged from red to green
and yet clearly there’s more ‘work’ going on here than I’d like in that the underlying data property ( CurrentItem ) doesn’t appear to have any problems but the SelectionChanged event is doing a rather odd thing of going null->red->null->red->green which is clearly a lot more work than needed.
The question here would be whether the red value actually ever appears on the screen? With a simple example like this, it’s hard to tell so I changed my data to add an Image property;
class DataItem { public string Name { get; set; } public UInt32 Argb { get; set; } public string Image { get; set; } }
and added a few more dummy records to the collection I’m using;
_colors = new DataItem[] { new DataItem() { Name = "red", Argb = 0xFFFF0000, Image="http://somepicture.com/img1.jpg" }, new DataItem() { Name = "green", Argb = 0xFF00FF00, Image="http://somepicture.com/img2.jpg" }, new DataItem() { Name = "green", Argb = 0xFF00FF00, Image="http://somepicture.com/img3.jpg" }, new DataItem() { Name = "green", Argb = 0xFF00FF00, Image="http://somepicture.com/img4.jpg" }, new DataItem() { Name = "green", Argb = 0xFF00FF00, Image="http://somepicture.com/img5.jpg" }, new DataItem() { Name = "green", Argb = 0xFF00FF00, Image="http://somepicture.com/img6.jpg" }, new DataItem() { Name = "blue", Argb = 0xFF0000FF, Image="http://somepicture.com/img7.jpg" } };
Then I notice from Fiddler that if I choose item index 0 on my main page such that it becomes the SelectedItem and then transition to the FlipView page;
then Fiddler shows that the control tried to get hold of images 1,2,3 for the 0,1,2 items;
whereas if I choose item index 6 on my main page such that it becomes the SelectedItem and then transition to the FlipView page;
then Fiddler shows that the control tried to get hold of images 1,2,3 for the 0,1,2 items and then also images 6,7 for the 5,6 items;
which seems to suggest that the control is first setting itself up in order to display items 0,1,2 and then realising that it has to display item 7 and so doing more work than it needs to which may well lead to that initial visible flicker as it displays these items.
I did try applying the workaround that Joe Stegman (of the XAML team) suggested in that forum post which involves waiting for the first layout updated event on the FlipView and then playing around a little with its SelectedItem but that didn’t seem to solve the problem for me.
Binding the SelectedIndex
At this point I wondered whether I might have more success avoiding the SelectedItem property and making use of the SelectedIndex property and so I gave that a whirl and added this property to my Data class;
public int SelectedIndex { get { Debug.WriteLine("SelectedIndex property getter returning {0}", this._selectedIndex); return (this._selectedIndex); } set { base.SetProperty(ref this._selectedIndex, value); Debug.WriteLine("SelectedIndex property setter changing value to {0}", this._selectedIndex); } }
As per binding the SelectedItem, I found that unless I made a TwoWay binding on my FlipView to the data property on my viewmodel then the FlipView seemed to display the wrong item.
If I made a TwoWay binding then the FlipView did seem to display the right item but I find that, again, more work is going on than I would like. Here’s the “diagnostics” from my Debug.Writeline calls;
Colours property getter
SelectedIndex property getter returning 0SelectedIndex property setter changing value to -1
SelectedIndex property setter changing value to 0
SelectedIndex property setter changing value to 6
Colours property getter
FlipView SelectionChanged from null to red
FlipView SelectionChanged from red to null
FlipView SelectionChanged from null to red
SelectedIndex property getter returning 6
FlipView SelectionChanged from red to blue
and so, once again, it feels that the FlipView is displaying item 0 and then displaying the item I have selected on screen (6 in this case).
Stuck
At that point, I get a bit stuck. Naturally, there are things that you can do to avoid this problem. You could do some kind of hack on your data collection to temporarily hack the first item so that it doesn’t load properly or perhaps returns NULL or something and there are probably lots of other schemes to come up with but I won’t write those up here.
Instead, I’ll go and query the status of the bug filed at Connect and will update this post if I find out more.