Skip to content

Commit 73f9123

Browse files
committed
Feature: Profile expander state
1 parent c317d77 commit 73f9123

File tree

6 files changed

+217
-4
lines changed

6 files changed

+217
-4
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.ComponentModel;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
5+
namespace NETworkManager.Controls
6+
{
7+
public class GroupExpander : Expander
8+
{
9+
public static readonly DependencyProperty StateStoreProperty =
10+
DependencyProperty.Register(
11+
"StateStore",
12+
typeof(GroupExpanderStateStore),
13+
typeof(GroupExpander),
14+
new PropertyMetadata(null, OnStateStoreChanged));
15+
16+
public GroupExpanderStateStore StateStore
17+
{
18+
get => (GroupExpanderStateStore)GetValue(StateStoreProperty);
19+
set => SetValue(StateStoreProperty, value);
20+
}
21+
22+
public static readonly DependencyProperty GroupNameProperty =
23+
DependencyProperty.Register(
24+
nameof(GroupName),
25+
typeof(string),
26+
typeof(GroupExpander),
27+
new PropertyMetadata(null, OnGroupNameChanged));
28+
29+
public string GroupName
30+
{
31+
get => (string)GetValue(GroupNameProperty);
32+
set => SetValue(GroupNameProperty, value);
33+
}
34+
35+
static GroupExpander()
36+
{
37+
IsExpandedProperty.OverrideMetadata(
38+
typeof(GroupExpander),
39+
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsExpandedChanged));
40+
}
41+
42+
private static void OnIsExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
43+
{
44+
var expander = (GroupExpander)d;
45+
46+
if (expander.StateStore != null && expander.GroupName != null)
47+
expander.StateStore[expander.GroupName] = (bool)e.NewValue;
48+
}
49+
50+
private static void OnStateStoreChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
51+
{
52+
var expander = (GroupExpander)d;
53+
54+
if (e.OldValue is GroupExpanderStateStore oldStore)
55+
oldStore.PropertyChanged -= expander.StateStore_PropertyChanged;
56+
57+
if (e.NewValue is GroupExpanderStateStore newStore)
58+
newStore.PropertyChanged += expander.StateStore_PropertyChanged;
59+
60+
expander.UpdateIsExpanded();
61+
}
62+
63+
private void StateStore_PropertyChanged(object sender, PropertyChangedEventArgs e)
64+
{
65+
if (e.PropertyName == $"Item[{GroupName}]")
66+
UpdateIsExpanded();
67+
}
68+
69+
private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
70+
{
71+
var expander = (GroupExpander)d;
72+
73+
expander.UpdateIsExpanded();
74+
}
75+
76+
private void UpdateIsExpanded()
77+
{
78+
if (StateStore == null || GroupName == null)
79+
return;
80+
81+
// Prevent recursive updates
82+
if (IsExpanded == StateStore[GroupName])
83+
return;
84+
85+
IsExpanded = StateStore[GroupName];
86+
}
87+
}
88+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System.Collections.Generic;
2+
using System.ComponentModel;
3+
using System.Diagnostics;
4+
5+
namespace NETworkManager.Controls
6+
{
7+
public class GroupExpanderStateStore : INotifyPropertyChanged
8+
{
9+
public event PropertyChangedEventHandler PropertyChanged;
10+
11+
protected void OnPropertyChanged(string propertyName) =>
12+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
13+
14+
/// <summary>
15+
/// Stores the expansion state of each group by its name.
16+
/// </summary>
17+
private readonly Dictionary<string, bool> _states = [];
18+
19+
/// <summary>
20+
/// The indexer to get or set the expansion state of a group by its name.
21+
/// </summary>
22+
/// <param name="groupName">Name of the group.</param>
23+
/// <returns>True if expanded, false if collapsed.</returns>
24+
public bool this[string groupName]
25+
{
26+
get
27+
{
28+
// Default to expanded if not set
29+
if (!_states.TryGetValue(groupName, out var val))
30+
_states[groupName] = val = true;
31+
32+
return val;
33+
}
34+
set
35+
{
36+
if (_states.TryGetValue(groupName, out var existing) && existing == value)
37+
return;
38+
39+
Debug.WriteLine("GroupExpanderStateStore: Setting state of '{0}' to {1}", groupName, value);
40+
41+
_states[groupName] = value;
42+
OnPropertyChanged($"Item[{groupName}]");
43+
}
44+
}
45+
}
46+
}

Source/NETworkManager.Localization/Resources/Strings.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Source/NETworkManager.Localization/Resources/Strings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3987,4 +3987,10 @@ Right-click for more options.</value>
39873987
<data name="AddGroupDots" xml:space="preserve">
39883988
<value>Add group...</value>
39893989
</data>
3990+
<data name="ExpandAll" xml:space="preserve">
3991+
<value>Expand all</value>
3992+
</data>
3993+
<data name="CollapseAll" xml:space="preserve">
3994+
<value>Collapse all</value>
3995+
</data>
39903996
</root>

Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using MahApps.Metro.Controls;
22
using MahApps.Metro.Controls.Dialogs;
3+
using NETworkManager.Controls;
34
using NETworkManager.Localization.Resources;
45
using NETworkManager.Models;
56
using NETworkManager.Models.Network;
@@ -279,6 +280,9 @@ public bool IsProfileFilterSet
279280
}
280281
}
281282

