ListView in progress to fly out
I am using grouped ListView
internally Flyout
and am getting weird UI issue with the group title when the popup opens. This happens for a split second, but is still noticeable by most users.
XAML (excerpt from full reproduction example http://ge.tt/1DWlXbq1/v/0?c ):
<Page.Resources>
<DataTemplate x:Key="GroupHeaderTemplate">
<ContentControl Content="{Binding Key}"
FontWeight="Bold"
FontSize="{ThemeResource TextStyleLargeFontSize}"
Foreground="{ThemeResource PhoneAccentBrush}"
Margin="0 20" />
</DataTemplate>
<CollectionViewSource x:Key="ItemsViewSource"
IsSourceGrouped="True"
Source="{Binding Items}" />
</Page.Resources>
<Page.BottomAppBar>
<CommandBar>
<AppBarButton Icon="Caption">
<AppBarButton.Flyout>
<Flyout>
<ListView ItemsSource="{Binding Source={StaticResource ItemsViewSource}}"
Margin="20 0">
<ListView.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ListView.GroupStyle>
</ListView>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
</CommandBar>
</Page.BottomAppBar>
I can't use the built-in ListPickerFlyout
one as it doesn't support grouping.
I tried to find an appropriate storyboard or default style transition for ListView
/ Flyout
but couldn't.
I would like to fix this animation or disable it altogether. Any help is appreciated.
source to share
One way to get rid of the weird animation error is to start the control animation first Flyout
and then show it after the animation ends ListView
.
To do this, you need to subscribe to the following events in the control Flyout
. Also, you need to specify the name of ListView
a and set it Opacity
to 0
start.
<Flyout Opened="Flyout_Opened" Closed="Flyout_Closed">
<ListView x:Name="MyListView" Opacity="0" ItemsSource="{Binding Source={StaticResource ItemsViewSource}}" Margin="20 0">
Then in the code behind you show ListView
after a short delay. I created a small animation Opacity
to ListView
, to make the whole transition much smoother. Every time it Flyout
closes, we reset ListView
back to invisible.
private async void Flyout_Opened(object sender, object e)
{
// a short delay to allow the Flyout in animation to take place
await Task.Delay(400);
// animate in the ListView
var animation = new DoubleAnimation
{
Duration = TimeSpan.FromMilliseconds(200),
To = 1
};
Storyboard.SetTarget(animation, this.MyListView);
Storyboard.SetTargetProperty(animation, "Opacity");
var storyboard = new Storyboard();
storyboard.Children.Add(animation);
storyboard.Begin();
}
private void Flyout_Closed(object sender, object e)
{
this.MyListView.Opacity = 0;
}
However, having provided a possible solution, I don't think using a control Flyout
to change the visual style is the right approach.
The control is Flyout
not designed to handle large amounts of data. It doesn't support virtualization (I think). For example, if you increase the number of items from 30 to 300, it will only take a few seconds to load once you click the button.
Update (including working sample)
I thought maybe I could create a control that handles the whole thing, because at the end of the day, you want to get the item you clicked in the list, and also close the popup.
Unfortunately, it is ListPickerFlyout
sealed, so I decided to create a control that inherits from Flyout
.
It's pretty straight forward. Basically the control exposes properties like ItemsSource
, SelectedItem
etc. Also it attaches to the event ItemClick
ListView
, so whenever an element is clicked it closes Flyout
and fills SelectedItem
.
public class ListViewFlyout : Flyout
{
private ListView _listView;
public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(ListViewFlyout), new PropertyMetadata(null));
public DataTemplate HeaderTemplate
{
get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
}
public static readonly DependencyProperty HeaderTemplateProperty =
DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(ListViewFlyout), new PropertyMetadata(null));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(ListViewFlyout), new PropertyMetadata(null));
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(ListViewFlyout), new PropertyMetadata(null));
public ListViewFlyout()
{
// initialization
this.Placement = FlyoutPlacementMode.Full;
_listView = new ListView
{
Opacity = 0,
IsItemClickEnabled = true
};
this.Opened += ListViewFlyout_Opened;
this.Closed += ListViewFlyout_Closed;
}
private async void ListViewFlyout_Opened(object sender, object e)
{
await Task.Delay(400);
if (!_listView.Items.Any())
{
// assign the listView as the Content of this 'custom control'
_listView.ItemsSource = this.ItemsSource;
_listView.ItemTemplate = this.ItemTemplate;
_listView.GroupStyle.Add(new GroupStyle { HeaderTemplate = this.HeaderTemplate });
this.Content = _listView;
// whenever an item is clicked, we close the Layout and assign the SelectedItem
_listView.ItemClick += ListView_ItemClick;
}
// animate in the list
var animation = new DoubleAnimation
{
Duration = TimeSpan.FromMilliseconds(200),
To = 1
};
Storyboard.SetTarget(animation, _listView);
Storyboard.SetTargetProperty(animation, "Opacity");
var storyboard = new Storyboard();
storyboard.Children.Add(animation);
storyboard.Begin();
}
private void ListViewFlyout_Closed(object sender, object e)
{
_listView.Opacity = 0;
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
this.SelectedItem = e.ClickedItem;
this.Hide();
// to be removed
await Task.Delay(1000);
var dialog = new MessageDialog(e.ClickedItem.ToString() + " was clicked 1 sec ago!");
await dialog.ShowAsync();
}
}
xaml becomes as simple as this.
<AppBarButton Icon="Caption">
<AppBarButton.Flyout>
<local:ListViewFlyout ItemsSource="{Binding Source={StaticResource ItemsViewSource}}" ItemTemplate="{StaticResource ListViewItemTemplate}" HeaderTemplate="{StaticResource GroupHeaderTemplate}" FlyoutPresenterStyle="{StaticResource FlyoutPresenterStyle}" />
</AppBarButton.Flyout>
</AppBarButton>
Note that in style FlyoutPresenterStyle
I created Title
for the popup.
I've also included a fully functional sample here .
source to share
I have the same problem and found a workaround. I find the performances are pretty bad even without a workaround. It takes about 1 second for a full download on my device (Lumia 920).
I believe it is in charge ItemsStackPanel
which is the default ItemsPanel
for ListView
. When I use another panel, the problem does not occur. However, the offset of the scrollviewer is not reset when I close and reopen the popup, so I have to do it manually.
So, I used VirtalizingStackPanel
to keep virtualization.
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
And when the ListView loads, we find the ListView ScrollViewer and scroll to the top.
private void ListView_Loaded(object sender, RoutedEventArgs e)
{
var listView = (ListView)sender;
var scrollviewer = listView.FindFirstChild<ScrollViewer>();
scrollviewer.ScrollToVerticalOffset(0);
}
listView.FindFirstChild<ScrollViewer>();
is just a helper, I have to find the child controls using VisualTreeHelper.GetChild
.
I find this solution slightly better from a performance point of view: By default, List Visibility is collapsed:
<ListView Visibility="Collapsed" />
I subscribe to Flyout events Opened
and Closed
:
<Flyout Placement="Full"
Opened="Flyout_Opened"
Closed="Flyout_Closed" />
Then when the popup opens, I change the visibility after 400ms.
private async void Flyout_Opened(object sender, object e)
{
await Task.Delay(400);
var listView = (ListView)((Flyout)sender).Content;
listView.Visibility = Windows.UI.Xaml.Visibility.Visible;
}
private void Flyout_Closed(object sender, object e)
{
var listView = (ListView)((Flyout)sender).Content;
listView.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
Also, by default the popup has ScrollViewer
, which will break virtualization. You need to remove it from the control template FlyoutPresenter
or disable it with ScrollViewer.VerticalScrollMode
.
source to share
As it turns out, the strange animation comes from ItemsStackPanel
. Therefore, if (and only if) virtualization is not required, you can specify StackPanel
as ItemsPanel
:
<Flyout>
<ListView ItemsSource="{Binding Source={StaticResource ItemsViewSource}}"
Margin="20 0">
<ListView.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ListView.GroupStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Flyout>
source to share