Advanced Flyouts for UWP (Windows 10)

2015-12-03 12:28:40来源:作者:mareinsula人点击

Suppose you are adding abutton for searching ina ‘command bar’ and you want to create a flyout that contains a textbox and a gridview like the following:

The usual step is to fill the property Flyout of the Button search with a Flyout, but this makes two issues in this case:

If you want to show a child flyout with a context menu as shown, instead of appearing, both parent and child disappears. In Mobile when you have the virtual keyboard and a flyout, after losing the focus of the virtual keyboard, the whole flyout disappears.

In order to solve both issues, we need to understand what is really a Flyout and see how to implement our solution.

Analysis

To begin, let’s use the standard Flyout:

<Button.Flyout> <Flyout > <ContentPresenter ContentTemplate="{StaticResource SearchTemplate}"/> </Flyout></Button.Flyout>

Where SearchTemplate has the following structure:

<DataTemplate x:Key="SearchTemplate"> <Grid Height="320" Width="320" > <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="1*"/> </Grid.RowDefinitions> <TextBox ></TextBox> <GridView Grid.Row="1"> </GridView> </Grid> </DataTemplate>

Now let’s create a method to analyze what happens when an item of the gridview is right tapped:

private void Child_RightTapped(object sender, RightTappedRoutedEventArgs e){ var current = (FrameworkElement)sender; var parent = (FrameworkElement)VisualTreeHelper.GetParent(current); while (parent != null) { current = parent; parent = (FrameworkElement)VisualTreeHelper.GetParent(current); }}

When this event is fired, we get as result that current is a Canvas, because a Flyout has no self representation, it is a Popup that has as child a FlyoutPresenter, to clarify:

var popupContainer = current as Canvas;var popup = popupContainer.Children.First() as Popup;var flyoutpresenter = popup.Child as FlyoutPresenter;

If we call:

var openpopups = VisualTreeHelper.GetOpenPopups(Window.Current);

It will confirm that we have one Popup which is the same of the Flyout. That means that we will have to create a new Flyout class that creates a Popup, but before that let’s analyze if we want to show a child Flyout inside the parent Flyout.

Child Flyout

Suppose we have the following itemtemplate for the items of the GridView inside the Parent Flyout:

<StackPanel Orientation="Vertical" Tapped="Child_Tapped" Holding="Child_Holding" RightTapped="Child_RightTapped" > <FlyoutBase.AttachedFlyout> <Flyout > <Border Padding="4"> ... </Border> </Flyout> </FlyoutBase.AttachedFlyout> <TextBlock .../></StackPanel>

As you see, you can add Flyouts for any Element, you will have to manage both events, holding and righttapped, to make appear its flyout:

var uiSender = sender as UIElement;var flyout = (FlyoutBase)uiSender.GetValue(FlyoutBase.AttachedFlyoutProperty);flyout.ShowAt(uiSender as FrameworkElement);

As I explain at the beginning, both parent and child Flyouts will close.

We need to create our own Flyout, that means creating a Popup with a content, manage how it appears, the datacontext and the virtual keyboard.

Custom Flyout

Due to you cannot inherit Flyout, we will create one from scratch as DependencyObject. We will call it TemplatedFlyout.

Content Template

The first property it has is the Template that the Flyout will show:

#region Templatepublic DataTemplate Template{ get { return (DataTemplate)GetValue(TemplateProperty); } set { SetValue(TemplateProperty, value); }}public static readonly DependencyProperty TemplateProperty = DependencyProperty.Register(nameof(Template), typeof(DataTemplate), typeof(TemplatedFlyout), new PropertyMetadata(null));#endregion Initialization

The control that has this Templated Flyout, will call initialization when this is attached:

private Popup popup;private FrameworkElement senderElement;private FrameworkElement frameworkContent;public void Initialize(UIElement sender){ senderElement = sender as FrameworkElement; senderElement.DataContextChanged += (s,e)=> frameworkContent.DataContext = senderElement.DataContext; popup = new Popup { IsLightDismissEnabled = true, ChildTransitions = new TransitionCollection() }; popup.ChildTransitions.Add(new PaneThemeTransition { Edge = EdgeTransitionLocation.Bottom }); frameworkContent = Template.LoadContent() as FrameworkElement; frameworkContent.DataContext = senderElement.DataContext; popup.Child = frameworkContent; FocusKeeper(); sender.Tapped += (s,e)=> Show();}