283+
private readonly GroupExpanderStateStore _groupExpanderStateStore = new();
284+
public GroupExpanderStateStore GroupExpanderStateStore => _groupExpanderStateStore;
285+
282286
private bool _canProfileWidthChange = true;
283287
private double _tempProfileWidth;
284288

@@ -506,6 +510,19 @@ private void ClearProfileFilterAction()
506510
ProfileFilterIsOpen = false;
507511
}
508512

513+
public ICommand ExpandAllProfileGroupsCommand => new RelayCommand(_ => ExpandAllProfileGroupsAction());
514+
515+
private void ExpandAllProfileGroupsAction()
516+
{
517+
SetIsExpandedForAllProfileGroups(true);
518+
}
519+
520+
public ICommand CollapseAllProfileGroupsCommand => new RelayCommand(_ => CollapseAllProfileGroupsAction());
521+
522+
private void CollapseAllProfileGroupsAction()
523+
{
524+
SetIsExpandedForAllProfileGroups(false);
525+
}
509526
#endregion
510527

511528
#region Methods
@@ -643,6 +660,12 @@ private void AddHostToHistory(string host)
643660
list.ForEach(x => SettingsManager.Current.PingMonitor_HostHistory.Add(x));
644661
}
645662

663+
private void SetIsExpandedForAllProfileGroups(bool isExpanded)
664+
{
665+
foreach (var group in Profiles.Groups.Cast<CollectionViewGroup>())
666+
GroupExpanderStateStore[group.Name.ToString()] = isExpanded;
667+
}
668+
646669
private void ResizeProfile(bool dueToChangedSize)
647670
{
648671
_canProfileWidthChange = false;

Source/NETworkManager/Views/PingMonitorHostView.xaml

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
xmlns:mahAppsControls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
99
xmlns:validators="clr-namespace:NETworkManager.Validators;assembly=NETworkManager.Validators"
1010
xmlns:converters="clr-namespace:NETworkManager.Converters;assembly=NETworkManager.Converters"
11+
xmlns:controls="clr-namespace:NETworkManager.Controls;assembly=NETworkManager.Controls"
1112
xmlns:viewModels="clr-namespace:NETworkManager.ViewModels"
1213
xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization"
1314
xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings"
@@ -569,17 +570,48 @@
569570
<Setter Property="Template">
570571
<Setter.Value>
571572
<ControlTemplate>
572-
<Expander IsExpanded="True"
573-
Style="{StaticResource ResourceKey=DefaultExpander}">
573+
<controls:GroupExpander Style="{StaticResource DefaultExpander}"
574+
StateStore="{Binding Path=DataContext.GroupExpanderStateStore, RelativeSource={RelativeSource AncestorType=ListBox}}"
575+
GroupName="{Binding Path=(CollectionViewGroup.Name)}">
574576
<Expander.Header>
575577
<Grid>
576578
<Grid.ColumnDefinitions>
577579
<ColumnDefinition Width="*" />
578580
<ColumnDefinition Width="10" />
579581
<ColumnDefinition Width="Auto" />
580582
</Grid.ColumnDefinitions>
583+
<Grid.ContextMenu>
584+
<ContextMenu Style="{StaticResource DefaultContextMenu}">
585+
<MenuItem Header="{x:Static Member=localization:Strings.ExpandAll}"
586+
Command="{Binding Path=DataContext.ExpandAllProfileGroupsCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TypeName=ListBox}}}">
587+
<MenuItem.Icon>
588+
<Rectangle Width="16" Height="16"
589+
Fill="{DynamicResource ResourceKey=MahApps.Brushes.Gray3}">
590+
<Rectangle.OpacityMask>
591+
<VisualBrush Stretch="Uniform"
592+
Visual="{iconPacks:Material Kind=PlusBoxMultipleOutline}" />
593+
</Rectangle.OpacityMask>
594+
</Rectangle>
595+
</MenuItem.Icon>
596+
</MenuItem>
597+
<MenuItem Header="{x:Static Member=localization:Strings.CollapseAll}"
598+
Command="{Binding Path=DataContext.CollapseAllProfileGroupsCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TypeName=ListBox}}}">
599+
<MenuItem.Icon>
600+
<Rectangle Width="16" Height="16"
601+
Fill="{DynamicResource ResourceKey=MahApps.Brushes.Gray3}">
602+
<Rectangle.OpacityMask>
603+
<VisualBrush Stretch="Uniform"
604+
Visual="{iconPacks:Material Kind=MinusBoxMultipleOutline}" />
605+
</Rectangle.OpacityMask>
606+
</Rectangle>
607+
</MenuItem.Icon>
608+
</MenuItem>
609+
</ContextMenu>
610+
</Grid.ContextMenu>
581611
<Rectangle Grid.Column="0" Grid.ColumnSpan="3"
582-
Fill="Transparent" />
612+
Fill="Transparent">
613+
614+
</Rectangle>
583615
<TextBlock Grid.Column="0"
584616
Text="{Binding Path=(CollectionViewGroup.Name)}"
585617
Style="{DynamicResource ResourceKey=ProfileGroupTextBlock}" />
@@ -614,7 +646,7 @@
614646
</Grid>
615647
</Expander.Header>
616648
<ItemsPresenter />
617-
</Expander>
649+
</controls:GroupExpander>
618650
</ControlTemplate>
619651
</Setter.Value>
620652
</Setter>

0 commit comments

Comments
 (0)