WPFUI报错

WPFUI报错 WPFUI 报错

WPFUI报错

page does not have a parameterless constructor. If you are using Wpf.Ui.IPageService do not navigate initially and don't use Cache or Precache

问题原因

WPFUI中的NavigationView只支持导航页面的无参构造函数或含一个dataContext的有参构造函数。因为在View的构造函数中注入了一些服务,导致View创建失败,WPFUI报错。

问题处理

查看异常堆栈,找报错位置:

在 Wpf.Ui.Controls.NavigationViewActivator.CreateInstance(Type pageType, Object dataContext) 在 Wpf.Ui.Controls\NavigationViewActivator.cs 中: 第 37 行
在 Wpf.Ui.Controls.NavigationView.GetPageInstanceFromCache(Type targetPageType) 在 Wpf.Ui.Controls\NavigationView.cs 中: 第 1206 行
在 Wpf.Ui.Controls.NavigationCache.Remember(Type entryType, NavigationCacheMode cacheMode, Func`1 generate) 在 Wpf.Ui.Controls\NavigationCache.cs 中: 第 25 行
在 Wpf.Ui.Controls.NavigationView.GetNavigationItemInstance(INavigationViewItem viewItem) 在 Wpf.Ui.Controls\NavigationView.cs 中: 第 1185 行
在 Wpf.Ui.Controls.NavigationView.NavigateInternal(INavigationViewItem viewItem, Object dataContext, Boolean addToNavigationStack, Boolean isBackwardsNavigated) 在 Wpf.Ui.Controls\NavigationView.cs 中: 第 1131 行
在 Wpf.Ui.Controls.NavigationViewItem.OnClick() 在 Wpf.Ui.Controls\NavigationViewItem.cs 中: 第 314 行
...

看源码,找解决方案:

private object GetNavigationItemInstance(INavigationViewItem viewItem)
{
    if (viewItem.TargetPageType is null)
    {
        throw new InvalidOperationException(
            $"The {nameof(viewItem)}.{nameof(viewItem.TargetPageType)} property cannot be null."
        );
    }

    if (_serviceProvider is not null)
    {
        return _serviceProvider.GetService(viewItem.TargetPageType)
            ?? throw new InvalidOperationException(
                $"{nameof(_serviceProvider)}.{nameof(_serviceProvider.GetService)} returned null for type {viewItem.TargetPageType}."
            );
    }

    if (_pageService is not null)
    {
        return _pageService.GetPage(viewItem.TargetPageType)
            ?? throw new InvalidOperationException(
                $"{nameof(_pageService)}.{nameof(_pageService.GetPage)} returned null for type {viewItem.TargetPageType}."
            );
    }

    return _cache.Remember(
            viewItem.TargetPageType,
            viewItem.NavigationCacheMode,
            ComputeCachedNavigationInstance
        )
        ?? throw new InvalidOperationException(
            $"Unable to get or create instance of {viewItem.TargetPageType} from cache."
        );

    object? ComputeCachedNavigationInstance() => GetPageInstanceFromCache(viewItem.TargetPageType);
}

可以看到,如果提供了serviceProvider或者pageService,就可以通过容器获取View实例,不需要通过NavigationViewActivator.CreateInstance创建实例了。

提供ServiceProvider,例如使用Prism:

public class PrismServiceProvider : IServiceProvider
{
    private readonly IContainerProvider _containerProvider;

    public PrismServiceProvider(IContainerProvider containerProvider)
    {
        _containerProvider = containerProvider;
    }

    public object GetService(Type serviceType)
    {
        return _containerProvider.Resolve(serviceType);
    }
}

WPFUI中未提供默认的IPageService实现,但demo.mvvm中提供了基于IServiceProvider的实现,可以参考。本文基于Prism实现:

public class PageService : IPageService
{
    private readonly IContainerProvider _containerProvider;

    public PageService(IContainerProvider containerProvider)
    {
        _containerProvider = containerProvider;
    }

    public T GetPage<T>() where T : class
    {
        if (!typeof(FrameworkElement).IsAssignableFrom(typeof(T)))
            throw new InvalidOperationException("The page should be a WPF control.");

        return (T)_containerProvider.Resolve(typeof(T));
    }

    public FrameworkElement GetPage(Type pageType)
    {
        if (!typeof(FrameworkElement).IsAssignableFrom(pageType))
            throw new InvalidOperationException("The page should be a WPF control.");

        return _containerProvider.Resolve(pageType) as FrameworkElement;
    }
}

最后一步,将PrismServiceProvider、PageService注入容器,并通过INavigationService为NavigationView设置IServiceProvider和IPageService:

// App.xaml.cs
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    var serviceProvider = new PrismServiceProvider(Container);
    containerRegistry.RegisterInstance<IServiceProvider>(serviceProvider);
    containerRegistry.RegisterSingleton<IPageService, PageService>();
    containerRegistry.RegisterSingleton<INavigationService, NavigationService>();
}

// MainWindow.xaml.cs
public MainWindow(INavigationService navigationService,
    IPageService pageService,
    ISnackbarService snackbarService)
{
    InitializeComponent();

    navigationService.SetPageService(pageService);
    navigationService.SetNavigationControl(NavigationView);
}
转载请注明出处,欢迎交流。
评论