Full screen Flyout on WinRT Phone 8.1

Flyouts in WinRT are a way for you to add additional information to FrameworkElements in a modal way.

They can be used for various reasons :

  • Showing feedback when an element gets focus, or any event takes place
  • To replace the ComboBox control, by adding a ListView to select an item
  • To replace the MessageDialog control so you can add a custom interface

Now there are these drawbacks on using the Flyout :

  • Appbar of the page does not automatically hides when showing a Flyout in Full Placement
  • Flyout does not close when an item is selected
  • Nested listviews are not scrollable inside the Flyout

What we will achieve :

FullFlyoutBehavior

How this is setup:

  • Give the root element the name “layoutRoot”
  • Create two behaviors (FlyoutFullPageBehavior and AppBarFlyoutBehavior)
  • Add a reference to the behaviors namespace in your page
  • Implement the XAML snippet below
                        <Button.Flyout>
                            <Flyout Placement="Full">
                                <i:Interaction.Behaviors>
                                    <behaviors:FlyoutFullPageBehavior Parent="{Binding ElementName=layoutRoot}" />
                                    <behaviors:AppBarFlyoutBehavior Parent="{Binding ElementName=layoutRoot}" />
                                </i:Interaction.Behaviors>
                                <!-- Flyout Content here -->
                            </Flyout>
                        </Button.Flyout>

AppBarFlyoutBehavior.cs

