WPF modal progress window

I apologize if this question has been answered many times, but I can’t find an answer that works for me. I would like to create a modal window that shows various progress messages while my application performs lengthy tasks. These tasks are performed in a separate thread, and I can update the text in the execution window at different stages of the process. Communication between threads works well. The problem is that I cannot force the window to be on top of other application windows (not for every application on the computer), stay on top, prevent interaction with the parent window and still allow it to continue working.

Here is what I have tried so far:

Firstly, my popup is a custom class that extends the Window class and has methods for updating the message box. I create a new instance of the splash class at an early stage and show / hide it as needed.

In the simplest case, I instantiate a 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 displays the window correctly and continues to run my code to handle initialization tasks, but allows me to click on the parent window and bring it to the foreground.

Next, I tried to turn off the main window and turn it back on 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 on the main window and bring it before the splash screen, which is not what I want.

Then I tried to use the top property in the splash window. This holds it in front of everything, and in combination with setting the IsEnabled property of the main window, I can prevent interaction, but it causes a pop-up screen in front of EVERYTHING, including other applications. I don’t want that either. I just want this to be the topmost window in this application.

Then I found messages about using .ShowDialog() instead of .Show() . I tried this and it showed the dialog correctly and did not allow me to click on the parent window, but the .ShowDialog() call causes the program to hang until you close the dialog before it continues to execute the code. This is obviously not what I want. I suppose I could call ShowDialog() in another thread so that this thread hangs, but the thread doing the work is not ... is this the recommended method?

I also considered the option of not using the window at all and instead placing the full-sized window element in front of everything else on the page. This will work, except that I have other windows that I open, and I would like to be able to use the splash screen when they are open. If I used a window element, I would have to recreate it in each window, and I would not be able to use my convenient UpdateSplashText method in my custom splash class.

So that brings me to the question. What is the right way to handle this?

Thanks for your time and sorry for the long question, but the details are important :)

+8
c # wpf window modal-dialog dialog
source share
4 answers

You are correct that ShowDialog gives you most of the user interface behavior you want.

It has a problem that as soon as you call it, you are blocking the execution. How could you run some code after the form is shown, but determine what it should be before it is shown? It's your problem.

You can just do all the work in the splash class, but this is pretty bad practice due to the tight connection.

What you can do is use the Loaded Window event to determine the code that should run after the window displays, but where it is defined before it is displayed.

 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(); } 

Please note that this method is intended to encapsulate the template code here, so you can pass any work method that accepts a progress indicator, and it will do this work in the background thread, showing a general splash screen that has progress indicated by the worker.

Then one could call 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"); }); } 
+13
source share

You may have a constructor for the Task execution window, and then make sure the window calls task.Start in the OnLoaded event. Then you use ShowDialog from the parent form, which will cause the progress window to start the task.

Note that you can also call task.Start in the constructor or in the parent form somewhere before calling ShowDialog . What is the most suitable for you?

Another option is to simply use the progress bar in the status bar of the main window and get rid of the pop-up window. This option seems to be more and more common these days.

+1
source share

You can use the Visibility property on Window to hide the entire window when the splash screen starts.

Xaml

 <Window ... Name="window" /> 

the code

 window.Visibility = System.Windows.Visibility.Hidden; //show splash //do work //end splash window.Visibility = System.Windows.Visibility.Visible; 
+1
source share

I found a way to make this work by calling ShowDialog() in a separate thread. I created my own ShowMe() and HideMe() methods in my dialog class that handle the work. I also capture the Closing event to prevent the dialog from closing so that I can reuse it.

Here is my code for my splash screen:

 public partial class StartupSplash : Window { private Thread _showHideThread; public StartupSplash() { InitializeComponent(); this.Closing += OnCloseDialog; } public string Message { get { return this.lb_progress.Content.ToString(); } set { if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread) this.lb_progress.Content = value; else this.lb_progress.Dispatcher.Invoke(new Action(() => this.lb_progress.Content = value)); } } public void ShowMe() { _showHideThread = new Thread(new ParameterizedThreadStart(doShowHideDialog)); _showHideThread.Start(true); } public void HideMe() { //_showHideThread.Start(false); this.doShowHideDialog(false); } private void doShowHideDialog(object param) { bool show = (bool)param; if (show) { if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread) this.ShowDialog(); else Application.Current.Dispatcher.Invoke(new Action(() => this.ShowDialog())); } else { if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread) this.Close(); else Application.Current.Dispatcher.Invoke(new Action(() => this.Close())); } } private void OnCloseDialog(object sender, CancelEventArgs e) { e.Cancel = true; this.Hide(); } } 
-one
source share

All Articles