if (File.Exists(jsonFilePath)) { string jsonContent = File.ReadAllText(jsonFilePath); using (JsonDocument jsonDoc = JsonDocument.Parse(jsonContent)) { JsonElement root = jsonDoc.RootElement; if (root.TryGetProperty("Groups", out JsonElement groups) && groups.ValueKind == JsonValueKind.Array) { foreach (JsonElement group in groups.EnumerateArray()) { if (group.TryGetProperty("Items", out JsonElement items) && items.ValueKind == JsonValueKind.Array) { ProcessItems(items, xamlPages); // Call the recursive method } } } } } else { #> //Json file not found. <# } #> }; } <#+ // Helper function to load XAML classes 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; }
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 } #>
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
<TargetName="TransformAllT4Templates"BeforeTargets="BeforeBuild"> <ItemGroup> <!-- This picks up all T4 templates in the project --> <T4TemplateInclude="**\*.tt" /> </ItemGroup>
<!-- Check if TextTransform.exe exists before running --> <ExecCommand="if exist "$(DevEnvDir)TextTransform.exe""$(DevEnvDir)TextTransform.exe""%(T4Template.FullPath)""Condition="Exists('$(DevEnvDir)TextTransform.exe')" /> </Target>
use `dev:BreadcrumbNavigator.PageTitle` and `dev:BreadcrumbNavigator.IsHeaderVisible` attached properties on your pages, for Title and Header visiblity.
```t4 <#@ 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$; publicpartialclassBreadcrumbPageMappings { publicstatic 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}" #> <# } }
voidExtractAttachedProperties(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 }
// 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()}}}}}," #> <# } stringGetXClassValue(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 } returnnull; }
stringGetAttachedPropertyValue(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;
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; }
stringGetRelativePath(string basePath, string fullPath) { Uri baseUri = new Uri(basePath + Path.DirectorySeparatorChar); Uri fullUri = new Uri(fullPath); return baseUri.MakeRelativeUri(fullUri).ToString().Replace('/', Path.DirectorySeparatorChar); } stringFindProjectRoot(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; }
returnnull; // No project root found } #> }; }
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
<TargetName="TransformAllT4Templates"BeforeTargets="BeforeBuild"> <ItemGroup> <!-- This picks up all T4 templates in the project --> <T4TemplateInclude="**\*.tt" /> </ItemGroup>
<!-- Check if TextTransform.exe exists before running --> <ExecCommand="if exist "$(DevEnvDir)TextTransform.exe""$(DevEnvDir)TextTransform.exe""%(T4Template.FullPath)""Condition="Exists('$(DevEnvDir)TextTransform.exe')" /> </Target>
ConfigureSectionPage
if you want to click on a group and see list of items, you can use SectionPage:
step2: add `"UsexUid": true` for every item in json file.
step3: add some resources in your resw files. for example:
|Key|Value| |-|-| |Nav_HomeTitle|Home|
step4: copy and paste Key in your json file for `Title` or `subtitle`...
`"Title": "Nav_HomeTitle"`
In the last step, you need to create the json file: create a new `json` file (`AppData.json`) in a folder called `DataModel`:
{% note warning %} - Set BuildAction for `AppData.json` to `Content` andif you are in a Unpackaged app, also set `CopyToOutput` to `Always` {% endnote %}
{% note info %} To see details and descriptions of Json's properties, refer to <ins>**[this](https://ghost1372.github.io/DevWinUI/jsonFile)**</ins> page {% endnote %} {% note warning %} If you have items that use the same Page, you should set the `parameter` property in the json file to avoid navigation errors. "UniqueId": "DevWinUI.DemoApp.Pages.myPage", "Title": "Movie" "Parameter": "Movie" --- "UniqueId": "DevWinUI.DemoApp.Pages.myPage", "Title": "Series" "Parameter": "Series" {% endnote %} this is a json file content: ```json { "Groups": [ { "UniqueId": "Features", "Title": "Features pages", "IsSpecialSection": false, "Items": [ { "UniqueId": "DevWinUI.DemoApp.Pages.ApplicationDataContainerPage", "Title": "ApplicationDataContainer", "SecondaryTitle": "Test SecondaryTitle", "Subtitle": "you can use ApplicationDataContainerHelper for saving and loading application settings.", "ImagePath": "ms-appx:///Assets/Modules/PT.png", "Description": "test description", "IsUpdated": true, "IncludedInBuild": true, "Links": [ { "Title": "ApplicationDataContainerPage", "Uri": "https://DevWinUI.github.io/DevWinUI/helpers/applicationDataContainerHelper/" } ], "Extra": [ "AppBarToggleButton", "AppBarSeparator", "CommandBar" ] }, { "UniqueId": "DevWinUI.DemoApp.Pages.AppNotificationPage", "Title": "App Notification", "SecondaryTitle": "Test SecondaryTitle", "Subtitle": "you can use AppNotificationPage for Sending Toast Notification.", "ImagePath": "ms-appx:///Assets/Modules/PT.png", "IncludedInBuild": true, "Links": [ { "Title": "AppNotificationPage", "Uri": "https://DevWinUI.github.io/DevWinUI/helpers/appNotification/" } ] }, ] }, { "UniqueId": "Settings", "Title": "Settings pages", "SecondaryTitle": "Test SecondaryTitle", "Items": [ { "UniqueId": "DevWinUI.DemoApp.Pages.OobePage", "Title": "Oobe Page", "ApiNamespace": "DemoApp", "SecondaryTitle": "Test SecondaryTitle", "Subtitle": "Settings Page with a Hero Image", "ImagePath": "ms-appx:///Assets/Modules/PT.png", "IsUpdated": true, "IncludedInBuild": true, } ] } ] }
UniqueId this is your pages full address, for example: DevWinUI.DemoApp.Pages.ApplicationDataContainerPage
ApiNamespace this is your apps namespace, for example: DevWinUI.DemoApp
If you want to use Frame.GoBack to navigate back in the frame while maintaining the correct NavigationViewItem selection, you should use JsonNavigationService.GoBack instead. This ensures that the NavigationView stays in sync with the current page.