Skip to content

Commit 0bf2a59

Browse files
authored
Feature: Align admin tooltips (#3626)
* Feature: Align admin tooltips * Update next-release.md
1 parent 4e7b902 commit 0bf2a59

9 files changed

Lines changed: 175 additions & 16 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System.Windows;
2+
using System.Windows.Controls;
3+
4+
namespace NETworkManager.Utilities.WPF;
5+
6+
/// <summary>
7+
/// Attached-property helper to show a hint (e.g. "requires elevated rights") as tooltip on a control,
8+
/// which is disabled because the application is missing a permission (e.g. not running as administrator).
9+
/// The tooltip is also shown while the control is disabled and only if <see cref="HasPermissionProperty"/>
10+
/// is <see langword="false"/>. An existing tooltip of the control is never overwritten.
11+
/// </summary>
12+
public static class PermissionToolTipHelper
13+
{
14+
/// <summary>
15+
/// Resource key of the tooltip style (limits the width and wraps long text).
16+
/// Defined in "Resources/Styles/ToolTipStyles.xaml" of the application.
17+
/// </summary>
18+
private const string ToolTipStyleKey = "WrapToolTip";
19+
20+
/// <summary>
21+
/// Text of the tooltip, which is shown if the permission is missing.
22+
/// </summary>
23+
public static readonly DependencyProperty TextProperty =
24+
DependencyProperty.RegisterAttached(
25+
"Text",
26+
typeof(string),
27+
typeof(PermissionToolTipHelper),
28+
new PropertyMetadata(null, OnChanged));
29+
30+
public static void SetText(FrameworkElement element, string value) =>
31+
element.SetValue(TextProperty, value);
32+
33+
public static string GetText(FrameworkElement element) =>
34+
(string)element.GetValue(TextProperty);
35+
36+
/// <summary>
37+
/// Indicates whether the permission is available (e.g. running as administrator).
38+
/// Defaults to <see langword="true"/>, so no tooltip is shown until the binding is resolved.
39+
/// </summary>
40+
public static readonly DependencyProperty HasPermissionProperty =
41+
DependencyProperty.RegisterAttached(
42+
"HasPermission",
43+
typeof(bool),
44+
typeof(PermissionToolTipHelper),
45+
new PropertyMetadata(true, OnChanged));
46+
47+
public static void SetHasPermission(FrameworkElement element, bool value) =>
48+
element.SetValue(HasPermissionProperty, value);
49+
50+
public static bool GetHasPermission(FrameworkElement element) =>
51+
(bool)element.GetValue(HasPermissionProperty);
52+
53+
/// <summary>
54+
/// Indicates whether the tooltip was set by this helper (to only clear our own tooltip).
55+
/// </summary>
56+
private static readonly DependencyProperty IsAppliedProperty =
57+
DependencyProperty.RegisterAttached(
58+
"IsApplied",
59+
typeof(bool),
60+
typeof(PermissionToolTipHelper),
61+
new PropertyMetadata(false));
62+
63+
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
64+
{
65+
if (d is not FrameworkElement element)
66+
return;
67+
68+
var text = GetText(element);
69+
var isApplied = (bool)element.GetValue(IsAppliedProperty);
70+
71+
if (!GetHasPermission(element) && !string.IsNullOrEmpty(text))
72+
{
73+
// Don't overwrite a tooltip that was set on the control itself
74+
if (!isApplied && element.ToolTip != null)
75+
return;
76+
77+
var toolTip = new ToolTip { Content = text };
78+
toolTip.SetResourceReference(FrameworkElement.StyleProperty, ToolTipStyleKey);
79+
80+
element.ToolTip = toolTip;
81+
ToolTipService.SetShowOnDisabled(element, true);
82+
element.SetValue(IsAppliedProperty, true);
83+
}
84+
else if (isApplied)
85+
{
86+
element.ClearValue(FrameworkElement.ToolTipProperty);
87+
element.ClearValue(ToolTipService.ShowOnDisabledProperty);
88+
element.SetValue(IsAppliedProperty, false);
89+
}
90+
}
91+
}

‎Source/NETworkManager/Resources/Styles/RectangleStyles.xaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666

6767
<Style x:Key="ErrorImageRectangle" TargetType="{x:Type Rectangle}">
6868
<Style.Resources>
69-
<Style TargetType="{x:Type ToolTip}" BasedOn="{StaticResource DefaultToolTip}" />
69+
<Style TargetType="{x:Type ToolTip}" BasedOn="{StaticResource ErrorToolTip}" />
7070
</Style.Resources>
7171
<Setter Property="Fill" Value="#b95353" />
7272
<Setter Property="OpacityMask">

‎Source/NETworkManager/Resources/Styles/ToolTipStyles.xaml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
<Style TargetType="{x:Type TypeName=ToolTip}" BasedOn="{StaticResource ResourceKey=DefaultToolTip}" />
1212

13-
<Style x:Key="HelpToolTip" TargetType="{x:Type TypeName=ToolTip}" BasedOn="{StaticResource ResourceKey=DefaultToolTip}">
13+
<!-- Generic tooltip for (long) text, limits the width and wraps the text -->
14+
<Style x:Key="WrapToolTip" TargetType="{x:Type TypeName=ToolTip}" BasedOn="{StaticResource ResourceKey=DefaultToolTip}">
1415
<Setter Property="Template">
1516
<Setter.Value>
1617
<ControlTemplate TargetType="{x:Type TypeName=ToolTip}">
@@ -53,6 +54,8 @@
5354
</Setter>
5455
</Style>
5556

57+
<Style x:Key="HelpToolTip" TargetType="{x:Type TypeName=ToolTip}" BasedOn="{StaticResource ResourceKey=WrapToolTip}" />
58+
5659
<Style x:Key="WarnToolTip" TargetType="{x:Type TypeName=ToolTip}" BasedOn="{StaticResource ResourceKey=HelpToolTip}" />
5760

5861
<Style x:Key="ErrorToolTip" TargetType="{x:Type TypeName=ToolTip}" BasedOn="{StaticResource ResourceKey=HelpToolTip}" />

‎Source/NETworkManager/Views/DiscoveryProtocolView.xaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
xmlns:converters="clr-namespace:NETworkManager.Converters;assembly=NETworkManager.Converters"
99
xmlns:viewModels="clr-namespace:NETworkManager.ViewModels"
1010
xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization"
11+
xmlns:wpfHelpers="clr-namespace:NETworkManager.Utilities.WPF;assembly=NETworkManager.Utilities.WPF"
1112
xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings"
1213
mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:DiscoveryProtocolViewModel}">
1314
<UserControl.Resources>
@@ -65,6 +66,8 @@
6566
<ComboBox Grid.Column="6" ItemsSource="{Binding Durations}"
6667
SelectedItem="{Binding SelectedDuration}" />
6768
<Button Grid.Column="8" Command="{Binding CaptureCommand}"
69+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.NetworkPacketsCaptureAdminMessage}"
70+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}"
6871
Style="{StaticResource ImageWithTextButton}">
6972
<Button.Content>
7073
<Grid>

