Best way to architect this scenario
I would:
Create classes to manage the data for your "Select Project" window (such as MRU lists, templates, etc), making sure they are self-contained and don't require interaction with the main thread.
Store information about the "current" project(s) in the Application object or a singleton object.
Create the "Select Project" window to operate completely independently of the main window.
Set your "Select Project" window's "Open" and "Create" buttons to fire the Application.Open
command, and bind the CommandParameter on the button to the property that contains the filename to open (or the template to instantiate).
Add a CommandBinding
for the Application.Open
command at the window level to take the command parameter and set the current project in the Application object.
In your application's startup code, immediately spawn a thread to create the "Select Project" window, then immediately do {a Dispatcher.Invoke at ApplicationIdle priority followed by a very short Thread.Sleep} several times, then proceed, allowing the main window to be created but make it initially invisible.
Set a trigger in the main window to make it visible once the current project has been set for the first time.
Here is how the Open
command handler in the "Select Project" window might update the project path in the Application object:
Application.Current.Dispatcher.Invoke(DispatcherPriority.Send, new Action(() =>
{
((App)Application.Current).CurrentProjectPath = e.Parameter;
}
Here is what the application's startup code would look like:
public override void OnStartup(StartupEventArgs e)
{
new Thread(() => { new SelectProject().Show(); }).Start();
for(int i=0; i<10; i++)
{
Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, () => {});
Thread.Sleep(20);
}
_guts = new MainWindowContent(); // probably a UserControl
_guts.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
_guts.Arrange(new Rect(0, 0, guts.DesiredSize.Width, guts.DesiredSize.Height));
_guts.SetBinding(VisibilityProperty,
new Binding { Source = this, Path="StartupFinished",
Converter = BoolToVisibiltyConverter.Instance });
_mainWindow = new MainWindow { Content = guts };
// Note: Title and other properties of MainWindow would be set in the XAML
}
Note the extra code to make sure the guts get measured and arranged ahead of time.
Here is the code to set the current project path:
public string CurrentProjectPath
{
get { return _currentProjectPath; }
set
{
_currentProjectPath = value;
LoadProject();
_startupFinished = true;
OnPropertyChanged("StartupFinished");
}
}
This code assumes the XAML binds _mainWindow's Visibility property
StartupFinished is a DependencyProperty that the main window's Visibility property is bound to, but it could be done in other ways.
Will you be able to see faster startup time this way?
Yes.
Should Select Project be created on main thread & main on other thread, or vice versa
You should create MainWindow on the main thread so it shares the same Dispatcher with the Application object. That means "Select Project" has to be in the second thread.
If you need to use a Win32 startup window for speed
It is probably probably not necessary. Startup of WPF itself is only a fraction of a second. But if it is necessary, the Application.Current.Dispatcher.Invoke
technique described above will work.
Do you know how Blend implements its startup window?
No