WPF modal progress window

c#, dialog, modal-dialog, window, wpf

Solution

You are correct that `ShowDialog` gives you most of the UI behavior that you want.

It does have the problem that as soon as you call it you block execution though. How could you possibly run some code after you show the form, but define what it should be before it's shown? That's your problem.

You could just do all of the work within the splash class, but that's rather poor practice due to tight coupling.

What you can do is leverage the `Loaded` event of `Window` to define code that should run after the window is shown, but where it is defined before you show it.

public static void DoWorkWithModal(Action<IProgress<string>> work)
{
    SplashWindow splash = new SplashWindow();

    splash.Loaded += (_, args) =>
    {
        BackgroundWorker worker = new BackgroundWorker();

        Progress<string> progress = new Progress<string>(
            data => splash.Text = data);

        worker.DoWork += (s, workerArgs) => work(progress);

        worker.RunWorkerCompleted +=
            (s, workerArgs) => splash.Close();

        worker.RunWorkerAsync();
    };

    splash.ShowDialog();
}

Note that this method is designed to encapsulate the boilerplate code here, so that you can pass in any worker method that accepts the progress indicator and it will do that work in a background thread while showing a generic splash screen that has progress indicated from the worker.

This could then be called something like this:

public void Foo()
{
    DoWorkWithModal(progress =>
    {
        Thread.Sleep(5000);//placeholder for real work;
        progress.Report("Finished First Task");

        Thread.Sleep(5000);//placeholder for real work;
        progress.Report("Finished Second Task");

        Thread.Sleep(5000);//placeholder for real work;
        progress.Report("Finished Third Task");
    });
}

Problem

I apologize if this question has been answered tons of times, but I can't seem to find an answer that works for me. I would like to create a modal window that shows various progress messages while my application performs long running tasks. These tasks are run on a separate thread and I am able to update the text on the progress window at different stages of the process. The cross-thread communication is all working nicely. The problem is that I can't get the window to be on top of only other application windows (not every application on the computer), stay on top, prevent interaction with the parent window, and still allow the work to continue. Here's what I've tried so far: First, my splash window is a custom class that extends the Window class and has methods to update the message box. I create a new instance of the splash class early on and Show/Hide it as needed. In the simplest of cases, I instantiate the window and call `.Show()` on it: ``` //from inside my secondary thread this._splash.Dispatcher.Invoke(new Action(() => this._splash.Show()); //Do things //update splash text //Do more things //close the splash when done this._splash.Dispatcher.Invoke(new Action(() => this._splash.Hide()); ``` This correctly displays the window and continues running my code to handle the initialization tasks, but it allows me to click on the parent window and bring that to the front. Next I tried disabling the main window and re-enabling later: ``` Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = false)); //show splash, do things, etc Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = true)); ``` This disables all the elements in the window, but I can still click the main window and bring it in front of the splash screen, which is not what I want. Next I tried using the topmost property on the splash window. This keeps it in front of everything, and in conjunction with setting the main window IsEnabled property I could prevent interaction, but this makes the splash screen appear in front of EVERYTHING, including other applications. I don't want that either. I just want it to be the topmost window within THIS application. Then I found posts about using `.ShowDialog()` instead of `.Show()`. I tried this, and it correctly showed the dialog and did not allow me to click on the parent window, but calling `.ShowDialog()` makes the program hang waiting for you to close the dialog before it will continue running code. This is obviously, not what I want either. I suppose I could call `ShowDialog()` on a different thread so that that thread would hang but the thread doing the work would not...is that the recommended method? I have also considered the possibility of not using a window at all and instead putting a full-sized window element in front of everything else on the page. This would work except that I have other windows I open and I'd like to be able to use the splash screen when those are open too. If I used a window element I would have to re-create it on every window and I wouldn't be able to use my handy `UpdateSplashText` method in my custom splash class. So this brings me to the question. What is the right way to handle this? Thanks for your time and sorry for the long question but details are important :)

Original source