To understand this initialization:

Sender is the element that has the Templated Flyout as attached property The datacontext could be null at the beginning, so we need to manage its changes In this case the transition will make appear the flyout from the bottom. The content of the Popup will be an instance of the DataTemplate FocusKeeper is a workaround method to avoid closing the Popup when the virtual keyboard hides Keeping the Popup open

FocusKeeper is necessary because visually the keyboard is Visible when the Popup is Closed but the property of the InputPane IsOpen is false.To handle if the Popup must still open:

bool keyboardOpen = false;InputPane keyboard;private void FocusKeeper(){ keyboard = InputPane.GetForCurrentView(); popup.Closed += (s, e) => { if (keyboardOpen) Popup.IsOpen = true; }; if (keyboard != null) { keyboard.Showing += (s, e) => keyboardOpen = true; keyboard.Hiding += (s, e) => keyboardOpen = false; }}

This will only allow to hide the Popup if the keyboard is not really visible.

Adjusting the vertical position of the Popup

In order to have an offset from a position from the screen, we need know which element will be taken. To do that we will add another dependency property called Container.

In the case of Universal Characters Map, you can increase the size of the ‘command bar’ so we cannot hardcode the offset.

#region Containerpublic FrameworkElement Container{ get { return (FrameworkElement)GetValue(ContainerProperty); } set { SetValue(ContainerProperty, value); }}public static readonly DependencyProperty ContainerProperty =DependencyProperty.Register(nameof(Container), typeof(FrameworkElement),typeof(TemplatedFlyout), new PropertyMetadata(null));#endregion Show the Popup

we just need to create the method Show when the attached control is tapped:

public void Show(){ popup.RequestedTheme = ((Window.Current.Content as Frame).Content as Page).RequestedTheme; var h = frameworkContent.Height + Container?.ActualHeight; popup.SetValue(Canvas.TopProperty, Window.Current.Bounds.Height - h); popup.IsOpen = true; if(frameworkContent is Panel) { var panel = frameworkContent as Panel; if(panel.Children.Any()) { if(panel.Children.First() is Control) { (panel.Children.First() as Control).Focus(FocusState.Keyboard); } } }}

I have added a way to keep the theme in the Popup, the offset, even Focus a Textbox when the Popup appears.

Attached Property

To add the Templated Flyout in XAML we need an attached property like the following:

public class Extensions : DependencyObject #region Flyout Property public static void SetFlyout(UIElement element, TemplatedFlyout value) { element.SetValue(FlyoutProperty, value); } public static TemplatedFlyout GetFlyout(UIElement element) { return (TemplatedFlyout)element.GetValue(FlyoutProperty); } public static readonly DependencyProperty FlyoutProperty = DependencyProperty.RegisterAttached(nameof(FlyoutProperty), typeof(TemplatedFlyout), typeof(Extensions), new PropertyMetadata(null, TemplatedFlyoutChanged)); private static void TemplatedFlyoutChanged(DependencyObject d,DependencyPropertyChangedEventArgs e) { var uiSender = d as UIElement; var flyout = e.NewValue as TemplatedFlyout; flyout.Initialize(uiSender); } #endregion }

As we see, here is where we attach the element with the initialization of the Flyout.

Example

Suppose we have a grid in the bottom part of the Window, and we add the following button:

<Button x:Name="SearchButton" Background="Transparent" BorderThickness="0" Height="42"><SymbolIcon Symbol="Find"/> <a:Extensions.Flyout> <c:TemplatedFlyout Template="{StaticResource SearchTemplate}" Container="{Binding ElementName=CommandsGrid}"/> </a:Extensions.Flyout></Button>

Where ‘CommandsGrid’ is the container of the Button.

Conclusions

With all this analysis, now we know what’s the difference between a Popup and a Flyout, and a way to solve some behaviors of the Flyouts using a new Flyout that handles the virtual keyboard and allows child Flyouts.

As always, state of art of UWP at @juanpaexpedite

And the sourcecode of the Advanced Flyout: github

相关文章

    无相关信息

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台