如何在Uno Platform的TabViewItem内使用NavigateViewAsync导航页面?
我有一个带动态树形项的NavigationView,点击项时需要打开新标签页,但调用Navigator().NavigateViewAsync<...>()时,会覆盖整个窗口内容,而非在目标TabViewItem的内容区域内导航,且多次触发ArgumentNullException,多数错误指向Navigator的IDispatcher实例为空。
相关XAML代码
<Grid utu:SafeArea.Insets="All"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <utu:NavigationBar Grid.Row="0" Content="{Binding Title}" /> <TabView uen:Region.Name="tabview" uen:Region.Attached="True" Grid.Column="1" TabCloseRequested="TV_TabClose" x:Name="projectTabs" IsAddTabButtonVisible="False" /> <Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <NavigationView Grid.Column="0" x:Name="sidebar" IsBackButtonVisible="Collapsed" IsSettingsVisible="False"> <NavigationView.PaneCustomContent> <TreeView ItemInvoked="TreeItemClicked" CanDragItems="False" CanReorderItems="False" x:Name="ProjectTree" ItemTemplateSelector="{StaticResource TplSelector}" ItemsSource="{Binding wi}"/> </NavigationView.PaneCustomContent> </NavigationView> </Grid> </Grid>
最初尝试的代码
public async void NavToProject(WorkspaceProject? p) { if (p != null) { var newTab = new TabViewItem(); newTab.Content = new Frame(); await this.NavigateViewAsync<Project>(this, data: p.Project); } }
自定义Navigator/FrameNavigator的尝试
public class MainModel { ILogger<Navigator> log1 { get; set; } ILogger<FrameNavigator> log2 { get; set; } IDispatcher dispatcher { get; set; } public MainModel( IStringLocalizer localizer, INavigator navigator, IConfiguration config, IRouteResolver routeresolver, IDispatcher idispatcher, ILogger<Navigator> log1, ILogger<FrameNavigator> log2) { viewmapresolver = routeresolver; this.log1 = log1; this.log2 = log2; this.dispatcher = idispatcher; this.config = config; config.GetSection("RecentProjects").Bind(appopts); _navigator = navigator; } public async Task<TabViewItem?> NavToProject(WorkspaceProject? p) { if (p != null) { var newTab = new TabViewItem(); newTab.Content = new Frame(); var nb = new Navigator(log1, dispatcher, new NavigationRegion(view: newTab.Content as FrameworkElement), views); var fn = new FrameNavigator(null, dispatcher, new NavigationRegion(view: newTab.Content as FrameworkElement), views, new RegionControlProvider() { RegionControl = newTab.Content }) ; // 此处抛出异常 await fn.NavigateViewAsync<Project>(fn, data: p.Project); // 此处也抛出异常 await nb.NavigateViewAsync<Project>(nb, data: p.Project); return newTab; } return null; } }
两种尝试均触发Uno内部代码抛出的ArgumentNullException。
当前实现代码
App.cs路由配置
private static void RegisterRoutes(IViewRegistry views, IRouteRegistry routes) { views.Register( new ViewMap(ViewModel: typeof(ShellModel)), new ViewMap<MainPage, MainModel>(), new DataViewMap<ListPage, ListPageModel, G.Project>(), new DataViewMap<turquoise.Presentation.Project, ProjectModel, G.Project>(), new DataViewMap<TaskTree, TreeModel, G.Section>() ); routes.Register( new RouteMap("", View: views.FindByViewModel<ShellModel>(), Nested: new RouteMap[] { new RouteMap("Main", View: views.FindByViewModel<MainModel>()), new RouteMap("/Project/List", View: views.FindByViewModel<ListPageModel>()), new RouteMap("/Project", View: views.FindByViewModel<ProjectModel>()), } ) ); }
MainModel.cs代码
public partial class MainModel { private INavigator _navigator; private IConfiguration config; public static G.Project CurrentProject; public IDispatcher dispatcher { get; set; } public IRouteResolver viewmapresolver { get; set; } public MainModel( INavigator navigator, IConfiguration config, IRouteResolver routeresolver, IDispatcher idispatcher) { dispatcher = idispatcher; viewmapresolver = routeresolver; this.config = config; config.GetSection("RecentProjects").Bind(appopts); _navigator = navigator; } public async Task<TabViewItem?> NavToProject(WorkspaceProject? p) { if (p != null) { var newTab = new TabViewItem(); newTab.Header = p.Project.Name; var cc = new Frame(); Region.SetParent(cc, newTab); Region.SetName(cc, "/Project"); Region.SetAttached(cc, true); cc.SetValue(Region.ParentProperty, this); cc.SetValue(Region.NameProperty, "./Project"); cc.SetValue(Region.AttachedProperty, true); var nnr = new NavigationRegion(cc, Ioc.Default.GetRequiredService<IServiceProvider>()); var nav = new FrameNavigator( Ioc.Default.GetRequiredService<ILogger<FrameNavigator>>(), dispatcher, nnr, Ioc.Default.GetRequiredService<IRouteResolver>(), new RegionControlProvider() { RegionControl = cc } ); cc.SetNavigatorInstance(nav); cc.SetValue(Region.NavigatorProperty, "/Project"); nnr.ReassignParent(); NavigationResponse? resp = await nav?.NavigateViewAsync<Project>(cc, data: p.Project); newTab.Content = cc; return newTab; } return null; } }
目前仍多次触发ArgumentNullException,多数错误指向Navigator的IDispatcher实例为空。
解决方案
核心问题分析
- 手动实例化
FrameNavigator时,部分依赖项未正确注入,尤其是IDispatcher可能未关联到UI线程; - 未将Tab内的Frame注册为独立导航区域,导致导航操作仍作用于全局根区域;
- 原XAML中TabView的Grid列配置错误,导致布局异常。
修正后的实现步骤
1. 简化Tab创建与区域注册
无需手动实例化Navigator,利用Uno的区域导航机制为每个Tab的Frame分配独立区域:
public async Task<TabViewItem?> NavToProject(WorkspaceProject? p) { if (p == null) return null; var newTab = new TabViewItem { Header = p.Project.Name, Content = new Frame() }; // 为Frame设置唯一导航区域标识 var frame = newTab.Content as Frame; Region.SetAttached(frame, true); Region.SetName(frame, $"ProjectTab_{p.Project.Id}"); // 指定导航到该Frame的独立区域 var navigationResult = await _navigator.NavigateViewAsync<Project>( this, data: p.Project, region: Region.GetName(frame) ); return navigationResult.Success ? newTab : null; }
2. 确保Dispatcher正确注入
直接使用DI容器注入的UI线程Dispatcher,无需手动传递给新Navigator:
public MainModel( INavigator navigator, IConfiguration config, IRouteResolver routeresolver, IDispatcher dispatcher) { this.dispatcher = dispatcher; this.viewmapresolver = routeresolver; this.config = config; config.GetSection("RecentProjects").Bind(appopts); this._navigator = navigator; }
3. 修正XAML布局
调整TabView的Grid列配置,确保其处于正确的布局位置:
<Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="3*"/> </Grid.ColumnDefinitions> <NavigationView Grid.Column="0" x:Name="sidebar" IsBackButtonVisible="Collapsed" IsSettingsVisible="False"> <NavigationView.PaneCustomContent> <TreeView ItemInvoked="TreeItemClicked" CanDragItems="False" CanReorderItems="False" x:Name="ProjectTree" ItemTemplateSelector="{StaticResource TplSelector}" ItemsSource="{Binding wi}"/> </NavigationView.PaneCustomContent> </NavigationView> <TabView uen:Region.Name="tabview" uen:Region.Attached="True" Grid.Column="1" TabCloseRequested="TV_TabClose" x:Name="projectTabs" IsAddTabButtonVisible="False" /> </Grid>
4. 关键注意事项
- 每个Tab的Frame必须设置唯一的
Region.Name,避免导航冲突; - 导航时必须指定
region参数,明确目标区域; - 不要手动实例化
FrameNavigator,依赖DI注入的INavigator即可; - 确保所有UI操作在UI线程执行,DI注入的
IDispatcher已关联当前线程。
内容的提问来源于stack exchange,提问作者The Tablet
相关产品推荐
相关产品推荐

