Visually Located

XAML and GIS

Creating a behavior or action to close a Flyout from a control within the Flyout

I recently started work on a new project and I’m trying hard to use “no code-behind”. This has been challenging for me as I tend to always put very view specific logic within the view itself. Things like navigation, starting storyboards or showing popups/flyouts. One thing I was trying to do recently was close a Flyout from a button within the flyout. My first approach was to try an EventTriggerBehavior for the Click event of the button along with the CallMethodAction. I tried two ways to call the Hide method on the flyout.

   1: <Button x:Name="AddButton" Content="Add Item" >
   2:     <Button.Flyout>
   3:         <Flyout x:Name="AddItemFlyout"
   4:                 Placement="Full">
   5:             <StackPanel>
   6:                 <TextBox x:Name="PlaceName" Header="Name"/>
   7:                 <Grid>
   8:                     <Grid.ColumnDefinitions>
   9:                         <ColumnDefinition/>
  10:                         <ColumnDefinition/>
  11:                     </Grid.ColumnDefinitions>
  12:                     <Button Content="Cancel" Margin="0,0,9.5,0" >
  13:                         <interactivity:Interaction.Behaviors>
  14:                             <core:EventTriggerBehavior EventName="Click">
  15:                                 <core:CallMethodAction TargetObject="{Binding ElementName=AddItemFlyout}"
  16:                                                        MethodName="Hide" />
  17:                             </core:EventTriggerBehavior>
  18:                         </interactivity:Interaction.Behaviors>
  19:                     </Button>
  20:                     <Button Content="Create" Grid.Column="1" Margin="9.5,0,0,0" 
  21:                             Command="{Binding CreateItemCommand}" 
  22:                             CommandParameter="{Binding Text, ElementName=PlaceName}">
  23:                         <interactivity:Interaction.Behaviors>
  24:                             <core:EventTriggerBehavior  EventName="Click">
  25:                                 <core:CallMethodAction TargetObject="{Binding Flyout, ElementName=AddButton}"
  26:                                                        MethodName="Hide" />
  27:                             </core:EventTriggerBehavior>
  28:                         </interactivity:Interaction.Behaviors>
  29:                     </Button>
  30:                 </Grid>
  31:             </StackPanel>
  32:         </Flyout>
  33:     </Button.Flyout>
  34: </Button>

Notice in the cancel button I tried hooking into the flyout by name and the create button I tried accessing the Flyout property of the button. Neither one of these would close the flyout. I searched online and found that this is not a new problem. Someone asked a question on StackOverflow a year ago.

My next step was to create a behavior and define a Flyout dependency property on it. When the button was clicked it would hide the flyout. This did not work because the Flyout property was always null. I tried setting it two different ways just as I had before.

With these not working I decided to walk the tree until I hit the Flyout and close it.

var flyout = AssociatedObject.GetVisualParent<Flyout>();
if(flyout != null)
{
    flyout.Hide();
}

NOTE: This uses an extension method for walking up the visual tree using the VisualTreeHelper.

This didn’t work because the Flyout is not an visual control. A Flyout is rendered with the FlyoutPresenter control. That’s all fine and dandy, but the FlyoutPresenter does not offer a way to close itself. So what to do? I went to XamlSpy to have a look at what is rendering the Flyout.

xamlspy

XamlSpy shows us that a popup is rendering the FlyoutPresenter. Perfect, we’ll walk the tree until we get to the popup, and then set IsOpen to false;

var popup = AssociatedObject.GetVisualParent<Popup>();
if(popup != null)
{
    popup.IsOpen = false;
}

Sweet! Let’s run this and watch the Flyout close! Click button… Wait… why isn’t it closing… Popup is null? Huh? It turns out that the VisualTreeHelper reports that the FlyoutPresenter does not have a parent. The VisualTreeHelper may report there isn’t a parent, but luckily the Parent property on the FlyoutPresenter does give us the Popup!

var flyout = AssociatedObject.GetVisualParent<FlyoutPresenter>();
if (flyout != null)
{
    var popup = flyout.Parent as Popup;
    if (popup != null)
    {
        popup.IsOpen = false;
    }
}

This allows the Flyout to close and even fires off the Closed event.

Here is some code to make this into a behavior that only works on buttons.

public class CloseFlyoutBehavior : Behavior<Button>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Click += AssociatedObjectOnClick;
    }
    private void AssociatedObjectOnClick(object sender, RoutedEventArgs routedEventArgs)
    {
        var flyout = AssociatedObject.GetVisualParent<FlyoutPresenter>();
        if (flyout != null)
        {
            var popup = flyout.Parent as Popup;
            if (popup != null)
            {
                popup.IsOpen = false;
            }
        }
    }
}

Note that this uses the Behavior base class I previously wrote about.

And if you’d like to make this into an action that can work for anything.

public class CloseFlyoutAction : IAction
{
    public object Execute(object sender, object parameter)
    {
        var element = sender as FrameworkElement;
        if (element == null) return null;
 
        var flyout = element.GetVisualParent<FlyoutPresenter>();
        if (flyout != null)
        {
            var popup = flyout.Parent as Popup;
            if (popup != null)
            {
                popup.IsOpen = false;
            }
        }
        return null;
    }
}