‎Source/NETworkManager/Views/FirewallView.xaml‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@
9999
<ContextMenu x:Key="RowContextMenu" Opened="ContextMenu_Opened" MinWidth="150">
100100
<MenuItem Header="{x:Static localization:Strings.EnableRule}"
101101
Command="{Binding Path=EnableRuleCommand}"
102+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.FirewallAdminMessage}"
103+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}"
102104
Visibility="{Binding Path=SelectedResult.IsEnabled, Converter={StaticResource BooleanReverseToVisibilityCollapsedConverter}}">
103105
<MenuItem.Icon>
104106
<Rectangle Width="16" Height="16" Fill="{DynamicResource MahApps.Brushes.Gray3}">
@@ -110,6 +112,8 @@
110112
</MenuItem>
111113
<MenuItem Header="{x:Static localization:Strings.DisableRule}"
112114
Command="{Binding Path=DisableRuleCommand}"
115+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.FirewallAdminMessage}"
116+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}"
113117
Visibility="{Binding Path=SelectedResult.IsEnabled, Converter={StaticResource BooleanToVisibilityCollapsedConverter}}">
114118
<MenuItem.Icon>
115119
<Rectangle Width="16" Height="16" Fill="{DynamicResource MahApps.Brushes.Gray3}">
@@ -120,7 +124,9 @@
120124
</MenuItem.Icon>
121125
</MenuItem>
122126
<MenuItem Header="{x:Static localization:Strings.EditRuleDots}"
123-
Command="{Binding Path=EditRuleCommand}">
127+
Command="{Binding Path=EditRuleCommand}"
128+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.FirewallAdminMessage}"
129+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}">
124130
<MenuItem.Icon>
125131
<Rectangle Width="16" Height="16" Fill="{DynamicResource MahApps.Brushes.Gray3}">
126132
<Rectangle.OpacityMask>
@@ -130,7 +136,9 @@
130136
</MenuItem.Icon>
131137
</MenuItem>
132138
<MenuItem Header="{x:Static localization:Strings.DeleteRule}"
133-
Command="{Binding Path=DeleteRuleCommand}">
139+
Command="{Binding Path=DeleteRuleCommand}"
140+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.FirewallAdminMessage}"
141+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}">
134142
<MenuItem.Icon>
135143
<Rectangle Width="16" Height="16" Fill="{DynamicResource MahApps.Brushes.Gray3}">
136144
<Rectangle.OpacityMask>
@@ -529,7 +537,10 @@
529537
Orientation="Horizontal"
530538
VerticalAlignment="Center"
531539
HorizontalAlignment="Right">
532-
<Button Command="{Binding Path=AddRuleCommand}" Style="{StaticResource ImageWithTextButton}">
540+
<Button Command="{Binding Path=AddRuleCommand}"
541+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.FirewallAdminMessage}"
542+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}"
543+
Style="{StaticResource ImageWithTextButton}">
533544
<Grid>
534545
<Grid.ColumnDefinitions>
535546
<ColumnDefinition Width="Auto" />

‎Source/NETworkManager/Views/HostsFileEditorView.xaml‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@
8989
<ContextMenu x:Key="RowContextMenu" Opened="ContextMenu_Opened" MinWidth="150">
9090
<MenuItem Header="{x:Static Member=localization:Strings.EnableEntry}"
9191
Command="{Binding Path=EnableEntryCommand}"
92+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.HostsFileEditorAdminMessage}"
93+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}"
9294
Visibility="{Binding Path=SelectedResult.IsEnabled, Converter={StaticResource ResourceKey=BooleanReverseToVisibilityCollapsedConverter}}">
9395
<MenuItem.Icon>
9496
<Rectangle Width="16" Height="16"
@@ -102,6 +104,8 @@
102104
</MenuItem>
103105
<MenuItem Header="{x:Static Member=localization:Strings.DisableEntry}"
104106
Command="{Binding Path=DisableEntryCommand}"
107+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.HostsFileEditorAdminMessage}"
108+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}"
105109
Visibility="{Binding Path=SelectedResult.IsEnabled, Converter={StaticResource ResourceKey=BooleanToVisibilityCollapsedConverter}}">
106110
<MenuItem.Icon>
107111
<Rectangle Width="16" Height="16"
@@ -114,7 +118,9 @@
114118
</MenuItem.Icon>
115119
</MenuItem>
116120
<MenuItem Header="{x:Static Member=localization:Strings.EditEntryDots}"
117-
Command="{Binding Path=EditEntryCommand}">
121+
Command="{Binding Path=EditEntryCommand}"
122+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.HostsFileEditorAdminMessage}"
123+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}">
118124
<MenuItem.Icon>
119125
<Rectangle Width="16" Height="16"
120126
Fill="{DynamicResource ResourceKey=MahApps.Brushes.Gray3}">
@@ -125,7 +131,9 @@
125131
</MenuItem.Icon>
126132
</MenuItem>
127133
<MenuItem Header="{x:Static Member=localization:Strings.DeleteEntry}"
128-
Command="{Binding Path=DeleteEntryCommand}">
134+
Command="{Binding Path=DeleteEntryCommand}"
135+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.HostsFileEditorAdminMessage}"
136+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}">
129137
<MenuItem.Icon>
130138
<Rectangle Width="16" Height="16"
131139
Fill="{DynamicResource ResourceKey=MahApps.Brushes.Gray3}">
@@ -170,6 +178,8 @@
170178
<ContextMenu x:Key="MultiRowContextMenu" Opened="ContextMenu_Opened" MinWidth="150">
171179
<MenuItem Header="{x:Static Member=localization:Strings.Enable}"
172180
Command="{Binding Path=EnableEntryCommand}"
181+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.HostsFileEditorAdminMessage}"
182+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}"
173183
Visibility="{Binding Path=IsAnySelectedEntryDisabled, Converter={StaticResource ResourceKey=BooleanToVisibilityCollapsedConverter}}">
174184
<MenuItem.Icon>
175185
<Rectangle Width="16" Height="16"
@@ -183,6 +193,8 @@
183193
</MenuItem>
184194
<MenuItem Header="{x:Static Member=localization:Strings.Disable}"
185195
Command="{Binding Path=DisableEntryCommand}"
196+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.HostsFileEditorAdminMessage}"
197+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}"
186198
Visibility="{Binding Path=IsAnySelectedEntryEnabled, Converter={StaticResource ResourceKey=BooleanToVisibilityCollapsedConverter}}">
187199
<MenuItem.Icon>
188200
<Rectangle Width="16" Height="16"
@@ -195,7 +207,9 @@
195207
</MenuItem.Icon>
196208
</MenuItem>
197209
<MenuItem Header="{x:Static Member=localization:Strings.EditEntriesDots}"
198-
Command="{Binding Path=EditEntryCommand}">
210+
Command="{Binding Path=EditEntryCommand}"
211+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.HostsFileEditorAdminMessage}"
212+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}">
199213
<MenuItem.Icon>
200214
<Rectangle Width="16" Height="16"
201215
Fill="{DynamicResource ResourceKey=MahApps.Brushes.Gray3}">
@@ -206,7 +220,9 @@
206220
</MenuItem.Icon>
207221
</MenuItem>
208222
<MenuItem Header="{x:Static Member=localization:Strings.DeleteEntries}"
209-
Command="{Binding Path=DeleteEntryCommand}">
223+
Command="{Binding Path=DeleteEntryCommand}"
224+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.HostsFileEditorAdminMessage}"
225+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}">
210226
<MenuItem.Icon>
211227
<Rectangle Width="16" Height="16"
212228
Fill="{DynamicResource ResourceKey=MahApps.Brushes.Gray3}">
@@ -316,7 +332,10 @@
316332
Orientation="Horizontal"
317333
VerticalAlignment="Center"
318334
HorizontalAlignment="Right">
319-
<Button Command="{Binding AddEntryCommand}" Style="{StaticResource ImageWithTextButton}">
335+
<Button Command="{Binding AddEntryCommand}"
336+
wpfHelpers:PermissionToolTipHelper.Text="{x:Static localization:Strings.HostsFileEditorAdminMessage}"
337+
wpfHelpers:PermissionToolTipHelper.HasPermission="{Binding Source={x:Static settings:ConfigurationManager.Current}, Path=IsAdmin}"
338+
Style="{StaticResource ImageWithTextButton}">
320339
<Grid>
321340
<Grid.ColumnDefinitions>
322341
<ColumnDefinition Width="Auto" />

0 commit comments

Comments
 (0)