From 70db61c188c3ac9869a685c8e13dd8b1d3293aaf Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Mon, 19 Oct 2026 16:53:12 +0200
Subject: [PATCH 1/2] Feature: Align admin tooltips
---
.../PermissionToolTipHelper.cs | 91 +++++++++++++++++++
.../Resources/Styles/RectangleStyles.xaml | 2 +-
.../Resources/Styles/ToolTipStyles.xaml | 5 +-
.../Views/DiscoveryProtocolView.xaml | 3 +
Source/NETworkManager/Views/FirewallView.xaml | 17 +++-
.../Views/HostsFileEditorView.xaml | 29 +++++-
.../Views/NeighborTableView.xaml | 14 ++-
.../Views/NetworkInterfaceView.xaml | 25 ++++-
Website/docs/changelog/next-release.md | 5 +
9 files changed, 175 insertions(+), 16 deletions(-)
create mode 100644 Source/NETworkManager.Utilities.WPF/PermissionToolTipHelper.cs
diff --git a/Source/NETworkManager.Utilities.WPF/PermissionToolTipHelper.cs b/Source/NETworkManager.Utilities.WPF/PermissionToolTipHelper.cs
new file mode 100644
index 0000000000..f6f679a287
--- /dev/null
+++ b/Source/NETworkManager.Utilities.WPF/PermissionToolTipHelper.cs
@@ -0,0 +1,91 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace NETworkManager.Utilities.WPF;
+
+///
+/// Attached-property helper to show a hint (e.g. "requires elevated rights") as tooltip on a control,
+/// which is disabled because the application is missing a permission (e.g. not running as administrator).
+/// The tooltip is also shown while the control is disabled and only if
+/// is . An existing tooltip of the control is never overwritten.
+///
+public static class PermissionToolTipHelper
+{
+ ///
+ /// Resource key of the tooltip style (limits the width and wraps long text).
+ /// Defined in "Resources/Styles/ToolTipStyles.xaml" of the application.
+ ///
+ private const string ToolTipStyleKey = "WrapToolTip";
+
+ ///
+ /// Text of the tooltip, which is shown if the permission is missing.
+ ///
+ public static readonly DependencyProperty TextProperty =
+ DependencyProperty.RegisterAttached(
+ "Text",
+ typeof(string),
+ typeof(PermissionToolTipHelper),
+ new PropertyMetadata(null, OnChanged));
+
+ public static void SetText(FrameworkElement element, string value) =>
+ element.SetValue(TextProperty, value);
+
+ public static string GetText(FrameworkElement element) =>
+ (string)element.GetValue(TextProperty);
+
+ ///
+ /// Indicates whether the permission is available (e.g. running as administrator).
+ /// Defaults to , so no tooltip is shown until the binding is resolved.
+ ///
+ public static readonly DependencyProperty HasPermissionProperty =
+ DependencyProperty.RegisterAttached(
+ "HasPermission",
+ typeof(bool),
+ typeof(PermissionToolTipHelper),
+ new PropertyMetadata(true, OnChanged));
+
+ public static void SetHasPermission(FrameworkElement element, bool value) =>
+ element.SetValue(HasPermissionProperty, value);
+
+ public static bool GetHasPermission(FrameworkElement element) =>
+ (bool)element.GetValue(HasPermissionProperty);
+
+ ///
+ /// Indicates whether the tooltip was set by this helper (to only clear our own tooltip).
+ ///
+ private static readonly DependencyProperty IsAppliedProperty =
+ DependencyProperty.RegisterAttached(
+ "IsApplied",
+ typeof(bool),
+ typeof(PermissionToolTipHelper),
+ new PropertyMetadata(false));
+
+ private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is not FrameworkElement element)
+ return;
+
+ var text = GetText(element);
+ var isApplied = (bool)element.GetValue(IsAppliedProperty);
+
+ if (!GetHasPermission(element) && !string.IsNullOrEmpty(text))
+ {
+ // Don't overwrite a tooltip that was set on the control itself
+ if (!isApplied && element.ToolTip != null)
+ return;
+
+ var toolTip = new ToolTip { Content = text };
+ toolTip.SetResourceReference(FrameworkElement.StyleProperty, ToolTipStyleKey);
+
+ element.ToolTip = toolTip;
+ ToolTipService.SetShowOnDisabled(element, true);
+ element.SetValue(IsAppliedProperty, true);
+ }
+ else if (isApplied)
+ {
+ element.ClearValue(FrameworkElement.ToolTipProperty);
+ element.ClearValue(ToolTipService.ShowOnDisabledProperty);
+ element.SetValue(IsAppliedProperty, false);
+ }
+ }
+}
diff --git a/Source/NETworkManager/Resources/Styles/RectangleStyles.xaml b/Source/NETworkManager/Resources/Styles/RectangleStyles.xaml
index 3a2365c7de..475520e48b 100644
--- a/Source/NETworkManager/Resources/Styles/RectangleStyles.xaml
+++ b/Source/NETworkManager/Resources/Styles/RectangleStyles.xaml
@@ -66,7 +66,7 @@
+
+
diff --git a/Source/NETworkManager/Views/DiscoveryProtocolView.xaml b/Source/NETworkManager/Views/DiscoveryProtocolView.xaml
index 9c49ffc957..692cc16a9a 100644
--- a/Source/NETworkManager/Views/DiscoveryProtocolView.xaml
+++ b/Source/NETworkManager/Views/DiscoveryProtocolView.xaml
@@ -8,6 +8,7 @@
xmlns:converters="clr-namespace:NETworkManager.Converters;assembly=NETworkManager.Converters"
xmlns:viewModels="clr-namespace:NETworkManager.ViewModels"
xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization"
+ xmlns:wpfHelpers="clr-namespace:NETworkManager.Utilities.WPF;assembly=NETworkManager.Utilities.WPF"
xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings"
mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:DiscoveryProtocolViewModel}">
@@ -65,6 +66,8 @@