Skip to content

Commit fa0ab07

Browse files
Merge pull request #3252 from tom-englert/dev/#3251
Fixes #3251: Decompiler Settings: Checkbox in group header does not reflect state of the group
2 parents 7e74de2 + 1520b41 commit fa0ab07

4 files changed

Lines changed: 106 additions & 150 deletions

File tree

Lines changed: 35 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,38 @@
11
<UserControl x:Class="ICSharpCode.ILSpy.Options.DecompilerSettingsPanel"
22
x:ClassModifier="internal"
3-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5-
xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties"
6-
xmlns:toms="urn:TomsToolbox">
7-
<UserControl.Resources>
8-
<CollectionViewSource x:Key="SettingsCollection" Source="{Binding Settings}">
9-
<CollectionViewSource.GroupDescriptions>
10-
<PropertyGroupDescription PropertyName="Category" />
11-
</CollectionViewSource.GroupDescriptions>
12-
</CollectionViewSource>
13-
</UserControl.Resources>
14-
<Grid>
15-
<Grid.RowDefinitions>
16-
<RowDefinition Height="Auto" />
17-
<RowDefinition Height="*" />
18-
</Grid.RowDefinitions>
19-
<Grid.ColumnDefinitions>
20-
<ColumnDefinition Width="*" />
21-
</Grid.ColumnDefinitions>
22-
<TextBlock Margin="3" Grid.ColumnSpan="3" TextWrapping="Wrap" Text="{x:Static properties:Resources.DecompilerSettingsPanelLongText}" />
23-
<ListBox Grid.Row="1" ItemsSource="{Binding Source={StaticResource SettingsCollection}}">
24-
<ListBox.ItemContainerStyle>
25-
<Style TargetType="ListBoxItem">
26-
<Setter Property="IsTabStop" Value="False"/>
27-
<Setter Property="Focusable" Value="False"/>
28-
</Style>
29-
</ListBox.ItemContainerStyle>
30-
<ListBox.GroupStyle>
31-
<GroupStyle>
32-
<GroupStyle.Panel>
33-
<ItemsPanelTemplate>
34-
<VirtualizingStackPanel Orientation="Vertical" />
35-
</ItemsPanelTemplate>
36-
</GroupStyle.Panel>
37-
<GroupStyle.ContainerStyle>
38-
<Style TargetType="{x:Type GroupItem}" BasedOn="{StaticResource {x:Type GroupItem}}">
39-
<Setter Property="Template">
40-
<Setter.Value>
41-
<ControlTemplate>
42-
<Expander Padding="0" BorderThickness="0" IsExpanded="True">
43-
<Expander.Header>
44-
<CheckBox Checked="OnGroupChecked" Unchecked="OnGroupUnchecked" Loaded="OnGroupLoaded" VerticalContentAlignment="Center"
45-
FontSize="16" FontWeight="Bold" Content="{Binding Name}" />
46-
</Expander.Header>
47-
<ItemsPresenter/>
48-
</Expander>
49-
</ControlTemplate>
50-
</Setter.Value>
51-
</Setter>
52-
</Style>
53-
</GroupStyle.ContainerStyle>
54-
</GroupStyle>
55-
</ListBox.GroupStyle>
56-
<ListBox.ItemTemplate>
57-
<DataTemplate>
58-
<CheckBox Margin="19,0,0,0" IsChecked="{Binding IsEnabled}" Content="{Binding Description}" />
59-
</DataTemplate>
60-
</ListBox.ItemTemplate>
61-
</ListBox>
62-
</Grid>
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
7+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8+
xmlns:options="clr-namespace:ICSharpCode.ILSpy.Options"
9+
d:DataContext="{d:DesignInstance options:DecompilerSettingsViewModel}">
10+
<DockPanel>
11+
<TextBlock Margin="3" DockPanel.Dock="Top" TextWrapping="Wrap" Text="{x:Static properties:Resources.DecompilerSettingsPanelLongText}" />
12+
<ScrollViewer>
13+
<ItemsControl ItemsSource="{Binding Settings}">
14+
<ItemsControl.ItemTemplate>
15+
<DataTemplate>
16+
<Grid>
17+
<Expander Padding="0" BorderThickness="0" IsExpanded="True">
18+
<Expander.Header>
19+
<CheckBox VerticalContentAlignment="Center"
20+
FontSize="16" FontWeight="Bold"
21+
Content="{Binding Category}"
22+
IsChecked="{Binding AreAllItemsChecked, Mode=TwoWay}"/>
23+
</Expander.Header>
24+
<ItemsControl ItemsSource="{Binding Settings}">
25+
<ItemsControl.ItemTemplate>
26+
<DataTemplate>
27+
<CheckBox Margin="28,0,0,0" IsChecked="{Binding IsEnabled}" Content="{Binding Description}" />
28+
</DataTemplate>
29+
</ItemsControl.ItemTemplate>
30+
</ItemsControl>
31+
</Expander>
32+
</Grid>
33+
</DataTemplate>
34+
</ItemsControl.ItemTemplate>
35+
</ItemsControl>
36+
</ScrollViewer>
37+
</DockPanel>
6338
</UserControl>

ILSpy/Options/DecompilerSettingsPanel.xaml.cs

Lines changed: 2 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@
1616
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
1717
// DEALINGS IN THE SOFTWARE.
1818

19-
using System.ComponentModel;
20-
using System.Linq;
21-
using System.Reflection;
22-
using System.Windows;
23-
using System.Windows.Controls;
24-
using System.Windows.Data;
2519
using System.Xml.Linq;
2620

2721
using ICSharpCode.ILSpyX.Settings;
@@ -32,7 +26,7 @@ namespace ICSharpCode.ILSpy.Options
3226
/// Interaction logic for DecompilerSettingsPanel.xaml
3327
/// </summary>
3428
[ExportOptionPage(Title = nameof(Properties.Resources.Decompiler), Order = 10)]
35-
internal partial class DecompilerSettingsPanel : UserControl, IOptionPage
29+
internal partial class DecompilerSettingsPanel : IOptionPage
3630
{
3731
public DecompilerSettingsPanel()
3832
{
@@ -59,58 +53,9 @@ public void Save(XElement root)
5953
MainWindow.Instance.AssemblyListManager.UseDebugSymbols = newSettings.UseDebugSymbols;
6054
}
6155

62-
private void OnGroupChecked(object sender, RoutedEventArgs e)
63-
{
64-
CheckGroup((CollectionViewGroup)((CheckBox)sender).DataContext, true);
65-
}
66-
private void OnGroupUnchecked(object sender, RoutedEventArgs e)
67-
{
68-
CheckGroup((CollectionViewGroup)((CheckBox)sender).DataContext, false);
69-
}
70-
71-
void CheckGroup(CollectionViewGroup group, bool value)
72-
{
73-
foreach (var item in group.Items)
74-
{
75-
switch (item)
76-
{
77-
case CollectionViewGroup subGroup:
78-
CheckGroup(subGroup, value);
79-
break;
80-
case CSharpDecompilerSetting setting:
81-
setting.IsEnabled = value;
82-
break;
83-
}
84-
}
85-
}
86-
87-
bool IsGroupChecked(CollectionViewGroup group)
88-
{
89-
bool value = true;
90-
foreach (var item in group.Items)
91-
{
92-
switch (item)
93-
{
94-
case CollectionViewGroup subGroup:
95-
value = value && IsGroupChecked(subGroup);
96-
break;
97-
case CSharpDecompilerSetting setting:
98-
value = value && setting.IsEnabled;
99-
break;
100-
}
101-
}
102-
return value;
103-
}
104-
105-
private void OnGroupLoaded(object sender, RoutedEventArgs e)
106-
{
107-
CheckBox checkBox = (CheckBox)sender;
108-
checkBox.IsChecked = IsGroupChecked((CollectionViewGroup)checkBox.DataContext);
109-
}
110-
11156
public void LoadDefaults()
11257
{
113-
MainWindow.Instance.CurrentDecompilerSettings = new Decompiler.DecompilerSettings();
58+
MainWindow.Instance.CurrentDecompilerSettings = new();
11459
this.DataContext = new DecompilerSettingsViewModel(MainWindow.Instance.CurrentDecompilerSettings);
11560
}
11661
}

ILSpy/Options/DecompilerSettingsViewModel.cs

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,83 +16,120 @@
1616
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
1717
// DEALINGS IN THE SOFTWARE.
1818

19+
using System.Collections.Generic;
1920
using System.ComponentModel;
2021
using System.Linq;
2122
using System.Reflection;
22-
using System.Runtime.CompilerServices;
2323

2424
using ICSharpCode.ILSpy.Properties;
2525
using ICSharpCode.ILSpy.TreeNodes;
2626

27+
using TomsToolbox.Wpf;
28+
2729
namespace ICSharpCode.ILSpy.Options
2830
{
29-
public class DecompilerSettingsViewModel : INotifyPropertyChanged
31+
public sealed class DecompilerSettingsViewModel : ObservableObjectBase
3032
{
31-
public CSharpDecompilerSetting[] Settings { get; set; }
33+
public DecompilerSettingsGroupViewModel[] Settings { get; }
3234

3335
public DecompilerSettingsViewModel(Decompiler.DecompilerSettings settings)
3436
{
3537
Settings = typeof(Decompiler.DecompilerSettings).GetProperties()
3638
.Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false)
37-
.Select(p => new CSharpDecompilerSetting(p) { IsEnabled = (bool)p.GetValue(settings) })
39+
.Select(p => new DecompilerSettingsItemViewModel(p) { IsEnabled = p.GetValue(settings) is true })
3840
.OrderBy(item => item.Category, NaturalStringComparer.Instance)
39-
.ThenBy(item => item.Description)
41+
.GroupBy(p => p.Category)
42+
.Select(g => new DecompilerSettingsGroupViewModel(g.Key, g.OrderBy(i => i.Description).ToArray()))
4043
.ToArray();
4144
}
4245

43-
public event PropertyChangedEventHandler PropertyChanged;
44-
45-
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
46-
{
47-
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
48-
}
49-
5046
public Decompiler.DecompilerSettings ToDecompilerSettings()
5147
{
5248
var settings = new Decompiler.DecompilerSettings();
53-
foreach (var item in Settings)
49+
50+
foreach (var item in Settings.SelectMany(group => group.Settings))
5451
{
5552
item.Property.SetValue(settings, item.IsEnabled);
5653
}
54+
5755
return settings;
5856
}
5957
}
60-
public class CSharpDecompilerSetting : INotifyPropertyChanged
58+
59+
public sealed class DecompilerSettingsGroupViewModel : ObservableObjectBase
6160
{
62-
bool isEnabled;
61+
private bool? _areAllItemsChecked;
6362

64-
public CSharpDecompilerSetting(PropertyInfo p)
63+
public DecompilerSettingsGroupViewModel(string category, DecompilerSettingsItemViewModel[] settings)
6564
{
66-
this.Property = p;
67-
this.Category = GetResourceString(p.GetCustomAttribute<CategoryAttribute>()?.Category ?? Resources.Other);
68-
this.Description = GetResourceString(p.GetCustomAttribute<DescriptionAttribute>()?.Description ?? p.Name);
69-
}
65+
Settings = settings;
66+
Category = category;
7067

71-
public PropertyInfo Property { get; }
68+
_areAllItemsChecked = GetAreAllItemsChecked(Settings);
7269

73-
public bool IsEnabled {
74-
get => isEnabled;
70+
foreach (DecompilerSettingsItemViewModel viewModel in settings)
71+
{
72+
viewModel.PropertyChanged += Item_PropertyChanged;
73+
}
74+
}
75+
76+
public bool? AreAllItemsChecked {
77+
get => _areAllItemsChecked;
7578
set {
76-
if (value != isEnabled)
79+
SetProperty(ref _areAllItemsChecked, value);
80+
81+
if (!value.HasValue)
82+
return;
83+
84+
foreach (var setting in Settings)
7785
{
78-
isEnabled = value;
79-
OnPropertyChanged();
86+
setting.IsEnabled = value.Value;
8087
}
8188
}
8289
}
8390

84-
public string Description { get; set; }
91+
public string Category { get; }
8592

86-
public string Category { get; set; }
93+
public DecompilerSettingsItemViewModel[] Settings { get; }
8794

88-
public event PropertyChangedEventHandler PropertyChanged;
95+
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
96+
{
97+
if (e.PropertyName == nameof(DecompilerSettingsItemViewModel.IsEnabled))
98+
{
99+
AreAllItemsChecked = GetAreAllItemsChecked(Settings);
100+
}
101+
}
89102

90-
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
103+
private static bool? GetAreAllItemsChecked(ICollection<DecompilerSettingsItemViewModel> settings)
91104
{
92-
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
105+
var numberOfEnabledItems = settings.Count(item => item.IsEnabled);
106+
107+
if (numberOfEnabledItems == settings.Count)
108+
return true;
109+
110+
if (numberOfEnabledItems == 0)
111+
return false;
112+
113+
return null;
114+
}
115+
}
116+
117+
public sealed class DecompilerSettingsItemViewModel(PropertyInfo property) : ObservableObjectBase
118+
{
119+
private bool _isEnabled;
120+
121+
public PropertyInfo Property { get; } = property;
122+
123+
public bool IsEnabled {
124+
get => _isEnabled;
125+
set => SetProperty(ref _isEnabled, value);
93126
}
94127

95-
static string GetResourceString(string key)
128+
public string Description { get; set; } = GetResourceString(property.GetCustomAttribute<DescriptionAttribute>()?.Description ?? property.Name);
129+
130+
public string Category { get; set; } = GetResourceString(property.GetCustomAttribute<CategoryAttribute>()?.Category ?? Resources.Other);
131+
132+
private static string GetResourceString(string key)
96133
{
97134
var str = !string.IsNullOrEmpty(key) ? Resources.ResourceManager.GetString(key) : null;
98135
return string.IsNullOrEmpty(key) || string.IsNullOrEmpty(str) ? key : str;

ILSpy/Options/DisplaySettingsPanel.xaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
88
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
99
xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes"
10-
xmlns:toms="urn:TomsToolbox"
1110
d:DataContext="{d:DesignInstance local:DisplaySettingsViewModel}">
1211
<UserControl.Resources>
1312
<local:FontSizeConverter x:Key="fontSizeConv" />

0 commit comments

Comments
 (0)