views:

437

answers:

1

Our main WPF window ('Main Window') takes considerable time to create (it is using a ribbon and docking panel similar to Visual Studio). Just like in Blend the first action will always be to either choose an existing project to work on or to create a new project. Therefore we would like to show this 'Select Project' window as quickly as possible and create the heavy 'Main Window' in the background (invisible until the user clicks OK on the 'Select Project' window).

What's the best way to architect this startup scenario, preferably using 2 WPF windows (one for the project selection and one for the main control)?

I'm aware of this sample which demonstrates how to load a Win32 window shortly after application startup, but I'd prefer a WPF-only solution.

Another potential solution is to use multiple WPF UI threads, however lifetime management and window-to-window communication are non-trivial.

Questions:

Will I be able to see a faster startup time when implementing the 2 WPF window solution as compared to simply creating the 'Main Window' and not implement the 'Select Project' window (assuming I can find an implementation that takes care of the window lifetimes)?

Should the 'Select Project' window be created on the main application thread and the 'Main Window' on a background thread (or visa versa)?

If I need to use a Win32 startup window for speed, how would I communicate the user's choice of project name to the main WPF application window?

Does anyone know how Blend actually implements their startup window (which I am trying to mimic here)?

Any implementation pointers would be much appreciated!

+1  A: 

Best way to architect this scenario

I would:

  1. 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.

  2. Store information about the "current" project(s) in the Application object or a singleton object.

  3. Create the "Select Project" window to operate completely independently of the main window.

  4. 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).

  5. 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.

  6. 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.

  7. 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

Ray Burns