Search code examples
c#loadingstartupnavigationviewwinui-3

How do I pre-load ShellPage during Activation in a WinUI 3 NavigationView Desktop project?


I'd like to pre-load the ShellPage in a WinUI 3 (v1.1.5) Desktop application. That is, during Activation (called by

await App.GetService<IActivationService>().ActivateAsync(args);

in the OnLaunched handler of the App class), I'd like to make sure ShellPage is loaded before any of the navigation pages are displayed. I've changed the service configuration to include

    services.AddSingleton<ShellPage>();
    services.AddSingleton<ShellViewModel>();

in the constructor for the App class which should mean only one of each of ShellPage and ShellViewModel will be instantiated for the app run but the question is when are they fully provisioned?

The normal progression is that the Activation step first assigns ShellPage to MainWindow.Content, then navigates to MainPage (these are the names for the default project). Because MainPage is actually loaded into a Frame on ShellPage, it seems layout for MainPage happens before ShellPage layout is completed.

Any idea how I do this on initial startup? This is only an issue when the first Page is presented. After that, ShellPage is reused.


Solution

  • A little clarification first, and then the answer I found to the issue.

    Andrew's answer (above) is great for instantiating all of the Pages in the NavigationView at startup but the very first page loaded still would not have access to a fully loaded ShellPage in its constructor (and thus, a fully populated element tree). Andrew is right that the NavigationViewItems (Pages) don't persist by default, but the ShellPage does as it's part of the UI. Specifically, it is the content of the MainWindow and defines a Frame into which NavigationViewItems are loaded. Regardless of which Page is displayed, it's the same instance of the ShellPage people see.

    The issue arises because of the order in which Activation (specifically, the DefaultActivationHandler) is done at App startup. When the App starts, it calls

    await App.GetService<IActivationService>().ActivateAsync(args);
    

    which does

        // Set the MainWindow Content.
        if (App.MainWindow.Content == null)
        {
            _shell = App.GetService<ShellPage>();
            App.MainWindow.Content = _shell ?? new Frame();
        }
    

    and navigates to the first Page (loads the first Page into the NavigationView.Frame by calling DefaultActivationHandler) before finishing the loading of ShellPage. Thus, ShellPage is not fully loaded (ShellPage.IsLoaded == false) when MainPage is loaded.

    To fully instantiate ShellPage before any of the NavigationViewItem Pages are loaded, simply change the loading sequence. First, defer the navigation to the first page (whichever you choose) by editing HandleInternalAsync in DefaultActivationHandler.cs to

        protected async override Task HandleInternalAsync(LaunchActivatedEventArgs args)
        {
            //_navigationService.NavigateTo(typeof(MainViewModel).FullName!, args.Arguments);
    
            await Task.CompletedTask;
        }
    

    Move the navigation to the OnLoaded handler in ShellPage.xaml.cs:

        private void OnLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
        {
            TitleBarHelper.UpdateTitleBar(RequestedTheme);
    
            KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu));
            KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.GoBack));
    
            App.GetService<INavigationService>().NavigateTo(typeof(MainViewModel).FullName!);
        }
    

    All Pages now receive a loaded ShellPage when navigated to, regardless of order.