Ghost1372

everything can be handy

NavigationServiceEx

Easily implement a NavigationView!

First Add a NavigationView with your Items:

1
2
3
4
5
6
7
8
9
10
11
<NavigationView x:Name="NavView">
<NavigationView.MenuItems>
<NavigationViewItem dev:NavigationServiceEx.NavigateTo="views:BlankPage1"
Content="Page 1" />
<NavigationViewItem dev:NavigationServiceEx.NavigateTo="views:BlankPage2"
Content="Page 2" />
<NavigationViewItem dev:NavigationServiceEx.NavigateTo="views:BlankPage3"
Content="Page 3" />
</NavigationView.MenuItems>
<Frame x:Name="NavFrame"/>
</NavigationView>

NavigationServiceEx.NavigateTo attached property allows each NavigationViewItem to define the destination page directly in XAML. It simplifies navigation by binding menu items to specific views without requiring explicit event handlers.

then Create a new INavigationServiceEx:

1
2
3
INavigationServiceEx navigationService;
navigationService = new NavigationServiceEx();

now you should call Initialize method with a NavigationView and Frame

1
navigationService.Initialize(NavView, NavFrame);

NavigateToCommand

You can use a predefined command for navigating between pages. To do this, define a CommandParameter using the NavigationParameter markup extension, specifying the target page and the BreadCrumbNavigator header (if using ConfigureBreadCrumbNavigator). Additionally, you can set a NavigationTransitionInfo to customize the transition effect.

1
2
<Button Content="Navigate" Command="{x:Bind local:App.Current.NavService.NavigateToCommand}"
CommandParameter="{dev:NavigationParameter PageType=views:GeneralSettingPage, BreadCrumbHeader='General'}" />

Configs

there are some configure methods:

ConfigureDefaultPage

set Default page for NavigationView

1
Initializ(...).ConfigureDefaultPage(typeof(HomeLandingsPage));

ConfigureSettingsPage

set Settings page for NavigationView

1
Initializ(...).ConfigureSettingsPage(typeof(GeneralPage));

ConfigureBreadcrumbBar

You should have a PageDictionary like this, which you can define your pages:

1
2
3
4
5
6
public Dictionary<Type, BreadcrumbPageConfig> PageDictionary = new()
{
{typeof(BlankPage1), new BreadcrumbPageConfig { PageTitle = "Page 1", IsHeaderVisible = true, ClearNavigation = false}},
{typeof(BlankPage2), new BreadcrumbPageConfig { PageTitle = "Page 2", IsHeaderVisible = true, ClearNavigation = false}},
{typeof(BlankPage3), new BreadcrumbPageConfig { PageTitle = "Page 3", IsHeaderVisible = true, ClearNavigation = false}},
};

it is better to place BreadcrumbNavigator in NavigationView.Header

1
2
3
<NavigationView.Header>
<dev:BreadcrumbNavigator x:Name="BreadCrumbNav" />
</NavigationView.Header>

Now Call ConfigureBreadcrumbBar method:

1
Initializ(...).ConfigureBreadcrumbBar(BreadCrumbNav, NavigationPageMappings.PageDictionary);

use dev:BreadcrumbNavigator.PageTitle and dev:BreadcrumbNavigator.IsHeaderVisible attached properties on your pages, for Title and Header visiblity.

T4 Template

You can simplify creating PageDictionary by Creating a new T4 template.
Copy-Paste following Script, this script help you to Auto Generate PageDictionary:

  • Replace $T4_NAMESPACE$ with your app namespace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<#@ template language="C#" hostspecific="true" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.IO" #>
<#@ assembly name="System.Text.Json" #>
<#@ assembly name="System.Memory" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.Json" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
// -----------------------------------------------------------------------------------------------------
// | <auto-generated> |
// | This code was generated by a tool. |
// | |
// | Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. |
// | </auto-generated> |
// -----------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;

namespace $T4_NAMESPACE$;
public partial class BreadcrumbPageMappings
{
public static Dictionary<Type, BreadcrumbPageConfig> PageDictionary = new()
{
<#
// Get the directory of the project
string rootDirectory = FindProjectRoot(Path.GetDirectoryName(Host.TemplateFile));
List<string> xamlFiles = GetXamlClasses(rootDirectory);
foreach (var file in xamlFiles)
{
try
{
string content = File.ReadAllText(file);
string relativePath = GetRelativePath(rootDirectory, file);
ExtractAttachedProperties(content);
}
catch (Exception ex)
{
#>
// Output an error message as comment in the generated file
<#= $"// Error loading XAML file: {file} - {ex.Message}" #>
<#
}
}

void ExtractAttachedProperties(string content)
{
// Extract the Type from the x:Class attribute
string xClassValue = GetXClassValue(content);
if (string.IsNullOrEmpty(xClassValue))
{
return; // Skip if no x:Class found
}

string pageTitle = GetAttachedPropertyValue("BreadcrumbNavigator.PageTitle", content);
string isHeaderVisible = GetAttachedPropertyValue("BreadcrumbNavigator.IsHeaderVisible", content);
string clearCache = GetAttachedPropertyValue("BreadcrumbNavigator.ClearCache", content);

// Using ternary operators to set values or default to empty strings
string title = !string.IsNullOrEmpty(pageTitle) ? pageTitle : string.Empty;

// Convert to bool based on string value directly
bool isHeaderVisibile = isHeaderVisible?.Equals("True", StringComparison.OrdinalIgnoreCase) ?? false;
bool clearNavigation = clearCache?.Equals("True", StringComparison.OrdinalIgnoreCase) ?? false;

if (string.IsNullOrEmpty(title) && string.IsNullOrEmpty(isHeaderVisible) && string.IsNullOrEmpty(clearCache))
{
return;
}
#>
<#= $"{{typeof({xClassValue}), new BreadcrumbPageConfig {{ PageTitle = {(string.IsNullOrEmpty(title) ? "null" : $"\"{title}\"")}, IsHeaderVisible = {isHeaderVisibile.ToString().ToLower()}, ClearNavigation = {clearNavigation.ToString().ToLower()}}}}}," #>
<#
}
string GetXClassValue(string content)
{
// Simple regex to match x:Class attribute
var match = System.Text.RegularExpressions.Regex.Match(content, @"x:Class=""([^""]+)""");
if (match.Success && match.Groups.Count > 1)
{
return match.Groups[1].Value; // Return the matched class value
}
return null;
}

string GetAttachedPropertyValue(string propertyName, string content)
{
// Find the property assignment in the content
var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
if (line.Contains(propertyName))
{
// Extract the value after '=' and before the next ';' or new line
int startIndex = line.IndexOf('=') + 1;
int endIndex = line.IndexOf(';', startIndex);
if (endIndex == -1) endIndex = line.Length;

string value = line.Substring(startIndex, endIndex - startIndex).Trim().Trim('"');
return value;
}
}
return null;
}

List<string> GetXamlClasses(string projectDirectory)
{
var xamlClasses = new List<string>();
var xamlFiles = Directory.GetFiles(projectDirectory, "*.xaml", SearchOption.AllDirectories);

foreach (var file in xamlFiles)
{
// Filter out files that belong to other projects (assumed by the presence of 'obj' or 'bin' folders)
if (file.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}"))
{
continue;
}

XDocument xamlDoc = null;
try
{
xamlDoc = XDocument.Load(file);
}
catch
{
continue;
}

XElement rootElement = xamlDoc.Root;
if (rootElement != null && rootElement.Name.LocalName != "Window" &&
rootElement.Name.LocalName != "Application" &&
rootElement.Name.LocalName != "ResourceDictionary")
{
XAttribute classAttribute = rootElement.Attribute(XName.Get("Class", "http://schemas.microsoft.com/winfx/2006/xaml"));
if (classAttribute != null)
{
xamlClasses.Add(file);
}
}
}

return xamlClasses;
}

string GetRelativePath(string basePath, string fullPath)
{
Uri baseUri = new Uri(basePath + Path.DirectorySeparatorChar);
Uri fullUri = new Uri(fullPath);
return baseUri.MakeRelativeUri(fullUri).ToString().Replace('/', Path.DirectorySeparatorChar);
}
string FindProjectRoot(string directory)
{
while (!string.IsNullOrEmpty(directory))
{
// Check for .csproj or .sln files in the current directory
if (Directory.GetFiles(directory, "*.csproj", SearchOption.TopDirectoryOnly).Any() ||
Directory.GetFiles(directory, "*.sln", SearchOption.TopDirectoryOnly).Any())
{
return directory; // Found the project root
}

// Move one level up in the directory hierarchy
directory = Directory.GetParent(directory)?.FullName;
}

return null; // No project root found
}
#>
};
}

Auto Generate Target

Visual Studio cannot automatically generate T4 files, to solve this problem we need to create a target to generate all T4 files when building the project.

1
2
3
4
5
6
7
8
9
<Target Name="TransformAllT4Templates" BeforeTargets="BeforeBuild">
<ItemGroup>
<!-- This picks up all T4 templates in the project -->
<T4Template Include="**\*.tt" />
</ItemGroup>

<!-- Check if TextTransform.exe exists before running -->
<Exec Command="if exist &quot;$(DevEnvDir)TextTransform.exe&quot; &quot;$(DevEnvDir)TextTransform.exe&quot; &quot;%(T4Template.FullPath)&quot;" Condition="Exists('$(DevEnvDir)TextTransform.exe')" />
</Target>

ConfigureTitleBar

you can use ConfigureTitleBar to automatically handle BackButton and PaneToggleButton

1
Initializ(...).ConfigureTitleBar(AppTitleBar);

DevWinUI

Complete Codes

1
2
3
4
5
6
INavigationServiceEx navigationService;
navigationService = new NavigationServiceEx();
navigationService.Initialize(NavView, NavFrame)
.ConfigureDefaultPage(typeof(HomeLandingPage))
.ConfigureSettingsPage(typeof(SettingsPage))
.ConfigureBreadcrumbBar(BreadCrumbNav, BreadcrumbPageMappings.PageDictionary);

MVVM Pattern

first register a INavigationServiceEx service:

1
services.AddSingleton<INavigationServiceEx, NavigationServiceEx>();

then:

1
2
3
4
5
6
7
8
9
public MainPage()
{
this.InitializeComponent();
var navService = App.GetService<INavigationServiceEx>() as NavigationServiceEx;

navService.Initialize(NavView, NavFrame)
.ConfigureDefaultPage(typeof(HomeLandingPage))
.ConfigureSettingsPage(typeof(SettingsPage));
}

INavigationAwareEx

you can use INavigationAwareEx in your ViewModel and you can access to OnNavigatedFrom and OnNavigatedTo methods.

1
2
3
4
5
6
7
8
9
10
11
12
public partial class myViewModel : INavigationAwareEx

public void OnNavigatedFrom()
{

}

public async void OnNavigatedTo(object parameter)
{

}

you should set DataContext, otherwise you cant use this interface.

1
2
3
4
5
6
7
8
public myViewModel ViewModel {get;}

public BlankPage()
{
ViewModel = App.GetService<myViewModel>();
DataContext = ViewModel;
this.InitializeComponent();
}

Notes

If you want to use Frame.GoBack to navigate back in the frame while maintaining the correct NavigationViewItem selection, you should use NavigationServiceEx.GoBack instead. This ensures that the NavigationView stays in sync with the current page.

Demo

you can run demo and see this feature.

DevWinUI

0%