Home > .NET, C#, MVVM, Windows Phone, WinRT > How to open and close Flyouts in Universal apps using MVVM

How to open and close Flyouts in Universal apps using MVVM

15/01/2015

Some times ago we talked about how to open attached Flyouts and close them using custom behaviors and MVVM. This was necessary because the Flyout control doesn’t expose properties to control its visibility, so we need to use its ShowAt and Hide methods explicitly.

While this approach works correctly, we can obtain the same result in a more elegant way with two simple Attached Properties. The following is a generic version of the implementation shown in the article Using Windows 8.1 Flyout with MVVM and works with every control, not only buttons (remember, in fact, that we can attach a Flyout on any FrameworkElement).

Let’s start creating a FlyoutHelper class:

public static class FlyoutHelper
{
    public static readonly DependencyProperty IsVisibleProperty = 
        DependencyProperty.RegisterAttached(
        "IsOpen", typeof(bool), typeof(FlyoutHelper), 
        new PropertyMetadata(true, IsOpenChangedCallback));

    public static readonly DependencyProperty ParentProperty = 
        DependencyProperty.RegisterAttached(
        "Parent", typeof(FrameworkElement), typeof(FlyoutHelper), null);

    public static void SetIsOpen(DependencyObject element, bool value)
    {
        element.SetValue(IsVisibleProperty, value);
    }

    public static bool GetIsOpen(DependencyObject element)
    {
        return (bool)element.GetValue(IsVisibleProperty);
    }

    private static void IsOpenChangedCallback(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        var fb = d as FlyoutBase;
        if (fb == null)
            return;

        if ((bool)e.NewValue)
        {
            fb.Closed += flyout_Closed;
            fb.ShowAt(GetParent(d));
        }
        else
        {
            fb.Closed -= flyout_Closed;
            fb.Hide();
        }
    }

    private static void flyout_Closed(object sender, object e)
    {
        // When the flyout is closed, sets its IsOpen attached property to false.
        SetIsOpen(sender as DependencyObject, false);
    }

    public static void SetParent(DependencyObject element, FrameworkElement value)
    {
        element.SetValue(ParentProperty, value);
    }

    public static FrameworkElement GetParent(DependencyObject element)
    {
        return (FrameworkElement)element.GetValue(ParentProperty);
    }
}

This code adds an IsOpen Attached Property to flyout objects. When it changes (lines 22-39), we check whether we want to open or close the Flyout: in the first case, we call the ShowAt method (lines 32), that actually displays it. Since this method requires a FrameworkElement that represents the flyout’s placement target, we need also to specify the target control for the popup, so we have defined a Dependecy Property named Parent that holds the container of the Flyout. If, instead, we are about to dismiss the control, when IsOpen is set to false, we invoke its Hide method (line 37).

Note that we also need to register for the Closed event (line 31), that occurs when the flyout is hidden. The Flyout, in fact, implements the light-dismiss behavior: it can be closed simply touching anywhere on the screen outside of it, so it is necessary to handle the Closing event and set the IsOpen property to false, to reflect the correct state of the Flyout.

Now we can define a ViewModel like this:

public class MainViewModel : ViewModelBase
{
    private bool isOpen;
    public bool IsOpen
    {
        get { return isOpen; }
        set { this.Set(ref isOpen, value); }
    }

    public RelayCommand OpenCommand { get; set; }
    public RelayCommand CloseCommand { get; set; }

    public MainViewModel()
    {
        OpenCommand = new RelayCommand(() => IsOpen = true);
        CloseCommand = new RelayCommand(() => IsOpen = false);
    }
}

We have a command to open the Flyout (setting the IsOpen variable to true, line 15) and, similarly, a command to close it (line 16). In this way, in the View we can use the following XAML:

<Button Content="Open Flyout" x:Name="button" Command="{Binding OpenCommand}">
    <FlyoutBase.AttachedFlyout>
        <Flyout controls:FlyoutHelper.IsOpen="{Binding IsOpen, Mode=TwoWay}" 
                controls:FlyoutHelper.Parent="{Binding ElementName=button}">
            <StackPanel>
                <TextBlock Text="Awesome Flyout!" Margin="10" />
                <Button Content="Close" Command="{Binding CloseCommand}" 
                        HorizontalAlignment="Right" />
            </StackPanel>
        </Flyout>
    </FlyoutBase.AttachedFlyout>
</Button>

Moveover, using the Behavior SDK, we can use the same approach to attach a Flyout to any control:

<Page
    ...
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    ...>

    ...
    <Image x:Name="image" Source="/Assets/Logo.scale-100.png" 
           Height="100" Width="100">
        <FlyoutBase.AttachedFlyout>
            <Flyout controls:FlyoutHelper.IsOpen="{Binding IsOpen, Mode=TwoWay}" 
                    controls:FlyoutHelper.Parent="{Binding ElementName=image}">
                <StackPanel>
                    <TextBlock Text="Awesome Flyout!" Margin="10" />
                    <Button Content="Close" Command="{Binding CloseCommand}" 
                            HorizontalAlignment="Right" />
                </StackPanel>
            </Flyout>
        </FlyoutBase.AttachedFlyout>
        <i:Interaction.Behaviors>
            <core:EventTriggerBehavior EventName="Tapped">
                <core:InvokeCommandAction Command="{Binding OpenCommand}" />
            </core:EventTriggerBehavior>
        </i:Interaction.Behaviors>
    </Image>
    ...
</Page>

The EventTriggerBehavior declared at lines 21-23 invokes the OpenCommand action as soon as the Tapped event raises. As seen before, this sets the IsOpen property of the ViewModel to true, so the Flyout attached property displays the popup.

Categories: .NET, C#, MVVM, Windows Phone, WinRT
  1. 15/01/2015 at 11:17

    Reblogged this on .Net Diaries.

  2. Benedikt
    20/02/2015 at 01:18

    Hi!
    I love your FlyoutHelper, but since I use it I run into some wired problems.
    For example:
    * I can open my flyout the first time, but after clicking the save button and trying to reopen I get an “WinRT information: Placement target needs to be in the visual tree.” Error.

    I try to generate a small sample project. I would really appreciate your help.
    Thanks!

    • 20/02/2015 at 09:46

      Yes, send me a sample project with the problem I would be happy to help you!

  3. Benedikt
    06/04/2015 at 16:08

    Hi! I created a small sample project. You can find it on github: https://github.com/benschi11/FlyoutHelperTest

  1. 15/01/2015 at 14:21
  2. 19/01/2015 at 14:51
Comments are closed.