WinRT Flyout Performance

I’ve been having problems with the files flyout in Surge for some time. The performance was pretty terrible: for normal torrents with at most hundreds of files, performance was almost acceptable on my desktop but terrible on my Surface RT. However, with torrents with thousands of files, even my desktop took more than thirty seconds to display the flyout.

This just isn’t acceptable, and frankly, shouldn’t happen. The list is virtualised and shouldn’t be giving me performance problems like this.

I finally got around to doing some testing, and it turned out that the inbuilt flyout class that Microsoft have provided was the problem. I’m not sure whether it was just breaking the virtualisation or if something else was going on, but I appeared to have found the problem.

To solve it, I wrote my own flyout class and performance is substantially better – up to 45 times faster with the larger lists – which is beyond what I expected.

I’ve included the XAML and C# code for the flyout itself inline below, and there’s a link to download a solution that demonstrates the use of the flyout. It’s a universal app, but the flyout is only for Windows 8, not for Windows Phone.

The XAML for the flyout references a button style for the app bar back button. This is included in the download, in Styles.xaml.

Using the control is very simple:
flyout.Show() shows the flyout. It’s async, so await it.
flyout.Hide() hides the flyout.
flyout.Reposition() moves the flyout. If you add a call to this to your page’s LayoutUpdated event then the flyout will stay open if the user snaps the app.

Download the Flyout Solution Demo here.

<UserControl
    x:Class="xxx.FlyOut"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="768"
    d:DesignWidth="500"
    Width="500"
    x:Name="flyoutRoot" >

    <Popup x:Name="uxFlyout"
           Closed="OnPopupClosed"
           IsLightDismissEnabled="False">
        <Border x:Name="uxBorder"
                BorderThickness="1,0,0,0"
                Background="#0193E8">
            <Border.Transitions>
                <TransitionCollection>
                    <EntranceThemeTransition FromHorizontalOffset="120" />
                </TransitionCollection>
            </Border.Transitions>

            <StackPanel Orientation="Vertical"
                        Background="#FFFFFF"
                        Margin="1,0,0,0">
                <StackPanel Width="{Binding ElementName=uxFlyout, Path=Width}"
                            Background="{Binding ElementName=uxBorder, Path=Background}"
                            Orientation="Horizontal"
                            HorizontalAlignment="Left"
                            VerticalAlignment="Top"
                            Height="80">
                    <Button Click="BackButton_Click"
                        Content="&#xE112;"
                        Style="{ThemeResource AppBarButtonStyle}" />
                    <TextBlock FontFamily="{StaticResource ContentControlThemeFontFamily}"
                               FontSize="28"
                               FontWeight="SemiLight"
                               Foreground="#FFFFFF"
                               HorizontalAlignment="Center"
                               LineHeight="28"
                               Margin="-26, 31, 0, 0"
                               Text="My Fly-out"
                               TextAlignment="Center"
                               VerticalAlignment="Top" />
                </StackPanel>

                <!-- Put your content here -->

            </StackPanel>
        </Border>
    </Popup>
</UserControl>
namespace xxx
{
    using System.Threading.Tasks;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;

    public sealed partial class FlyOut
    {
        /// <summary>
        /// Initialises a new instance of the <see cref="FlyOut"/> class.
        /// </summary>
        public FlyOut()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Display the fly-out and start the slide-in animation
        /// </summary>
        public async Task Show()
        {
            await Task.Delay(50); // This is a fairly hideous hack, but adding a 50ms delay seems to make WinRT animations MUCH more reliable.

            this.uxFlyout.IsOpen = true;

            this.Reposition();
        }

        /// <summary>
        /// Run when the fly-out is shown or when the size of the application has changed (snapping/resizing).
        /// </summary>
        public void Reposition()
        {
            if (uxFlyout.IsOpen == false)
            {
                return;
            }

            double screenWidth = Window.Current.Bounds.Width;

            // If the screen is smaller than 500, cap the width at screen width
            var chosenWidth = screenWidth > 500 ? 500 : screenWidth;

            this.uxFlyout.Width = chosenWidth;
            this.uxBorder.Width = chosenWidth;
            this.uxBorder.Height = Window.Current.Bounds.Height;

            Canvas.SetLeft(this.uxFlyout, screenWidth - chosenWidth);
        }

        /// <summary>
        /// Hide the fly-out
        /// </summary>
        public void Hide()
        {
            this.OnClosingEvent(this);
            this.uxFlyout.IsOpen = false;
        }

        /// <summary>
        /// Run when the fly-out is closing.
        /// </summary>
        /// <param name="sender">The argument is not used.</param>
        public void OnClosingEvent(object sender)
        {
            this.uxFlyout.IsOpen = false;
            Canvas.SetLeft(this.uxFlyout, Window.Current.Bounds.Width + 10);
        }

        /// <summary>
        /// Run when the pop-up is being closed - hides the fly-out.
        /// </summary>
        /// <param name="sender">The argument is not used.</param>
        /// <param name="e">The argument is not used.</param>
        private void OnPopupClosed(object sender, object e)
        {
            this.Hide();
        }

        /// <summary>
        /// Run when the back button is clicked - hides the fly-out.
        /// </summary>
        /// <param name="sender">The argument is not used.</param>
        /// <param name="e">The argument is not used.</param>
        private void BackButton_Click(object sender, RoutedEventArgs e)
        {
            this.Hide();
        }
    }
}

This code is based on code I used when Windows 8 was in pre-release. That code was in turn based on a flyout example I stumbled on at the time. I can’t find it now, but if anybody recognises it, please let me know so I can give credit where it’s due.