4

I am building my own Flyout control with list selection to use it on Windows Phone as well as Windows Desktop. Unlike ListPickerFlyout class, the Flyout class does not have an async method to show the flyout.

How can I call the ShowAt method async and return the selected value back after the flyout was closed?


Solution:

The async behavior can be achieved with TaskCompletionSource<T> (thanks to the AwaitableUI libary). What still bothers me is that I have to create the ListView manually within the constructor. It would be nice if I can use XAML instead and just assign a Template, but I did not find a working way.

public class ListPickerFlyout<T> : Flyout where T : class
{
    private event EventHandler<object> ItemPicked;

    public ListPickerFlyout(IEnumerable<T> items)
    {
        Placement = FlyoutPlacementMode.Full;
        Opening += OnOpening;
        Closed += OnClosed;

        var listView = new ListView();
        listView.SelectionMode = ListViewSelectionMode.None;
        listView.IsItemClickEnabled = true;
        listView.ItemClick += OnItemClick;
        listView.DisplayMemberPath = "Name";
        listView.SetBinding(ListView.ItemsSourceProperty, new Binding { Source = items });

        Content = listView;
    }

    public async Task<T> ShowAsync()
    {
        this.ShowAt(Window.Current.Content as Frame);

        var tcs = new TaskCompletionSource<T>();

        EventHandler<object> eventHandler = null;

        eventHandler = (s, e) =>
        {
            this.Closed -= eventHandler;
            this.ItemPicked -= eventHandler;

            tcs.SetResult(e as T);
        };

        this.Closed += eventHandler;
        this.ItemPicked += eventHandler;

        return await tcs.Task;
    }

    private void OnItemClick(object sender, ItemClickEventArgs e)
    {
        var selectedItem = e.ClickedItem as T;

        var eventHandler = ItemPicked;
        if (eventHandler != null)
            eventHandler(this, selectedItem);

        this.Hide();
    }

    private void OnOpening(object sender, object e)
    {
        var frame = Window.Current.Content as Frame;
        var page = frame.Content as Page;

        if (page != null)
            page.BottomAppBar.Visibility = Visibility.Collapsed;
    }

    private void OnClosed(object sender, object e)
    {
        var frame = Window.Current.Content as Frame;
        var page = frame.Content as Page;

        if (page != null)
            page.BottomAppBar.Visibility = Visibility.Visible;
    }
}
zirkelc
  • 1,451
  • 1
  • 23
  • 49
  • Have you tried `return new Task(() => ShowAt(Window.Current.Content as Frame));` Just going off the top of my head, the syntax may be slightly different since I'm not sure ShowAt returns after selection or not, but you can make a more complicated task there. – Ron Beyer Aug 10 '15 at 20:50
  • Yes, I have already tried this but it should return when the `Closed` event was raised. – zirkelc Aug 11 '15 at 10:02

2 Answers2

2

The WinRT XAML Toolkit has a concept of awaitable UI which makes it possible to await a lot of UI elements which usually don't apply the awaitable logic. I haven't tried it on a Flyout, but it might work.

You can find it here

Jon G Stødle
  • 3,844
  • 1
  • 16
  • 22
1

Unlike the back-end work job, the ShowAt is a UI interaction. The PickerFlyout class has the ShowAtAsync method, but it is not used to "wait" for the picker result.

In the XAML development, the async method is usually to avoid the UI blocking. And for the user interaction part, I recommended using the event-driven pattern.

In this case, I recommend retrieving the select item in "Closed" event handler.

Jeffrey Chen
  • 4,650
  • 1
  • 18
  • 22
  • I updated my question. The `ListPickerFlyout` has an async `ShowAt` method which returns the selected items. – zirkelc Aug 11 '15 at 10:29