From 45cfb8fdbeea65a696a6f579809e7836a78d112a Mon Sep 17 00:00:00 2001 From: tom-englert Date: Sat, 3 Aug 2024 15:10:02 +0200 Subject: [PATCH 1/3] Fix #3249: Settings panel shows strange hover/selection behavior --- ILSpy/Options/DecompilerSettingsPanel.xaml | 82 ++++++++++------------ ILSpy/Options/DisplaySettingsPanel.xaml | 1 - 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml b/ILSpy/Options/DecompilerSettingsPanel.xaml index 2e16f50540..f257d86546 100644 --- a/ILSpy/Options/DecompilerSettingsPanel.xaml +++ b/ILSpy/Options/DecompilerSettingsPanel.xaml @@ -1,9 +1,12 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:options="clr-namespace:ICSharpCode.ILSpy.Options" + d:DataContext="{d:DesignInstance options:DecompilerSettingsViewModel}"> @@ -20,44 +23,37 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml b/ILSpy/Options/DisplaySettingsPanel.xaml index 846a33c061..52f5c3ab13 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml +++ b/ILSpy/Options/DisplaySettingsPanel.xaml @@ -7,7 +7,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" - xmlns:toms="urn:TomsToolbox" d:DataContext="{d:DesignInstance local:DisplaySettingsViewModel}"> From d435f5ffafac6279ebe259940c60fae027ccb4d5 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Sat, 3 Aug 2024 15:55:20 +0200 Subject: [PATCH 2/3] Fix #3251: Decompiler Settings: CheckBox in group header does not reflect state of the group --- ILSpy/Options/DecompilerSettingsPanel.xaml | 50 ++++----- ILSpy/Options/DecompilerSettingsPanel.xaml.cs | 59 +--------- ILSpy/Options/DecompilerSettingsViewModel.cs | 101 ++++++++++++------ 3 files changed, 89 insertions(+), 121 deletions(-) diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml b/ILSpy/Options/DecompilerSettingsPanel.xaml index f257d86546..2078217e46 100644 --- a/ILSpy/Options/DecompilerSettingsPanel.xaml +++ b/ILSpy/Options/DecompilerSettingsPanel.xaml @@ -7,13 +7,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:options="clr-namespace:ICSharpCode.ILSpy.Options" d:DataContext="{d:DesignInstance options:DecompilerSettingsViewModel}"> - - - - - - - @@ -24,33 +17,26 @@ - - - - - - - - + - + + + + + + + + + + + + + + diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs index aec4ced54d..9f542f2434 100644 --- a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs +++ b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs @@ -16,12 +16,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; using System.Xml.Linq; using ICSharpCode.ILSpyX.Settings; @@ -32,7 +26,7 @@ namespace ICSharpCode.ILSpy.Options /// Interaction logic for DecompilerSettingsPanel.xaml /// [ExportOptionPage(Title = nameof(Properties.Resources.Decompiler), Order = 10)] - internal partial class DecompilerSettingsPanel : UserControl, IOptionPage + internal partial class DecompilerSettingsPanel : IOptionPage { public DecompilerSettingsPanel() { @@ -59,58 +53,9 @@ public void Save(XElement root) MainWindow.Instance.AssemblyListManager.UseDebugSymbols = newSettings.UseDebugSymbols; } - private void OnGroupChecked(object sender, RoutedEventArgs e) - { - CheckGroup((CollectionViewGroup)((CheckBox)sender).DataContext, true); - } - private void OnGroupUnchecked(object sender, RoutedEventArgs e) - { - CheckGroup((CollectionViewGroup)((CheckBox)sender).DataContext, false); - } - - void CheckGroup(CollectionViewGroup group, bool value) - { - foreach (var item in group.Items) - { - switch (item) - { - case CollectionViewGroup subGroup: - CheckGroup(subGroup, value); - break; - case CSharpDecompilerSetting setting: - setting.IsEnabled = value; - break; - } - } - } - - bool IsGroupChecked(CollectionViewGroup group) - { - bool value = true; - foreach (var item in group.Items) - { - switch (item) - { - case CollectionViewGroup subGroup: - value = value && IsGroupChecked(subGroup); - break; - case CSharpDecompilerSetting setting: - value = value && setting.IsEnabled; - break; - } - } - return value; - } - - private void OnGroupLoaded(object sender, RoutedEventArgs e) - { - CheckBox checkBox = (CheckBox)sender; - checkBox.IsChecked = IsGroupChecked((CollectionViewGroup)checkBox.DataContext); - } - public void LoadDefaults() { - MainWindow.Instance.CurrentDecompilerSettings = new Decompiler.DecompilerSettings(); + MainWindow.Instance.CurrentDecompilerSettings = new(); this.DataContext = new DecompilerSettingsViewModel(MainWindow.Instance.CurrentDecompilerSettings); } } diff --git a/ILSpy/Options/DecompilerSettingsViewModel.cs b/ILSpy/Options/DecompilerSettingsViewModel.cs index b7ce36fbe6..3a574cca90 100644 --- a/ILSpy/Options/DecompilerSettingsViewModel.cs +++ b/ILSpy/Options/DecompilerSettingsViewModel.cs @@ -16,83 +16,120 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TreeNodes; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpy.Options { - public class DecompilerSettingsViewModel : INotifyPropertyChanged + public sealed class DecompilerSettingsViewModel : ObservableObjectBase { - public CSharpDecompilerSetting[] Settings { get; set; } + public DecompilerSettingsGroupViewModel[] Settings { get; } public DecompilerSettingsViewModel(Decompiler.DecompilerSettings settings) { Settings = typeof(Decompiler.DecompilerSettings).GetProperties() .Where(p => p.GetCustomAttribute()?.Browsable != false) - .Select(p => new CSharpDecompilerSetting(p) { IsEnabled = (bool)p.GetValue(settings) }) + .Select(p => new DecompilerSettingsItemViewModel(p) { IsEnabled = p.GetValue(settings) is true }) .OrderBy(item => item.Category, NaturalStringComparer.Instance) - .ThenBy(item => item.Description) + .GroupBy(p => p.Category) + .Select(g => new DecompilerSettingsGroupViewModel(g.Key, g.OrderBy(i => i.Description).ToArray())) .ToArray(); } - public event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - public Decompiler.DecompilerSettings ToDecompilerSettings() { var settings = new Decompiler.DecompilerSettings(); - foreach (var item in Settings) + + foreach (var item in Settings.SelectMany(group => group.Settings)) { item.Property.SetValue(settings, item.IsEnabled); } + return settings; } } - public class CSharpDecompilerSetting : INotifyPropertyChanged + + public sealed class DecompilerSettingsGroupViewModel : ObservableObjectBase { - bool isEnabled; + private bool? _areAllItemsChecked; - public CSharpDecompilerSetting(PropertyInfo p) + public DecompilerSettingsGroupViewModel(string category, DecompilerSettingsItemViewModel[] settings) { - this.Property = p; - this.Category = GetResourceString(p.GetCustomAttribute()?.Category ?? Resources.Other); - this.Description = GetResourceString(p.GetCustomAttribute()?.Description ?? p.Name); - } + Settings = settings; + Category = category; - public PropertyInfo Property { get; } + _areAllItemsChecked = GetAreAllItemsChecked(Settings); - public bool IsEnabled { - get => isEnabled; + foreach (DecompilerSettingsItemViewModel viewModel in settings) + { + viewModel.PropertyChanged += Item_PropertyChanged; + } + } + + public bool? AreAllItemsChecked { + get => _areAllItemsChecked; set { - if (value != isEnabled) + SetProperty(ref _areAllItemsChecked, value); + + if (!value.HasValue) + return; + + foreach (var setting in Settings) { - isEnabled = value; - OnPropertyChanged(); + setting.IsEnabled = value.Value; } } } - public string Description { get; set; } + public string Category { get; } - public string Category { get; set; } + public DecompilerSettingsItemViewModel[] Settings { get; } - public event PropertyChangedEventHandler PropertyChanged; + private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(DecompilerSettingsItemViewModel.IsEnabled)) + { + AreAllItemsChecked = GetAreAllItemsChecked(Settings); + } + } - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + private static bool? GetAreAllItemsChecked(ICollection settings) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + var numberOfEnabledItems = settings.Count(item => item.IsEnabled); + + if (numberOfEnabledItems == settings.Count) + return true; + + if (numberOfEnabledItems == 0) + return false; + + return null; + } + } + + public sealed class DecompilerSettingsItemViewModel(PropertyInfo property) : ObservableObjectBase + { + private bool _isEnabled; + + public PropertyInfo Property { get; } = property; + + public bool IsEnabled { + get => _isEnabled; + set => SetProperty(ref _isEnabled, value); } - static string GetResourceString(string key) + public string Description { get; set; } = GetResourceString(property.GetCustomAttribute()?.Description ?? property.Name); + + public string Category { get; set; } = GetResourceString(property.GetCustomAttribute()?.Category ?? Resources.Other); + + private static string GetResourceString(string key) { var str = !string.IsNullOrEmpty(key) ? Resources.ResourceManager.GetString(key) : null; return string.IsNullOrEmpty(key) || string.IsNullOrEmpty(str) ? key : str; From 1520b4134193742813fc21d7bb03dcfdb449365d Mon Sep 17 00:00:00 2001 From: tom-englert Date: Sat, 3 Aug 2024 16:05:46 +0200 Subject: [PATCH 3/3] Simplify layout --- ILSpy/Options/DecompilerSettingsPanel.xaml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml b/ILSpy/Options/DecompilerSettingsPanel.xaml index 2078217e46..fe43682430 100644 --- a/ILSpy/Options/DecompilerSettingsPanel.xaml +++ b/ILSpy/Options/DecompilerSettingsPanel.xaml @@ -7,16 +7,9 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:options="clr-namespace:ICSharpCode.ILSpy.Options" d:DataContext="{d:DesignInstance options:DecompilerSettingsViewModel}"> - - - - - - - - - - + + + @@ -41,5 +34,5 @@ - + \ No newline at end of file