There are some excellent articles on Quantum Bit Designs on WPF and cross-thread updates. I borrowed one of them to implement a splash screen in my WPF demo application.
On start up of my WPF GUI application, I want to show a splash window with a progress bar underneath. At the same time, I do some potentially time-consuming tasks, such as openning a database connection and load some reference data into memory. Since the duration of this type of operation cannot be known up front and the percentage of each step wrt the entire operation cannot be determined either, it is sensible to use an undeterministic progress bar. The SplashScreen.xaml file is shown below:
The trick is to have a System.ComponentModel.BackgroundWorker to perform the long running operation. The initialisation operation class is defined as such:
public class InitOperation { bool completed = false; string errorDescription; ////// the operation is either completed successfully (true) or /// aborted due to exceptions (false). /// public bool Completed { get { return completed; } } ////// the error text to be displayed in GUI in case of error /// (i.e. completed==false) /// public string ErrorDescription { get { return errorDescription; } } public InitOperation() { completed = false; errorDescription = String.Empty; } public void Start() { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork+=new DoWorkEventHandler(worker_DoWork); worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); worker.RunWorkerAsync(); } void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { OnComplete(EventArgs.Empty); } ////// Upon operation completion, fire a Complete event so that the /// observer can receive and act on it. /// protected virtual void OnComplete(EventArgs e) { if (this.Complete != null) { this.Complete(this, e); } } ////// The long-running operation is performed here. /// void worker_DoWork(object sender, DoWorkEventArgs e) { try { // long-running operation goes here. completed=true; } catch (Exception ex) { completed=false; errorDescription = ex.Message + "\n" + ex.StackTrace; } } ////// the event to be fired upon operation completion /// public event EventHandler Complete; }
The core of the InitOperation is in the Start() method. It creates a background worker, adds the event handlers and then executes the worker in another thread. Once the worker completes, it will fire a Complete event. Whoever is listening to the event will receive it. So I need to have the Splash screen (running in the GUI thread) to register to the event and handle it:
public partial class SplashScreen : Window { InitOperation _operation; public SplashScreen(InitOperation worker) { this._operation = worker; this._operation.Complete += new EventHandler(_worker_Complete); InitializeComponent(); this.Loaded += new RoutedEventHandler(Splash_Loaded); } void _worker_Complete(object sender, EventArgs e) { if (_operation.Completed) { MainWindow mw = new MainWindow(); Close(); mw.Show(); } else { MessageBox.Show("Cannot initialise application:\n"+_operation.ErrorDescription, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Error); Close(); } } void Splash_Loaded(object sender, RoutedEventArgs e) { _operation.Start(); } }
The SplashScreen class is very straight forward. It registers itself to the InitOperation's Complete event and then Start() the initialisation operation. It takes an InitOperation as input parameter for its constructor. This wiring up is done at the App.xaml and App.xaml.cs.
Instead of specifying the StartupUri, a Startup event handler is defined in App.
public partial class App: System.Windows.Application { private void Application_Startup(object sender, StartupEventArgs e) { SplashScreen splash = new SplashScreen(new InitOperation()); splash.Show(); } }