I have a NavigationView that displays a dynamically generated tree of items, and when an item is clicked, it's supposed to open a new tab. However, instead of navigating inside the TabViewItem's content, calling Navigator().NavigateViewAsync<...>() overwrites the contents of the entire window with the new view, as opposed to the specific region i want.
Relevant 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>
Function as written in original attempt
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);
}
}
As well, I've tried initializing my own instances of Navigator and 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
}) ;
// this throws
await fn.NavigateViewAsync<Project>(fn, data: p.Project);
// this also throws
await nb.NavigateViewAsync<Project>(nb, data: p.Project);
return newTab;
}
return null;
}
}
Both attempts throw an ArgumentNullException from within Uno's internal code.
My current approach looks like this:
App.cs routes
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;
}
}
}
I'm still getting multiple ArgumentNullExceptions, mostly regarding the Navigator's IDispatcher instance being null.
Figured it out!
Basically, what I had to do was explicitly set the DataContext of the Frame, as simply navigating within it using
NavigateViewAsync(...)does NOT populate it like I naively thought it would.Final code is below: