Skip to content

Latest commit

 

History

History
226 lines (175 loc) · 7.91 KB

Popup-AdditionalLayoutProperties-Spec.md

File metadata and controls

226 lines (175 loc) · 7.91 KB

Popup additional layout properties

Background

Popups and flyouts in XAML are given two modes of display:

  • They can either appear as part of the rest of XAML, in which case they're confined to the bounds of the XAML root,
  • Or they can appear in their own HWND, which allows them to escape the bounds of the XAML root. This is common for elements such as context menus.

CommandBarFlyout is one such element, but since it's defined in the WinUI 2 controls library rather than in the OS, it does not have access to the HWND used to allow it to escape the XAML root's bounds. As such, it has no way to determine which monitor it's being displayed in, which makes it unable to know whether it has enough visual space to open the popup for its secondary commands below its primary commands or whether it should open them above instead.

This new API adds three properties and an event to Popup which will allow apps to specify where it logically desires a popup to be displayed relative to another element, and then respond to where system XAML was able to actually place the popup. This will allow elements such as CommandBarFlyout to be able to rely on system XAML for the placement of their child popups in a way that will take monitor or app bounds into account without needing to do that accounting manually.

Visual Examples

Opening down

When CommandBarFlyout has enough space below its primary commands, we want it to open down.

Secondary commands flyout closed:

Shows the CommandBarFlyout not yet open with sufficient monitor space below it

Secondary commands flyout open:

Shows the CommandBarFlyout opened down

Opening up

When CommandBarFlyout does not have enough space below its primary commands, we want it to be able to open up.

Secondary commands flyout closed:

Shows the CommandBarFlyout not yet open with insufficient monitor space below it

Secondary commands flyout open:

Shows the CommandBarFlyout opened up

API Pages

Popup class

class Popup
{
    // Existing APIs
    // ...

    // New APIs
    FrameworkElement PlacementTarget { get; set; }
    PopupPlacementMode DesiredPlacement { get; set; }
    PopupPlacementMode ActualPlacement { get; }
    
    public event System.EventHandler<object> ActualPlacementChanged;
}

The example below shows how the Placement APIs are used by the CommandBarFlyout to position its CommandBarFlyoutCommandBar's secondary commands Popup, and how to respond to the event raised when XAML places the Popup.

<!-- Part of the CommandBarFlyoutCommandBar's default template -->
<Popup
    x:Name="OverflowPopup"
    PlacementTarget="{Binding ElementName=PrimaryItemsRoot}"
    DesiredPlacement="Bottom">
</Popup>
void OnApplyTemplate()
{
    m_overflowPopup = GetTemplateChild("OverflowPopup");
    m_overflowPopup.ActualPlacementChanged += OnOverflowPopupActualPlacementChanged;
}

void OnOverflowPopupActualPlacementChanged(object sender, object args)
{
    UpdateVisualState(useTransitions: false);
}

void UpdateVisualState(bool useTransitions)
{
    if (m_overflowPopup.ActualPlacement == PopupPlacementMode.Top)
    {
        VisualStateManager.GoToState(this, "ExpandedUp", useTransitions);
    }
    else
    {
        VisualStateManager.GoToState(this, "ExpandedDown", useTransitions);
    }
}

Popup.PlacementTarget property

Use this property to describe which element the Popup should be positioned relative to. Defaults to null.

If this is null, then DesiredPlacement is ignored, ActualPlacement is always Auto, and ActualPlacementChanged is never raised. If the Popup is in the visual tree, PlacementTarget will override what its position would otherwise be set to by layout. Setting PlacementTarget to an element under a different XAML root than Popup.XamlRoot is invalid and will throw an ArgumentException.

Ignored if DesiredPlacement is Auto.

Spec note: this property is analogous to the FlyoutBase.Target property, but Popup.Target looked confusing, so we added the Placement prefix.

Popup.DesiredPlacement property

Use this property to describe how you would ideally like the Popup positioned relative to PlacementTarget. Defaults to Auto.

If this is Auto, then PlacementTarget is ignored, ActualPlacement is always Auto and ActualPlacementChanged is never raised. If both DesiredPlacement and PlacementTarget are set and HorizontalOffset and/or VerticalOffset are also set, then the latter two properties will offset the Popup from where it would have been placed by DesiredPlacement and PlacementTarget alone.

Ignored if PlacementTarget is null.

Popup.ActualPlacement property

Use this read-only property to determine where the popup was positioned. Will always be Auto if either PlacementTarget and DesiredPlacement are not set.

Popup.ActualPlacementChanged event

Raised whenever XAML changes the value of ActualPlacement, which allows apps to respond to where a Popup was placed.

For example, use this to determine the visual state to go into, based on whether a Popup is appearing above or below PlacementTarget.

This event is raised before the screen is refreshed, meaning that any visual changes made in response to this event can be made before anything is drawn to the screen at the new position. Will never be raised if either PlacementTarget and DesiredPlacement are not set.

PopupPlacementMode enum

Spec note: This is designed to align with the existing FlyoutPlacementMode enum, with the exception of the absence of "Full". "Full" is absent since developers should use a Flyout if they want something full-screen.

enum PopupPlacementMode
{
    Auto,
    Top,
    Bottom,
    Left,
    Right,
    TopEdgeAlignedLeft,
    TopEdgeAlignedRight,
    BottomEdgeAlignedLeft,
    BottomEdgeAlignedRight,
    LeftEdgeAlignedTop,
    LeftEdgeAlignedBottom,
    RightEdgeAlignedTop,
    RightEdgeAlignedBottom
}

Spec note: The meaning of "Left" and "Right" are swapped if FlowDirection is RightToLeft.

API Details

namespace Windows.UI.Xaml.Controls.Primitives
{
    [webhosthidden]
    enum PopupPlacementMode
    {
        Auto,
        Top,
        Bottom,
        Left,
        Right,
        TopEdgeAlignedLeft,
        TopEdgeAlignedRight,
        BottomEdgeAlignedLeft,
        BottomEdgeAlignedRight,
        LeftEdgeAlignedTop,
        LeftEdgeAlignedBottom,
        RightEdgeAlignedTop,
        RightEdgeAlignedBottom
    };

    [webhosthidden]
    interface IPopup2
    {
        Windows.UI.Xaml.FrameworkElement PlacementTarget;
        Windows.UI.Xaml.Controls.Primitives.PopupPlacementMode DesiredPlacement;
        Windows.UI.Xaml.Controls.Primitives.PopupPlacementMode ActualPlacement { get; };
        
        event Windows.Foundation.EventHandler<Object> ActualPlacementChanged;
        
        static Windows.UI.Xaml.DependencyProperty PlacementTargetProperty{ get; };
        static Windows.UI.Xaml.DependencyProperty DesiredPlacementProperty{ get; };
        static Windows.UI.Xaml.DependencyProperty ActualPlacementProperty{ get; };
    };
}