I’m trying to update a piece of code so that it simulates modal dialogs and Reactive Extensions felt like a proper tool to do so, but I can’t make it work.
Currently, the code looks something like this:
public bool ShowConfirmationDialogs(IEnumerable<ItemType> items)
{
bool canContinue = true;
foreach (var item in items)
{
Dialog dialog = new Dialog();
dialog.Prepare(item); // Prepares a "dialog" specific to each item
IObservable<bool> o = Service.ShowDialog(dialog, result =>
{
// do stuff with result that may impact next iteration, e.g.
canContinue = !result.Condition;
});
// tried using the following line to wait for observable to complete
// but it blocks the UI thread
o.FirstOrDefault();
if (!canContinue)
break;
}
if (!canContinue)
{
// do something that changes current object's state
}
return canContinue;
}
Up until now, the code from the lambda expression was used to do stuff when the “dialog” shown by ShowDialog was closed. The call to ShowDialog is non blocking and used to return void.
What happens behind the scenes in ShowDialog is that an object is added to an ObservableCollection so that it is shown on the screen.
I modified ShowDialog so that it will return an IObservable<bool> who calls OnCompleted of its subscribers when the dialog is closed. This works. I tested it with the following code:
o.Subscribe(b => Console.WriteLine(b), () => Console.WriteLine("Completed"));
And I can see the string "Completed" when the dialog is closed. My problem is that the line above is non-blocking, so I can potentially display several dialogs, which I don’t want to do.
I tried the following:
o.FirstOrDefault();
assuming that the program would wait there until the observable sent something or completed. The program blocks all right, but it also freezes the UI, which means I never see my dialog, which I can never close, so the observable never completes.
I tried several variations using ObserveOn and SubscribeOn to try to leave the UI thread do its work, with no luck. Any ideas would be greatly appreciated, my main goal being to keep the code looking sequential, kind of like when using Window.ShowDialog.
To summarize: (and to answer Chris in the comments)
The problem is that ShowDialog is non-blocking and, as stated above, the expected behavior is the same as when using Window.ShowDialog. Right now, I can either not block—but then the loop continues and I get several dialogs—or I can block (with FirstOrDefault), but it also blocks the UI, which prevents me from closing the dialog in order to complete the observable.
More explanations: (for Enigmativity)
When I call ShowDialog a control is displayed that is modal—in the sense that it blocks the user from accessing the rest of the application—but the call to the method is non-blocking, so execution continues immediately. In my example, this can potentially display several dialogs because of the loop. The method is non-blocking because all it does is add an object to a collection and I can’t change this behaviour.
However, hoping to use Rx, I made it so that ShowDialog will return an IObservable. So now the method returns immediately, but I have an object that will call OnCompleted of any observers once the control that was displayed by ShowDialog‘s actions is closed. I’m using a Subject for this, in case it matters.
What I want now, is to wait for this returned IObservable to complete before moving on, and so simulate a blocking call. FirstOrDefault does the waiting part successfully, but unfortunately, it also blocks the UI thread, preventing the control from actually showing, thus preventing the user from closing it, thus preventing the IObservable from completing.
I know my idea can’t be far off, because I can get things to kind of work by closing the dialog automatically after x seconds. All I need now is for the “waiting” part not to block the UI so that the user can close the control instead of a timer.
I found a solution to my problem, so I’ll share it in case you’re interested.
After some refactoring, I renamed the service’s method I used in the question and created a new one. It’s interface looks like this:
The part that solves my problem is the implementation of
ShowDialog:I think the comments should be enough to understand the code, which I can now use as so:
I’d love to hear any comments or alternatives other users may have.