This adds a simple hierarchical debug view window. A simple recursive visitor triggers every 2 seconds and updates the string contents of the debug window. I added this code to a net9.0-maccatalyst app. To get the windowing working you have to do https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/window?view=net-maui-9.0#ipados-and-macos-configuration
Created
March 20, 2025 00:55
-
-
Save bytesandwich/a5eed3d5eceb7b04af328ea27a68642c to your computer and use it in GitHub Desktop.
.NET Maui simple hierarchical debug view window
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Timers; | |
public partial class App : Application | |
{ | |
private Label? debugLabel; | |
private System.Timers.Timer? debugUpdateTimer; | |
private Window? debugWindow; | |
public App() | |
{ | |
InitializeComponent(); | |
MainPage = new AppShell(); | |
StartDebugUpdateTimer(); | |
} | |
private void StartDebugUpdateTimer() | |
{ | |
// Set up the timer to update every 2 seconds | |
debugUpdateTimer = new(2000); // 2000ms = 2 seconds | |
debugUpdateTimer.Elapsed += (sender, args) => | |
{ | |
// Get the UI hierarchy string | |
if (debugWindow == null) | |
{ | |
MainThread.BeginInvokeOnMainThread(() => | |
{ | |
// Create a Label to display the debug text | |
debugLabel = new Label | |
{ | |
Text = "Loading debug info...", | |
FontSize = 14, | |
LineBreakMode = LineBreakMode.WordWrap, | |
Padding = new Thickness(10) | |
}; | |
// Create a ScrollView to contain the Label | |
var scrollView = new ScrollView | |
{ | |
Content = debugLabel | |
}; | |
debugWindow = new Window( | |
new ContentPage | |
{ | |
Title = "Debug UI Hierarchy", | |
Content = scrollView | |
}) | |
{ | |
Width = 700, | |
Height = 500, | |
X = 100, | |
Y = 100 | |
}; | |
Application.Current?.OpenWindow(debugWindow); | |
Application.Current?.ActivateWindow(debugWindow); | |
}); | |
return; | |
} | |
string debugText = DebugLogger.GetVisualTreeAsString(); | |
// Update the Label on the UI thread | |
MainThread.BeginInvokeOnMainThread(() => | |
{ | |
debugLabel.Text = debugText; | |
}); | |
}; | |
// Start the timer | |
debugUpdateTimer.Start(); | |
} | |
protected override void OnStart() | |
{ | |
base.OnStart(); | |
// Ensure the timer starts when the app starts | |
debugUpdateTimer?.Start(); | |
} | |
protected override void OnSleep() | |
{ | |
base.OnSleep(); | |
// Stop the timer when the app goes to sleep | |
debugUpdateTimer?.Stop(); | |
} | |
protected override void OnResume() | |
{ | |
base.OnResume(); | |
// Resume the timer when the app resumes | |
debugUpdateTimer?.Start(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Text; | |
using Microsoft.Maui; | |
using Microsoft.Maui.Controls; | |
public static class DebugLogger | |
{ | |
public static string GetVisualTreeAsString() | |
{ | |
if (Application.Current == null) | |
{ | |
return "No current application instance found."; | |
} | |
var stringBuilder = new StringBuilder(); | |
stringBuilder.AppendLine("========== UI Hierarchy =========="); | |
foreach (var window in Application.Current.Windows) | |
{ | |
stringBuilder.AppendLine($"Window: {window.Title}"); | |
if (window.Page != null) | |
{ | |
AppendElementHierarchy(stringBuilder, window.Page, 1); | |
} | |
} | |
stringBuilder.AppendLine("=================================="); | |
return stringBuilder.ToString(); | |
} | |
private static void AppendElementHierarchy(StringBuilder stringBuilder, IView element, int indentLevel) | |
{ | |
string indent = new string(' ', indentLevel * 2); | |
stringBuilder.AppendLine($"{indent}- {element.GetType().Name}"); | |
if (element is Layout layout) // Handles StackLayout, Grid, FlexLayout, etc. | |
{ | |
foreach (var child in layout.Children) | |
{ | |
AppendElementHierarchy(stringBuilder, child, indentLevel + 1); | |
} | |
} | |
else if (element is ContentView contentView && contentView.Content != null) // Handles ContentView | |
{ | |
AppendElementHierarchy(stringBuilder, contentView.Content, indentLevel + 1); | |
} | |
else if (element is ScrollView scrollView && scrollView.Content != null) // Handles ScrollView | |
{ | |
AppendElementHierarchy(stringBuilder, scrollView.Content, indentLevel + 1); | |
} | |
else if (element is Frame frame && frame.Content != null) // Handles Frame | |
{ | |
AppendElementHierarchy(stringBuilder, frame.Content, indentLevel + 1); | |
} | |
else if (element is Border border && border.Content != null) // Handles Border | |
{ | |
AppendElementHierarchy(stringBuilder, border.Content, indentLevel + 1); | |
} | |
else if (element is Shell shell) // Handles Shell Navigation | |
{ | |
foreach (var item in shell.Items) | |
{ | |
AppendShellItemHierarchy(stringBuilder, item, indentLevel + 1); | |
} | |
} | |
} | |
private static void AppendShellItemHierarchy(StringBuilder stringBuilder, ShellItem item, int indentLevel) | |
{ | |
string indent = new string(' ', indentLevel * 2); | |
stringBuilder.AppendLine($"{indent}- ShellItem: {item.Title}"); | |
foreach (var section in item.Items) | |
{ | |
stringBuilder.AppendLine($"{indent} * Section: {section.Title}"); | |
foreach (var content in section.Items) | |
{ | |
if (content is ShellContent shellContent && shellContent.Content is Page page) | |
{ | |
AppendElementHierarchy(stringBuilder, page, indentLevel + 2); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment