Home > C#, MVVM, WinRT > Using Windows 8.1 Flyout control with MVVM

Using Windows 8.1 Flyout control with MVVM

30/07/2013

The Flyout XAML Control that ships with Windows 8.1 lacks a Dependency Property, let’s say IsOpen, that allows to control it using the MVVM pattern. Actually, opening it is not a problem, because it is tipically associated to a Button control, and so it is automatically shown when we tap on it. The problem arises when we need to close the flyout, for example when we click a command button. The only way to programmatically close the popup is to call its Hide method.

If we are following the MVVM pattern, however, we should have a property that we can change to show and hide the Flyout: this property must be bounded to the View Model. So, we need to define a new Attached Property:

public static class FlyoutHelpers
{
    public static readonly DependencyProperty IsOpenProperty =
        DependencyProperty.RegisterAttached("IsOpen", typeof(bool),
        typeof(FlyoutHelpers), new PropertyMetadata(false, OnIsOpenPropertyChanged));

    public static readonly DependencyProperty ParentProperty =
        DependencyProperty.RegisterAttached("Parent", typeof(Button),
        typeof(FlyoutHelpers), new PropertyMetadata(null, OnParentPropertyChanged));

    public static void SetIsOpen(DependencyObject d, bool value)
    {
        d.SetValue(IsOpenProperty, value);
    }

    public static bool GetIsOpen(DependencyObject d)
    {
        return (bool)d.GetValue(IsOpenProperty);
    }

    public static void SetParent(DependencyObject d, Button value)
    {
        d.SetValue(ParentProperty, value);
    }

    public static Button GetParent(DependencyObject d)
    {
        return (Button)d.GetValue(ParentProperty);
    }

    private static void OnParentPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var flyout = d as Flyout;
        if (flyout != null)
        {
            flyout.Opening += (s, args) =>;
                {
                    flyout.SetValue(IsOpenProperty, true);
                };

            flyout.Closed += (s, args) =>;
                {
                    flyout.SetValue(IsOpenProperty, false);
                };
        }
    }

    private static void OnIsOpenPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var flyout = d as Flyout;
        var parent = (Button)d.GetValue(ParentProperty);

        if (flyout != null && parent != null)
        {
            var newValue = (bool)e.NewValue;

            if (newValue)
                flyout.ShowAt(parent);
            else
                flyout.Hide();
        }
    }
}

The core part of this class is the OnIsOpenPropertyChanged method at line 49-64. It is raised when the IsOpen property changes: if its new value is false, the Hide method of the Flyout is called. Otherwise, the Flyout is made visible using the ShowAt method. Since the latter requires a FrameworkElement that represents the flyout’s placement target, in the class we have defined also a Dependecy Property named Parent that holds the container of the Flyout.

Another important method is at line 31-47. It is raised when the Parent property changes. The Flyout control is automatically shown when the user taps the button, so we must handle its Opening event and update the IsOpen value. In the same way, as Flyout 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 too and set the IsOpen property to false, to reflect the correct state of the Flyout.

Note that this is a little hack: actually, in the OnParentPropertyChanged method we don’t do anything with the new value of the property, but we use it as a trigger to register for the Flyout events. This is OK as we always need to specify a parent control, that never changes after been set, so this method will be executed only once. A cleaner solution would be to have an IsEnabled property that registers for the events when set to true.

Now suppose we have the following View Model, that is based on MVVM Light Toolkit:

public class MainViewModel : ViewModelBase
{
    public RelayCommand SendCommand { get; set; }

    private bool isFlyoutOpen;
    public bool IsFlyoutOpen 
    {
        get { return isFlyoutOpen; }
        set { this.Set(() => IsFlyoutOpen, ref isFlyoutOpen, value); }
    }

    public MainViewModel()
    {
        SendCommand = new RelayCommand(() =>
        {
            // Doing processing...
            IsFlyoutOpen = false;
        });
    }
}

As we can imagine, the IsFlyoutOpen property determines whether the Flyout is visible. In the SendCommand action, we perform some operations and then we want to hide the popup, so we set the IsFlyoutOpen property to false.

Finally, in the XAML we need to add the new Dependency Properties and bind them to the View Model:

<Button Content="Show" x:Name="ShowButton">
    <Button.Flyout>
        <Flyout local:FlyoutHelpers.Parent="{Binding ElementName=ShowButton}" 
                local:FlyoutHelpers.IsOpen="{Binding IsFlyoutOpen, Mode=TwoWay}">
            <StackPanel Width="406">
                <TextBlock Text="Insert the value and tap the Send button:" 
                           FontSize="16" 
                           FontWeight="SemiLight" FontFamily="Segoe UI"
                           Margin="0,0,0,10"  />
                <TextBox x:Name="ValueTextBox" />
                <Button  Content="Send" HorizontalAlignment="Right"
                         FontSize="16" Margin="0,10,0,0" 
                         Command="{Binding SendCommand}">
                </Button>
            </StackPanel>
        </Flyout>
    </Button.Flyout>
</Button>

At line 3-4, we set the Parent and IsOpen properties. It is important to remember that the binding of IsOpen is two-way, because, as we have seen, this property can be modified not only in the View Model.

That’s all. With this approach, we can completely control the Flyout state from the View Model.

Categories: C#, MVVM, WinRT
Comments are closed.
%d bloggers like this: