Home > front end >  Wait for user to choose from the backend
Wait for user to choose from the backend

Time:03-05

I'm coding the backend portion of a software and at some point I need my user to choose some things, it can be a convoluted process, the user can even cancel the selection at any point.

From the back end I'd like to do something like:

private async void StartAction()
{
    //some code
    var SelectedItem = await UI.RequestUserToChooseItem();
    // some final code using the selected item
}

Here I don't know how to handle cancellation, but I can send null and assume that if the SelectedItem is null it was canceled.

But what about the UI portion of it? How do I handle it to return the call when the thing is selected by the user?

I need to perform some steps here: (this is pseudocode, I don't even know where to start)

public List<Item> RequestUserToChooseItem()
{
    PrepareItemsInList();
    ShowSelectionPanel();
    WaitForUserToChose(); //???????
    return SelectedItemsFromPanel;

}

And then we have the cancel button:

private void CancelButtonClicked(object sender, EventArgs e)
{
    CancelWaitedSelectionProcessAndReturnNull(); //????
}

CodePudding user response:

You can use a TaskCompletionSource to signal the choices. Something like

private TaskCompletionSource<MyOptions> tcs;
public Task<MyOptions> ShowPanelAndWaitForSelection(){
    // show panel and do other initialization
    tcs = new TaskCompletionSource<MyOptions>();
    return  tcs.Task;
}
public void OnOptionSelection(MyOptions value) => tcs.SetResult(value);
public void OnCanceled() => tcs.SetCanceled();

When if the task is canceled, any awaiter will get a OperationCanceledException, so your code would normally look something like:

try{
    ...
    var selectedOption = await ShowPanelAndWaitForSelection();
    ...
}
catch(OperationCanceledException){
    // Handle cancellation
}
catch(Exception e){
   // Handle actual errors
}

This assumes your UI is non-modal, like showing and hiding panels in the same form. If you use modal dialogs for each step you do not need any async code.

This style essentially uses the compiler to generate a state machine, instead of writing such a state machine by hand. I think this can be a useful style to handle specific cases, since you can make a decision tree using regular constructs like if/while etc. But it may not always be a net positive, and it may trip up developers that do not expect it.

CodePudding user response:

Here is an asynchronous method that waits asynchronously for the first click on one or more buttons, and returns the clicked button:

public static Task<Button> OnClickAsync(params Button[] buttons)
{
    var tcs = new TaskCompletionSource<Button>();
    foreach (var button in buttons) button.Click  = OnClick;
    return tcs.Task;

    void OnClick(object sender, RoutedEventArgs e)
    {
        foreach (var button in buttons) button.Click -= OnClick;
        tcs.SetResult((Button)sender);
    }
}

It could be used like this:

public async Task<List<Item>> RequestUserToChooseItemAsync()
{
    PrepareItemsInList();
    ShowSelectionPanel();
    var selectedButton = await OnClickAsync(btnOK, btnCancel);
    if (selectedButton == btnCancel) return null;
    return SelectedItemsFromPanel;
}

This method should be called exclusively on the UI thread, not on background threads, because it interacts with UI elements.

  • Related