public class AppBarFlyoutBehavior : DependencyObject, IBehavior
    {
        private Flyout element = null;

        public DependencyObject AssociatedObject
        {
            get { return element; }
        }

        public FrameworkElement Parent
        {
            get { return GetValue(ParentProperty) as FrameworkElement; }
            set { SetValue(ParentProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EmptyDataTemplate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ParentProperty =
            DependencyProperty.Register("Parent", typeof(FrameworkElement), typeof(AppBarFlyoutBehavior), new PropertyMetadata(null, ParentPropertyChanged));

        private static void ParentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var behavior = (AppBarFlyoutBehavior)d;
            behavior.Parent = (FrameworkElement)e.NewValue;
        }

        public void Attach(DependencyObject associatedObject)
        {
            if (associatedObject as Flyout == null)
                throw new NotSupportedException("The associated object must be of type Flyout");
            element = associatedObject as Flyout;
            element.Opened += FlyoutOpened;
            element.Closed += FlyoutClosed;
        }

        private void FlyoutOpened(object sender, object e)
        {
            ShowAppBar(false);
        }

        private void FlyoutClosed(object sender, object e)
        {
            ShowAppBar(true);
        }

        private void ShowAppBar(bool show)
        {
            if (Parent != null)
            {
                var page = ControlHelper.GetParentPage(Parent);
                if (page != null && page.BottomAppBar != null)
                {
                    if (show)
                        page.BottomAppBar.Visibility = Visibility.Visible;
                    else
                        page.BottomAppBar.Visibility = Visibility.Collapsed;
                }
            }
        }

        public void Detach()
        {
            element.Opened -= FlyoutOpened;
            element.Closed -= FlyoutClosed;
        }
    }

FlyoutFullPageBehavior.cs

public class FlyoutFullPageBehavior : DependencyObject, IBehavior
    {
        private Flyout element = null;
        private double correction = 26;

        public DependencyObject AssociatedObject
        {
            get { return element; }
        }

        public FrameworkElement Parent
        {
            get { return GetValue(ParentProperty) as FrameworkElement; }
            set { SetValue(ParentProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EmptyDataTemplate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ParentProperty =
            DependencyProperty.Register("Parent", typeof(FrameworkElement), typeof(FlyoutFullPageBehavior), new PropertyMetadata(null, ParentPropertyChanged));

        private static void ParentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var behavior = (FlyoutFullPageBehavior)d;
            behavior.Parent = (FrameworkElement)e.NewValue;
        }

        public void Attach(DependencyObject associatedObject)
        {
            if (associatedObject as Flyout == null)
                throw new NotSupportedException("The associated object must be of type Flyout");
            element = associatedObject as Flyout;
            element.Opened += FlyoutOpened;
        }

        private void FlyoutOpened(object sender, object e)
        {
            if (element.Placement == FlyoutPlacementMode.Full)
            {
                Style flyoutPresenterStyle = Application.Current.Resources["FlyoutFullPageBehaviorStyle"] as Style;
                if (flyoutPresenterStyle == null)
                    flyoutPresenterStyle = new Style(typeof(FlyoutPresenter));
                var scrollSetter = new Setter(ScrollViewer.VerticalScrollModeProperty, ScrollMode.Disabled);
                if (flyoutPresenterStyle.Setters.Contains(scrollSetter))
                    flyoutPresenterStyle.Setters.Add(scrollSetter);
                element.FlyoutPresenterStyle = flyoutPresenterStyle;

                var content = element.Content as FrameworkElement;
                var page = ControlHelper.GetParentPage(Parent);
                if (page != null)
                {
                    page.SizeChanged += PageSizeChanged;

                    content.HorizontalAlignment = HorizontalAlignment.Stretch;
                    content.VerticalAlignment = VerticalAlignment.Stretch;
                    AppyHeightToFlyoutContent(element, page.ActualHeight, correction);
                }
            }
        }

        private void AppyHeightToFlyoutContent(Flyout flyout, double height, double correction)
        {
            var content = flyout.Content as FrameworkElement;
            if (content != null)
                content.Height = height - correction;
        }

        private void PageSizeChanged(object sender, SizeChangedEventArgs e)
        {
            var page = sender as Page;
            AppyHeightToFlyoutContent(element, page.ActualHeight, correction);
        }

        public void Detach()
        {
            element.Opened -= FlyoutOpened;

            var page = ControlHelper.GetParentPage(Parent);
            if (page != null)
            {
                page.SizeChanged -= PageSizeChanged;
            }
        }
    }

The flyout can be styled using the following XAML style

    <Style x:Key="FlyoutFullPageBehaviorStyle" TargetType="FlyoutPresenter">
        <Setter Property="Foreground" Value="Red" />
        <Setter Property="Background" Value="Green" />
    </Style>

Using this setup, you’ll be now able to show full page flyouts as if they where pop-ups. The appbar will become hidden when the flyout is visible.

Lets say we want to hide the flyout after an event has happened, a selection event in a listview, for example.

Then we could use the following behavior to close the flyout upon the click event of a listview.

Consider the following flyout for a button :

                    <Button>
                        <Button.Flyout>
                            <Flyout Placement="Full">
                                <i:Interaction.Behaviors>
                                    <behaviors:FlyoutFullPageBehavior Parent="{Binding ElementName=layoutRoot}" />
                                    <behaviors:AppBarFlyoutBehavior Parent="{Binding ElementName=layoutRoot}" />
                                </i:Interaction.Behaviors>
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto" />
                                        <RowDefinition Height="*" />
                                    </Grid.RowDefinitions>
                                    <TextBlock Margin="20,12,0,0"
                                               Text="Select an item below :" />
                                    <ListView Grid.Row="1"
                                              IsItemClickEnabled="True"
                                              ItemsSource="{Binding Items,
                                                                    Mode=OneWay}"
                                              SelectedItem="{Binding SelectedItem,
                                                                     Mode=TwoWay}">
                                        <i:Interaction.Behaviors>
                                            <behaviors:FlyoutListViewItemClickBehavior ListItemClickedCommand="{Binding ItemSelectedCommand}" />
                                        </i:Interaction.Behaviors>
                                        <ListView.ItemContainerStyle>
                                            <Style TargetType="ListViewItem">
                                                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                                            </Style>
                                        </ListView.ItemContainerStyle>
                                    </ListView>
                                </Grid>
                            </Flyout>
                        </Button.Flyout>
                    </Button>

BehaviorBase.cs

 public abstract class BehaviorBase<T> : DependencyObject, IBehavior where T : FrameworkElement
    {
        private T element;

        /// <summary
        /// !Important!
        /// Whenever you define Dependency Properties on Behaviors (or any DependencyObject for that matter), you need to set a private static function to act on PropertyMetadata changed.
        /// Since this is static, this can be executed multiple times from DependencyObjects that are not part of the main UI thread anymore
        /// So whenever you want to change something on the UI thread or push changes of bindingexpressions to the source, you need to surround it with an if-check:
        /// if(IsActive){
        /// //your UI and bindingexpression code here
        /// }
        /// </summary>
        private bool isActive = false;

        public bool IsActive
        {
            get
            {
                return isActive;
            }
            set
            {
                isActive = value;
            }
        }

        public T AssociatedElement { get { return element; } }

        public DependencyObject AssociatedObject { get { return element; } }

        public void Attach(DependencyObject associatedObject)
        {
            if (associatedObject as T == null)
                throw new NotSupportedException("The associated object must be of type " + typeof(T).Name);
            element = associatedObject as T;
            ElementAttached();
        }

        protected virtual void ElementAttached()
        {
            element.Loaded += ElementLoaded;
            isActive = true;
        }

        protected virtual void ElementLoaded(object sender, RoutedEventArgs e)
        {
            var page = ControlHelper.GetParentPage(element);
            if (page != null && page.NavigationCacheMode == NavigationCacheMode.Disabled)//Subcribe to the unloading event to handle state
                page.Unloaded += PageUnloaded;
        }

        private void PageUnloaded(object sender, RoutedEventArgs e)
        {
            this.Detach();
        }

        public void Detach()
        {
            ElementDetached();
            isActive = false;
        }

        protected virtual void ElementDetached()
        {
            var page = ControlHelper.GetParentPage(element);
            if (page != null && page.NavigationCacheMode == NavigationCacheMode.Disabled)//Subcribe to the unloading event to handle state
                page.Unloaded -= PageUnloaded;
            element.Loaded -= ElementLoaded;
        }
    }

ControlHelper.cs

public static class ControlHelper
    {
        public static Page GetParentPage(FrameworkElement elem)
        {
            var parent = elem.Parent;
            var parentPage = (parent as Page);
            while (parentPage == null && parent != null)
            {
                parent = (parent as FrameworkElement).Parent;
                parentPage = (parent as Page);
            }
            return parentPage;
        }

        public static T FindParent<T>(DependencyObject element) where T : FrameworkElement
        {
            FrameworkElement parent = VisualTreeHelper.GetParent(element) as FrameworkElement;

            while (parent != null)
            {
                T correctlyTyped = parent as T;
                if (correctlyTyped != null)
                    return correctlyTyped;
                else
                    return FindParent<T>(parent);
            }

            return null;
        }

        public static T FindChild<T>(FrameworkElement element) where T : FrameworkElement
        {
            int childCount = VisualTreeHelper.GetChildrenCount(element);
            for (int c = 0; c < childCount; c++)
            {
                FrameworkElement child = VisualTreeHelper.GetChild(element, c) as FrameworkElement;
                if (child != null)
                {
                    T correctlyTyped = child as T;
                    if (correctlyTyped != null)
                        return correctlyTyped;
                    else
                        return FindChild<T>(child);
                }
            }
            return null;
        }
    }

FlyoutListViewItemClickBehavior.cs

public class FlyoutListViewItemClickBehavior : BehaviorBase<ListViewBase>
    {
        public event EventHandler ItemClicked;

        protected override void ElementAttached()
        {
            base.ElementAttached();
            if (!AssociatedElement.IsItemClickEnabled)
                throw new NotSupportedException("Please set IsItemClickEnabled to true on the ListView!");
            AssociatedElement.ItemClick += listView_ItemClick;
        }

        protected override void ElementDetached()
        {
            base.ElementDetached();
            AssociatedElement.ItemClick -= listView_ItemClick;
        }

        private void listView_ItemClick(object sender, ItemClickEventArgs e)
        {
            var parentFlyout = ControlHelper.FindParent<FlyoutPresenter>(AssociatedElement);
            if (parentFlyout != null)
            {
                if (parentFlyout.Parent is Popup)
                {
                    Popup popUp = parentFlyout.Parent as Popup;
                    AssociatedElement.SelectedItem = e.ClickedItem;//Trigger the SelectedItem binding
                    popUp.IsOpen = false;//close the popup
                    if (ListItemClickedCommand != null)
                        ListItemClickedCommand.Execute(e.ClickedItem);
                    if (ItemClicked != null)
                        ItemClicked(e.ClickedItem, EventArgs.Empty);
                }
            }
        }

        public ICommand ListItemClickedCommand
        {
            get
            {
                return (ICommand)base.GetValue(FlyoutListViewItemClickBehavior.ListItemClickedCommandProperty);
            }
            set
            {
                base.SetValue(FlyoutListViewItemClickBehavior.ListItemClickedCommandProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ListItemClickedCommandProperty =
            DependencyProperty.Register("ListItemClickedCommand", typeof(ICommand), typeof(FlyoutListViewItemClickBehavior), new PropertyMetadata(null, OnListItemClickedCommandPropertyChanged));

        private static void OnListItemClickedCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var behavior = (FlyoutListViewItemClickBehavior)d;
            if (behavior.IsActive)
                behavior.ListItemClickedCommand = (ICommand)e.NewValue;
        }
    }
}

Source code (rename to .zip to unpack) :
FlyoutApp

Leave a comment