我想在 WinUI 3 (v1.1.5) 桌面应用程序中预加载ShellPage
。也就是说,在Activation
期间(由
await App.GetService<IActivationService>().ActivateAsync(args);
在App
类的OnLaunched
处理程序中),我想确保在显示任何导航页面之前加载ShellPage
。我已更改服务配置以包括
services.AddSingleton<ShellPage>();
services.AddSingleton<ShellViewModel>();
在App
类的构造函数中,这应该意味着每个ShellPage
和ShellViewModel
中只有一个将为应用运行实例化,但问题是它们何时完全预配?
正常的进展是Activation
步骤首先将ShellPage
分配给MainWindow.Content
,然后导航到MainPage
(这些是默认项目的名称)。因为MainPage
实际上是在ShellPage
上加载到Frame
中,所以似乎MainPage
的布局发生在布局完成之前ShellPage
。
知道我在初始启动时是如何做到这一点的吗?这只是呈现第一个Page
时的问题。之后,ShellPage
被重复使用。
先澄清一下,然后是我找到的问题的答案。
Andrew 的答案(上图)非常适合在启动时实例化NavigationView
中的所有Pages
,但加载的第一个页面仍然无法访问其构造函数中完全加载的ShellPage
(因此,完全填充的元素树)。Andrew 是对的,默认情况下NavigationViewItems
(Pages
) 不会持久,但ShellPage
会保留,因为它是 UI 的一部分。具体来说,它是MainWindow
的内容,并定义了加载NavigationViewItems
的Frame
。无论显示哪个Page
,它都是人们看到的ShellPage
的相同实例。
出现此问题的原因是Activation
(特别是DefaultActivationHandler
)在启动时完成App
顺序。当App
启动时,它会调用
await App.GetService<IActivationService>().ActivateAsync(args);
哪个做
// Set the MainWindow Content.
if (App.MainWindow.Content == null)
{
_shell = App.GetService<ShellPage>();
App.MainWindow.Content = _shell ?? new Frame();
}
并在完成ShellPage
加载之前导航到第一个Page
(通过调用DefaultActivationHandler
将第一个Page
加载到NavigationView.Frame
中)。因此,当ShellPage
加载时MainPage
没有完全加载(ShellPage.IsLoaded == false
)。
要在加载任何NavigationViewItem
Pages
之前完全实例化ShellPage
,只需更改加载顺序即可。首先,通过将HandleInternalAsync
编辑到DefaultActivationHandler.cs
protected async override Task HandleInternalAsync(LaunchActivatedEventArgs args)
{
//_navigationService.NavigateTo(typeof(MainViewModel).FullName!, args.Arguments);
await Task.CompletedTask;
}
将导航移动到ShellPage.xaml.cs
中的OnLoaded
处理程序:
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!);
}
现在,无论顺序如何,所有Pages
在导航到时都会收到已加载ShellPage
。
默认情况下,TemplateStudio的导航会为每个导航重新实例化页面。它不使用ServicesProvider
,因此将您的页面注册为单例将无济于事。
如果要保留页面实例,则需要在页面上设置NavigationCacheMode="Required"
。这样,即使您离开,您的页面也会被缓存。
不过,您的页面至少在您导航到它们一次之前不会被实例化。为了在一开始就实例化所有页面,您需要至少浏览一次。
您可以使用这样的方法获得所有NavigationViewItem
。
private static IEnumerable<NavigationViewItem> GetNavigationViewItems(IEnumerable<object> items)
{
foreach (var item in items.OfType<NavigationViewItem>())
{
yield return item;
foreach (var grandChild in GetNavigationViewItems(item.MenuItems.OfType<NavigationViewItem>()))
{
yield return grandChild;
}
}
}
并在NavigationViewService
的Initialize
方法中像这样使用它。
[MemberNotNull(nameof(_navigationView))]
public void Initialize(NavigationView navigationView)
{
_navigationView = navigationView;
_navigationView.BackRequested += OnBackRequested;
_navigationView.ItemInvoked += OnItemInvoked;
IEnumerable<NavigationViewItem> menuItems =
GetNavigationViewItems(_navigationView.MenuItems);
foreach (var item in menuItems)
{
if (item.GetValue(NavigationHelper.NavigateToProperty) is string pageKey)
{
_navigationService.NavigateTo(pageKey);
}
}
}