How to await a button click

[This post is part of a series How to await a storyboard, and other things]

 

Sometimes we want to await until a button has been clicked. Once place where this is useful is, for instance, when displaying the message “Click button to continue”.
  

Await button1.WhenClicked()

 

Why do it this way? Well, if you did it normally by writing top-level click handler event, then the event would be "always on". You'd need to enforce the invariant that the button was invisible when you didn't need it. Or, if the button were shared for different tasks at different times, then you'd have needed a top-level state field to record which of those tasks it's currently in use for. Or maybe you'd switch around which delegate was assigned to its Completed handler. Those approaches are all valid, but yhis "Await" approach works out nicer in some cases.
  

Here’s some code to support the above syntax. It's for win8, using RoutedEventHandler, but is easy to adapt to other platforms:
  

<Extension> Function WhenClicked(btn As Button) As Task

    Dim tcs As New TaskCompletionSource(Of Object)

    Dim lambda As RoutedEventHandler = Sub(s, e)

                                           RemoveHandler btn.Click, lambda

                                           tcs.SetResult(Nothing)

                                       End Sub

    AddHandler btn.Click, lambda

    Return tcs.Task

End Function
  

 

Note on naming convention:  The .NET async guidelines say that async methods should end in the suffix "Async". I've violated that guideline here. When we made the guidelines, I don't think we thought through the cases where you use async for events. This alternative name is closer in spirit to Task.WhenAll and Task.WhenAny. Thanks to Quartermeister for the suggestion.