Jaa


ComboBox on a Flyout attached to an AppBarButton loses mouse input on 1607

Several developers have asked why a ComboBox on a Flyout attached to an AppBarButton ignores mouse clicks after upgrading their UWP apps to target build 14393 (Windows 10, version 1607 – also known as the Windows 10 Anniversary Update). The same Xaml worked fine unchanged when the app targeted build 10586 (Windows 10, version 1511).

<tl;dr>

Apps can reenable focus here by setting the AppBarButton's AllowFocusOnInteraction property to true.

Why is this happening?

The new behavior is a side effect of the new focus system which allows users to interact with controls such as AppBarButtons without stealing the focus from the control they're trying to modify. This has been a huge request from devs whose apps have scenarios such as text editors: the app can have formatting buttons (e.g. bold, italic, colors) which change the formatting without affecting the text's focus or selection.

The new behavior enhances the common AppBarButton use case so AppBarButtons default to AllowFocusOnInteraction to false if the app opts in to new behavior by targeting build 14393.

In the case where the AppBarButton brings up a ComboBox, the ComboBox depends on being able to get the focus. For this case the app needs to override the default and explicitly set AllowFocusOnInteraction to true.

How do I let the ComboBox get focus?

If you just fired up your app in Visual Studio and added AllowFocusOnInteraction to the AppBarButton Xaml you probably discovered that the editor rejected it. AllowFocusOnInteraction is only available in build 14393 and later. If the app's minimum version is lower than that then it can't be used in version flexible Xaml.

Set in code behind with adaptive code

The standard pattern to set properties that may or may not exist is to check if it's present with the Windows.Foundation.Metadata.ApiInformation class:

 private void AppBarButton_Loaded(object sender, RoutedEventArgs e)
{
    bool allowFocusOnInteractionAvailable = 
        Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent(
            "Windows.UI.Xaml.FrameworkElement", 
            "AllowFocusOnInteraction");

    if (allowFocusOnInteractionAvailable)
    {
        var s = sender as FrameworkElement;
        if (s != null)
        {
            s.AllowFocusOnInteraction = true;
        }
    }
}

Set in Xaml via an Attached Property

If we don't want to set this directly from code-behind we can bring the adaptive code back to Xaml by exposing it as an attached property. This property can be set on any AppBarButton (&c) we want to set AllowFocusOnInteraction on regardless of version. If AllowFocusOnInteraction is available then the property will set it. If not, then it's a no-op.

 public class CompatExtensions
{
    public static bool GetAllowFocusOnInteraction(DependencyObject obj)
    {
        return (bool)obj.GetValue(AllowFocusOnInteractionProperty);
    }
    public static void SetAllowFocusOnInteraction(DependencyObject obj, bool value)
    {
        obj.SetValue(AllowFocusOnInteractionProperty, value);
    }
    // Using a DependencyProperty as the backing store for AllowFocusOnInteraction.  
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty AllowFocusOnInteractionProperty =
        DependencyProperty.RegisterAttached("AllowFocusOnInteraction", 
                                            typeof(bool),
                                            typeof(CompatExtensions),
                                            new PropertyMetadata(0, AllowFocusOnInteractionChanged));

    private static bool allowFocusOnInteractionAvailable = 
        Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent(
            "Windows.UI.Xaml.FrameworkElement", 
            "AllowFocusOnInteraction");
    private static void AllowFocusOnInteractionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (allowFocusOnInteractionAvailable)
        {
            var element = d as FrameworkElement;
            if (element != null)
            {
                element.AllowFocusOnInteraction = (bool)e.NewValue;
            }
        }
    }
}

And then set the property in Xaml without worrying about the app version:

 <AppBarButton local:CompatExtensions.AllowFocusOnInteraction="True" Icon="Setting">
    <AppBarButton.Flyout>
        <Flyout>
            <StackPanel Orientation="Vertical" >
                <ComboBox>
                    <ComboBoxItem Content="Red" IsSelected="True" />
                    <ComboBoxItem Content="Green" />
                    <ComboBoxItem Content="Blue"/>
                </ComboBox>
            </StackPanel>
        </Flyout>
    </AppBarButton.Flyout>
</AppBarButton>