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 `wuc:BreadcrumbNavigator.PageTitle` and `wuc: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> | // -----------------------------------------------------------------------------------------------------
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>
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 %} {% note info %} When we add page information (key, page) in the dictionary, the key is created as follows (parameter can be null) UniqueId + Parameter {% 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
for Navigating to a Page we used UniqueId.
Methods and Properties
there is some methods and properties that you can use them:
1 2 3
var jsonDataSource = jsonNavigationViewService.DataSource;