Blocking AlertWindow in non-message thread

How can I show a blocking alert window in a non-message thread? In essence, I have a worker thread, and in case there’s an unexpected error, I need to ask the user how to continue after the error, which means the message box must block the worker thread. Before, in Juce 6.0.8, I just did:

const auto result = AlertWindow::showYesNoCancelBox(AlertWindow::AlertIconType::QuestionIcon, "Continue?",
                                                            question,
                                                            "Retry",
                                                            "Force pass",
                                                            "Fail"
                                                            );

and that did the trick. Now, in Juce 7.0.6, I get an assert that the message manager should be locked. So now I write:

MessageManagerLock managerLock{};
const auto result = AlertWindow::showYesNoCancelBox(AlertWindow::AlertIconType::QuestionIcon, "Continue?",
                                                            question,
                                                            "Retry",
                                                            "Force pass",
                                                            "Fail"
                                                            );

However, this triggers an assert from Component::removeFromDesktop(), jassert (peer != nullptr); (at juce_Component.cpp::437).

I suppose I could use the callback version and implement an explicit wait, but then I’ll be reimplementing what the one-liner did early on itself. Is there an easier way?

You can use juce::MessageManager::callAsync to bring up the alert window in the message thread.

But that doesn’t block the calling thread, right?

Yes. Usually UI programming in JUCE is done in an async way. So you’ll need to apply some waiting on your side thread until the callback is returned, for example by waiting on an atomic bool.

You can use MessageManager::callFunctionOnMessageThread() to obtain the blocking behavior, unfortunately the argument taken by this call is not a simple lambda, but once you provide the pointer that it wants, it does exactly what you need.

I have written this wrapper around it, that does the same and it takes a lambda:

inline void callOnMessageThread (std::function <void ()> function)
{
    struct H
    {
        static void* call (void* fp)
        {
            auto func = *static_cast <std::function <void ()>*> (fp);
            if (func)
                func ();
            return nullptr;
        }
    };

    juce::MessageManager::getInstance ()->callFunctionOnMessageThread (H::call, &function);
}
2 Likes

Thanks, I just did about the same, but your wrapper is much nicer than my quick hack!

1 Like

I slightly expanded on the wrapper, to avoid an allocation and allow returning a value:

template<typename Function>
inline auto callOnMessageThread (Function && function)
{
    using R = decltype(function());
    struct Trampoline
    {
        static void* call (void* trampP)
        {
            auto tramp = static_cast <Trampoline*> (trampP);
            tramp->retVal = tramp->func();
            return nullptr;
        }
        
        Function &func;
        R       retVal;
    };
    Trampoline tramp{function, R{}};

    juce::MessageManager::getInstance()->callFunctionOnMessageThread(Trampoline::call, &tramp);
    return tramp.retVal;
}
2 Likes

Thank you very much for sharing this! I had my own version of this function but yours is better by a lot :smiley: