From a24e0f96c8debc15d8e468c79758c63889207d62 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Fri, 1 Nov 2024 16:10:20 +0100 Subject: [PATCH] Refactoring: - Decouple services to reduce circular dependencies - Move update panel to a separate control - Remove unrelated methods from MainWindow --- ICSharpCode.ILSpyX/Analyzers/AnalyzerScope.cs | 5 +- ICSharpCode.ILSpyX/Analyzers/IAnalyzer.cs | 1 + ILSpy/AboutPage.cs | 43 ++--- ILSpy/Analyzers/AnalyzeCommand.cs | 2 + ILSpy/Analyzers/AnalyzerSearchTreeNode.cs | 4 +- ILSpy/Analyzers/AnalyzerTreeNode.cs | 6 +- ILSpy/App.xaml.cs | 17 +- ILSpy/AssemblyTree/AssemblyTreeModel.cs | 86 +++++++--- ILSpy/Commands/CheckForUpdatesCommand.cs | 7 +- ILSpy/Commands/ExitCommand.cs | 4 +- ILSpy/Commands/ManageAssemblyListsCommand.cs | 16 +- ILSpy/Commands/OpenFromGacCommand.cs | 13 +- ILSpy/Commands/SearchMsdnContextMenuEntry.cs | 2 +- ILSpy/Commands/SimpleCommand.cs | 36 +--- ILSpy/Docking/DockWorkspace.cs | 56 +++---- ILSpy/Languages/Language.cs | 6 +- ILSpy/MainWindow.xaml | 29 ++-- ILSpy/MainWindow.xaml.cs | 154 +----------------- ILSpy/MainWindowViewModel.cs | 6 +- ILSpy/Options/OptionsDialog.xaml.cs | 5 +- ILSpy/Search/SearchPane.xaml.cs | 8 +- ILSpy/Search/SearchPaneModel.cs | 6 +- ILSpy/TextView/DecompilerTextView.cs | 47 +++--- ILSpy/TextView/DocumentationUIBuilder.cs | 6 +- ILSpy/TreeNodes/AssemblyListTreeNode.cs | 2 +- ILSpy/TreeNodes/AssemblyTreeNode.cs | 6 +- ILSpy/TreeNodes/ILSpyTreeNode.cs | 12 +- ...yOfUpdatesStrategy.cs => UpdateService.cs} | 8 +- ILSpy/Util/GlobalUtils.cs | 52 ++++++ ILSpy/Util/MenuService.cs | 52 +++--- ILSpy/Util/MessageBus.cs | 48 +++++- .../ManageAssemblyListsViewModel.cs | 15 +- ILSpy/ViewModels/PaneModel.cs | 5 +- ILSpy/ViewModels/TabPageModel.cs | 62 +++---- ILSpy/ViewModels/UpdatePanelViewModel.cs | 100 ++++++++++++ ILSpy/Views/UpdatePanel.xaml | 22 +++ .../UpdatePanel.xaml.cs} | 24 ++- 37 files changed, 525 insertions(+), 448 deletions(-) rename ILSpy/Updates/{NotifyOfUpdatesStrategy.cs => UpdateService.cs} (94%) create mode 100644 ILSpy/Util/GlobalUtils.cs create mode 100644 ILSpy/ViewModels/UpdatePanelViewModel.cs create mode 100644 ILSpy/Views/UpdatePanel.xaml rename ILSpy/{ViewModels/ViewModelBase.cs => Views/UpdatePanel.xaml.cs} (70%) diff --git a/ICSharpCode.ILSpyX/Analyzers/AnalyzerScope.cs b/ICSharpCode.ILSpyX/Analyzers/AnalyzerScope.cs index 290e225bf..3a0db60df 100644 --- a/ICSharpCode.ILSpyX/Analyzers/AnalyzerScope.cs +++ b/ICSharpCode.ILSpyX/Analyzers/AnalyzerScope.cs @@ -40,17 +40,14 @@ namespace ICSharpCode.ILSpyX.Analyzers /// public bool IsLocal { get; } - public AssemblyList AssemblyList { get; } - public ISymbol AnalyzedSymbol { get; } public ITypeDefinition TypeScope => typeScope; - Accessibility effectiveAccessibility; + readonly Accessibility effectiveAccessibility; public AnalyzerScope(AssemblyList assemblyList, IEntity entity) { - AssemblyList = assemblyList; assemblyListSnapshot = assemblyList.GetSnapshot(); AnalyzedSymbol = entity; DetermineEffectiveAccessibility(entity, out typeScope, out effectiveAccessibility); diff --git a/ICSharpCode.ILSpyX/Analyzers/IAnalyzer.cs b/ICSharpCode.ILSpyX/Analyzers/IAnalyzer.cs index d21a87183..e028e4b7f 100644 --- a/ICSharpCode.ILSpyX/Analyzers/IAnalyzer.cs +++ b/ICSharpCode.ILSpyX/Analyzers/IAnalyzer.cs @@ -42,6 +42,7 @@ namespace ICSharpCode.ILSpyX.Analyzers public interface IAnalyzerMetadata { string Header { get; } + int Order { get; } } } diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index 94ebd59c3..4d7b66639 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -26,36 +26,42 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; -using System.Windows.Navigation; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.Decompiler; -using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.Updates; +using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy { [ExportMainMenuCommand(ParentMenuID = nameof(Resources._Help), Header = nameof(Resources._About), MenuOrder = 99999)] [Shared] - public sealed class AboutPageCommand(AssemblyTreeModel assemblyTreeModel) : SimpleCommand + public sealed class AboutPage : SimpleCommand { + readonly SettingsService settingsService; + readonly IEnumerable aboutPageAdditions; + + public AboutPage(SettingsService settingsService, IEnumerable aboutPageAdditions) + { + this.settingsService = settingsService; + this.aboutPageAdditions = aboutPageAdditions; + MessageBus.Subscribers += (_, e) => ShowAboutPage(e.TabPage); + } + public override void Execute(object parameter) { - assemblyTreeModel.NavigateTo( - new RequestNavigateEventArgs(new Uri("resource://aboutpage"), null), - inNewTabPage: true - ); + MessageBus.Send(this, new NavigateToEventArgs(new(new("resource://aboutpage"), null), inNewTabPage: true)); } - } - [Export] - [Shared] - public sealed class AboutPage(IEnumerable aboutPageAdditions, SettingsService settingsService) - { - public void Display(DecompilerTextView textView) + private void ShowAboutPage(TabPageModel tabPage) + { + tabPage.ShowTextView(Display); + } + + private void Display(DecompilerTextView textView) { AvalonEditTextOutput output = new AvalonEditTextOutput() { Title = Resources.About, @@ -72,14 +78,14 @@ namespace ICSharpCode.ILSpy HorizontalAlignment = HorizontalAlignment.Center, Orientation = Orientation.Horizontal }; - if (NotifyOfUpdatesStrategy.LatestAvailableVersion == null) + if (UpdateService.LatestAvailableVersion == null) { AddUpdateCheckButton(stackPanel, textView); } else { // we already retrieved the latest version sometime earlier - ShowAvailableVersion(NotifyOfUpdatesStrategy.LatestAvailableVersion, stackPanel); + ShowAvailableVersion(UpdateService.LatestAvailableVersion, stackPanel); } CheckBox checkBox = new() { Margin = new Thickness(4), @@ -104,8 +110,7 @@ namespace ICSharpCode.ILSpy { using (StreamReader r = new StreamReader(s)) { - string line; - while ((line = r.ReadLine()) != null) + while (r.ReadLine() is { } line) { output.WriteLine(line); } @@ -166,7 +171,7 @@ namespace ICSharpCode.ILSpy try { - AvailableVersionInfo vInfo = await NotifyOfUpdatesStrategy.GetLatestVersionAsync(); + AvailableVersionInfo vInfo = await UpdateService.GetLatestVersionAsync(); stackPanel.Children.Clear(); ShowAvailableVersion(vInfo, stackPanel); } @@ -209,7 +214,7 @@ namespace ICSharpCode.ILSpy button.Content = Resources.Download; button.Cursor = Cursors.Arrow; button.Click += delegate { - MainWindow.OpenLink(availableVersion.DownloadUrl); + GlobalUtils.OpenLink(availableVersion.DownloadUrl); }; stackPanel.Children.Add(button); } diff --git a/ILSpy/Analyzers/AnalyzeCommand.cs b/ILSpy/Analyzers/AnalyzeCommand.cs index 30c5a9ee7..20d80d48f 100644 --- a/ILSpy/Analyzers/AnalyzeCommand.cs +++ b/ILSpy/Analyzers/AnalyzeCommand.cs @@ -73,6 +73,8 @@ namespace ICSharpCode.ILSpy.Analyzers } } + [Export] + [Shared] public sealed class AnalyzeCommand(AssemblyTreeModel assemblyTreeModel, AnalyzerTreeViewModel analyzerTreeViewModel) : SimpleCommand { public override bool CanExecute(object parameter) diff --git a/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs b/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs index 8609fc674..52556d623 100644 --- a/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs @@ -58,10 +58,10 @@ namespace ICSharpCode.ILSpy.Analyzers { if (symbol is IEntity) { - var context = new AnalyzerContext() { + var context = new AnalyzerContext { CancellationToken = ct, Language = Language, - AssemblyList = AssemblyTreeModel.AssemblyList + AssemblyList = AssemblyList }; var results = analyzer.Analyze(symbol, context).Select(SymbolTreeNodeFactory); if (context.SortResults) diff --git a/ILSpy/Analyzers/AnalyzerTreeNode.cs b/ILSpy/Analyzers/AnalyzerTreeNode.cs index 2775634a6..7937b12b4 100644 --- a/ILSpy/Analyzers/AnalyzerTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerTreeNode.cs @@ -30,7 +30,9 @@ namespace ICSharpCode.ILSpy.Analyzers { public abstract class AnalyzerTreeNode : SharpTreeNode { - public static Language Language => App.ExportProvider.GetExportedValue().Language; + protected static Language Language => App.ExportProvider.GetExportedValue().Language; + + protected static AssemblyList AssemblyList => App.ExportProvider.GetExportedValue(); public override bool CanDelete() { @@ -47,8 +49,6 @@ namespace ICSharpCode.ILSpy.Analyzers DeleteCore(); } - public static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue(); - public static ICollection> Analyzers => App.ExportProvider .GetExports("Analyzer") .OrderBy(item => item.Metadata?.Order) diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index f437811cf..c82af3125 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -25,8 +25,6 @@ using System.Reflection; using System.Runtime.Loader; using System.Threading.Tasks; using System.Windows; -using System.Windows.Documents; -using System.Windows.Navigation; using System.Windows.Threading; using ICSharpCode.ILSpy.AppEnv; @@ -115,9 +113,6 @@ namespace ICSharpCode.ILSpy Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new(sessionSettings.CurrentCulture); } - EventManager.RegisterClassHandler(typeof(Window), - Hyperlink.RequestNavigateEvent, - new RequestNavigateEventHandler(Window_RequestNavigate)); ILSpyTraceListener.Install(); if (CommandLineArguments.ArgumentsParser.IsShowingInformation) @@ -138,7 +133,7 @@ namespace ICSharpCode.ILSpy public new MainWindow MainWindow { get => (MainWindow)base.MainWindow; - set => base.MainWindow = value; + private set => base.MainWindow = value; } private static void SingleInstance_NewInstanceDetected(object sender, NewInstanceEventArgs e) => ExportProvider.GetExportedValue().HandleSingleInstanceCommandLineArguments(e.Args).HandleExceptions(); @@ -152,7 +147,7 @@ namespace ICSharpCode.ILSpy return context.LoadFromAssemblyPath(assemblyFileName); } - private static bool InitializeDependencyInjection(SettingsService settingsService) + private bool InitializeDependencyInjection(SettingsService settingsService) { // Add custom logic for resolution of dependencies. // This necessary because the AssemblyLoadContext.LoadFromAssemblyPath and related methods, @@ -193,11 +188,14 @@ namespace ICSharpCode.ILSpy services.AddSingleton(_ => ExportProvider); // Add the docking manager services.AddSingleton(serviceProvider => serviceProvider.GetService().DockManager); + services.AddTransient(serviceProvider => serviceProvider.GetService().AssemblyList); var serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); ExportProvider = new ExportProviderAdapter(serviceProvider); + Exit += (_, _) => serviceProvider.Dispose(); + return true; } catch (Exception ex) @@ -274,10 +272,5 @@ namespace ICSharpCode.ILSpy } } #endregion - - void Window_RequestNavigate(object sender, RequestNavigateEventArgs e) - { - ExportProvider.GetExportedValue().NavigateTo(e); - } } } \ No newline at end of file diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index 8665f38e4..3155b29b0 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -28,6 +28,7 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Threading.Tasks; using System.Windows; +using System.Windows.Documents; using System.Windows.Input; using System.Windows.Navigation; using System.Windows.Threading; @@ -37,15 +38,12 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.ILSpy.AppEnv; -using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; -using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpyX.TreeView; using TomsToolbox.Composition; @@ -58,7 +56,6 @@ namespace ICSharpCode.ILSpy.AssemblyTree { [ExportToolPane] [Shared] - [Export] public class AssemblyTreeModel : ToolPaneModel { public const string PaneContentId = "assemblyListPane"; @@ -69,16 +66,12 @@ namespace ICSharpCode.ILSpy.AssemblyTree private readonly NavigationHistory history = new(); private bool isNavigatingHistory; - private readonly AboutPage aboutPage; - private readonly SearchPaneModel searchPaneModel; private readonly SettingsService settingsService; private readonly LanguageService languageService; private readonly IExportProvider exportProvider; - public AssemblyTreeModel(AboutPage aboutPage, SearchPaneModel searchPaneModel, SettingsService settingsService, LanguageService languageService, IExportProvider exportProvider) + public AssemblyTreeModel(SettingsService settingsService, LanguageService languageService, IExportProvider exportProvider) { - this.aboutPage = aboutPage; - this.searchPaneModel = searchPaneModel; this.settingsService = settingsService; this.languageService = languageService; this.exportProvider = exportProvider; @@ -90,8 +83,18 @@ namespace ICSharpCode.ILSpy.AssemblyTree MessageBus.Subscribers += JumpToReference; MessageBus.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); + MessageBus.Subscribers += ApplySessionSettings; + MessageBus.Subscribers += ActiveTabPageChanged; + MessageBus.Subscribers += ResetLayout; + MessageBus.Subscribers += (_, e) => NavigateTo(e.Request, e.InNewTabPage); + MessageBus.Subscribers += (_, _) => { + Initialize(); + Show(); + }; + + EventManager.RegisterClassHandler(typeof(Window), Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler((_, e) => NavigateTo(e))); - refreshThrottle = new DispatcherThrottle(DispatcherPriority.Background, RefreshInternal); + refreshThrottle = new(DispatcherPriority.Background, RefreshInternal); AssemblyList = settingsService.CreateEmptyAssemblyList(); } @@ -183,8 +186,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree if (args.Search != null) { - this.searchPaneModel.SearchTerm = args.Search; - this.searchPaneModel.Show(); + MessageBus.Send(this, new ShowSearchPageEventArgs(args.Search)); } } @@ -301,11 +303,11 @@ namespace ICSharpCode.ILSpy.AssemblyTree SelectNode(node); // only if not showing the about page, perform the update check: - await App.Current.MainWindow.ShowMessageIfUpdatesAvailableAsync(updateSettings); + MessageBus.Send(this, new CheckIfUpdateAvailableEventArgs()); } else { - DockWorkspace.ActiveTabPage.ShowTextView(aboutPage.Display); + MessageBus.Send(this, new ShowAboutPageEventArgs(DockWorkspace.ActiveTabPage)); } } } @@ -442,7 +444,6 @@ namespace ICSharpCode.ILSpy.AssemblyTree AssemblyList.CollectionChanged -= assemblyList_CollectionChanged; AssemblyList = assemblyList; - assemblyList.CollectionChanged += assemblyList_CollectionChanged; assemblyListTreeNode = new(assemblyList) { @@ -655,7 +656,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree switch (reference) { case Decompiler.Disassembler.OpCodeInfo opCode: - MainWindow.OpenLink(opCode.Link); + GlobalUtils.OpenLink(opCode.Link); break; case EntityReference unresolvedEntity: string protocol = unresolvedEntity.Protocol; @@ -817,11 +818,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree public LanguageVersion? CurrentLanguageVersion => languageService.LanguageVersion; - public IEnumerable SelectedNodes { - get { - return GetTopLevelSelection().OfType(); - } - } + public IEnumerable SelectedNodes => GetTopLevelSelection().OfType(); #endregion @@ -856,7 +853,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree public bool CanNavigateForward => history.CanNavigateForward; - public void NavigateTo(RequestNavigateEventArgs e, bool inNewTabPage = false) + private void NavigateTo(RequestNavigateEventArgs e, bool inNewTabPage = false) { if (e.Uri.Scheme == "resource") { @@ -868,7 +865,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree if (e.Uri.Host == "aboutpage") { RecordHistory(); - DockWorkspace.ActiveTabPage.ShowTextView(aboutPage.Display); + MessageBus.Send(this, new ShowAboutPageEventArgs(DockWorkspace.ActiveTabPage)); e.Handled = true; return; } @@ -997,5 +994,48 @@ namespace ICSharpCode.ILSpy.AssemblyTree LoadAssemblies(fileNames, focusNode: focusNode); } + + private void ApplySessionSettings(object? sender, ApplySessionSettingsEventArgs e) + { + var settings = e.SessionSettings; + + settings.ActiveAssemblyList = AssemblyList.ListName; + settings.ActiveTreeViewPath = SelectedPath; + settings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(SelectedItem); + } + + private static string? GetAutoLoadedAssemblyNode(SharpTreeNode? node) + { + var assemblyTreeNode = node? + .AncestorsAndSelf() + .OfType() + .FirstOrDefault(); + + var loadedAssembly = assemblyTreeNode?.LoadedAssembly; + + return loadedAssembly is not { IsLoaded: true, IsAutoLoaded: true } + ? null + : loadedAssembly.FileName; + } + + private void ActiveTabPageChanged(object? sender, ActiveTabPageChangedEventArgs e) + { + if (e.ViewState is not { } state) + return; + + if (state.DecompiledNodes != null) + { + SelectNodes(state.DecompiledNodes); + } + else + { + NavigateTo(new(state.ViewedUri, null)); + } + + } + private void ResetLayout(object? sender, ResetLayoutEventArgs e) + { + RefreshDecompiledView(); + } } } diff --git a/ILSpy/Commands/CheckForUpdatesCommand.cs b/ILSpy/Commands/CheckForUpdatesCommand.cs index 54fb1ac8a..a9cb1033a 100644 --- a/ILSpy/Commands/CheckForUpdatesCommand.cs +++ b/ILSpy/Commands/CheckForUpdatesCommand.cs @@ -20,17 +20,16 @@ using System.Composition; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.Updates; namespace ICSharpCode.ILSpy { [ExportMainMenuCommand(ParentMenuID = nameof(Resources._Help), Header = nameof(Resources._CheckUpdates), MenuOrder = 5000)] [Shared] - sealed class CheckForUpdatesCommand(SettingsService settingsService) : SimpleCommand + sealed class CheckForUpdatesCommand : SimpleCommand { - public override async void Execute(object parameter) + public override void Execute(object parameter) { - await App.Current.MainWindow.ShowMessageIfUpdatesAvailableAsync(settingsService.GetSettings(), forceCheck: true); + MessageBus.Send(this, new CheckIfUpdateAvailableEventArgs(notify: true)); } } } diff --git a/ILSpy/Commands/ExitCommand.cs b/ILSpy/Commands/ExitCommand.cs index 67f698bfe..91483d829 100644 --- a/ILSpy/Commands/ExitCommand.cs +++ b/ILSpy/Commands/ExitCommand.cs @@ -23,11 +23,11 @@ namespace ICSharpCode.ILSpy { [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.E_xit), MenuOrder = 99999, MenuCategory = nameof(Resources.Exit))] [Shared] - sealed class ExitCommand : SimpleCommand + sealed class ExitCommand(MainWindow mainWindow) : SimpleCommand { public override void Execute(object parameter) { - App.Current.MainWindow.Close(); + mainWindow.Close(); } } } \ No newline at end of file diff --git a/ILSpy/Commands/ManageAssemblyListsCommand.cs b/ILSpy/Commands/ManageAssemblyListsCommand.cs index 8172f2182..02eb5284c 100644 --- a/ILSpy/Commands/ManageAssemblyListsCommand.cs +++ b/ILSpy/Commands/ManageAssemblyListsCommand.cs @@ -18,6 +18,8 @@ using System.Composition; +using System.Windows; +using System.Windows.Data; using ICSharpCode.ILSpy.Properties; @@ -25,13 +27,21 @@ namespace ICSharpCode.ILSpy { [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.ManageAssembly_Lists), MenuIcon = "Images/AssemblyList", MenuCategory = nameof(Resources.Open), MenuOrder = 1.7)] [Shared] - sealed class ManageAssemblyListsCommand(SettingsService settingsService) : SimpleCommand + sealed class ManageAssemblyListsCommand(SettingsService settingsService) : SimpleCommand, IProvideParameterBinding { public override void Execute(object parameter) { - ManageAssemblyListsDialog dlg = new ManageAssemblyListsDialog(settingsService); - dlg.Owner = App.Current.MainWindow; + ManageAssemblyListsDialog dlg = new(settingsService) { + Owner = parameter as Window + }; + dlg.ShowDialog(); } + + public Binding ParameterBinding => new() { + RelativeSource = new(RelativeSourceMode.FindAncestor) { + AncestorType = typeof(Window) + } + }; } } diff --git a/ILSpy/Commands/OpenFromGacCommand.cs b/ILSpy/Commands/OpenFromGacCommand.cs index ba65bfe21..b66e1c644 100644 --- a/ILSpy/Commands/OpenFromGacCommand.cs +++ b/ILSpy/Commands/OpenFromGacCommand.cs @@ -26,15 +26,8 @@ namespace ICSharpCode.ILSpy { [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.OpenFrom_GAC), MenuIcon = "Images/AssemblyListGAC", MenuCategory = nameof(Resources.Open), MenuOrder = 1)] [Shared] - sealed class OpenFromGacCommand : SimpleCommand + sealed class OpenFromGacCommand(AssemblyTreeModel assemblyTreeModel, MainWindow mainWindow) : SimpleCommand { - private readonly AssemblyTreeModel assemblyTreeModel; - - public OpenFromGacCommand(AssemblyTreeModel assemblyTreeModel) - { - this.assemblyTreeModel = assemblyTreeModel; - } - public override bool CanExecute(object parameter) { return AppEnvironment.IsWindows; @@ -42,8 +35,8 @@ namespace ICSharpCode.ILSpy public override void Execute(object parameter) { - OpenFromGacDialog dlg = new OpenFromGacDialog { - Owner = App.Current.MainWindow + OpenFromGacDialog dlg = new() { + Owner = mainWindow }; if (dlg.ShowDialog() == true) diff --git a/ILSpy/Commands/SearchMsdnContextMenuEntry.cs b/ILSpy/Commands/SearchMsdnContextMenuEntry.cs index 3dfa67032..c6a8babad 100644 --- a/ILSpy/Commands/SearchMsdnContextMenuEntry.cs +++ b/ILSpy/Commands/SearchMsdnContextMenuEntry.cs @@ -136,7 +136,7 @@ namespace ICSharpCode.ILSpy address = address.ToLower(); if (!string.IsNullOrEmpty(address)) - MainWindow.OpenLink(address); + GlobalUtils.OpenLink(address); } } } \ No newline at end of file diff --git a/ILSpy/Commands/SimpleCommand.cs b/ILSpy/Commands/SimpleCommand.cs index 67c7f517e..19c1c7c9e 100644 --- a/ILSpy/Commands/SimpleCommand.cs +++ b/ILSpy/Commands/SimpleCommand.cs @@ -17,7 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.ComponentModel; +using System.Windows.Data; using System.Windows.Input; namespace ICSharpCode.ILSpy @@ -37,38 +37,8 @@ namespace ICSharpCode.ILSpy } } - public abstract class ToggleableCommand : ICommand, INotifyPropertyChanged + public interface IProvideParameterBinding { - private bool isChecked; - - public event EventHandler CanExecuteChanged { - add { CommandManager.RequerySuggested += value; } - remove { CommandManager.RequerySuggested -= value; } - } - - public event PropertyChangedEventHandler PropertyChanged; - - void ICommand.Execute(object parameter) - { - IsChecked = Execute(parameter); - } - - public bool IsChecked { - get => isChecked; - set { - if (isChecked != value) - { - isChecked = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsChecked))); - } - } - } - - public abstract bool Execute(object parameter); - - public virtual bool CanExecute(object parameter) - { - return true; - } + Binding ParameterBinding { get; } } } diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index 938020117..d92b92847 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -33,7 +33,6 @@ using AvalonDock.Layout.Serialization; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.ILSpy.Analyzers; -using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.ViewModels; @@ -49,25 +48,18 @@ namespace ICSharpCode.ILSpy.Docking public class DockWorkspace : ObservableObject, ILayoutUpdateStrategy { private readonly IExportProvider exportProvider; - - private SettingsService SettingsService { get; } - - private LanguageService LanguageService => exportProvider.GetExportedValue(); - - private SessionSettings SessionSettings { get; } private readonly ObservableCollection tabPages = []; - private DockingManager DockingManager => exportProvider.GetExportedValue(); + readonly SessionSettings sessionSettings; - private AssemblyTreeModel AssemblyTreeModel => exportProvider.GetExportedValue(); + private DockingManager DockingManager => exportProvider.GetExportedValue(); public DockWorkspace(SettingsService settingsService, IExportProvider exportProvider) { this.exportProvider = exportProvider; - - SettingsService = settingsService; - SessionSettings = settingsService.SessionSettings; + + sessionSettings = settingsService.SessionSettings; this.tabPages.CollectionChanged += TabPages_CollectionChanged; TabPages = new(tabPages); @@ -77,10 +69,9 @@ namespace ICSharpCode.ILSpy.Docking private void CurrentAssemblyList_Changed(object sender, NotifyCollectionChangedEventArgs e) { - if (e.OldItems == null) - { + if (e.OldItems is not { } oldItems) return; - } + foreach (var tab in tabPages.ToArray()) { var state = tab.GetState(); @@ -91,7 +82,7 @@ namespace ICSharpCode.ILSpy.Docking bool found = decompiledNodes .Select(node => node.Ancestors().OfType().LastOrDefault()) .ExceptNullItems() - .Any(assemblyNode => !e.OldItems.Contains(assemblyNode.LoadedAssembly)); + .Any(assemblyNode => !oldItems.Contains(assemblyNode.LoadedAssembly)); if (!found) { @@ -127,7 +118,7 @@ namespace ICSharpCode.ILSpy.Docking public void AddTabPage(TabPageModel tabPage = null) { - tabPages.Add(tabPage ?? new TabPageModel(AssemblyTreeModel, SettingsService, LanguageService)); + tabPages.Add(tabPage ?? exportProvider.GetExportedValue()); } public ReadOnlyObservableCollection TabPages { get; } @@ -169,22 +160,13 @@ namespace ICSharpCode.ILSpy.Docking } set { if (!SetProperty(ref activeTabPage, value)) - { return; - } var state = value?.GetState(); - if (state != null) - { - if (state.DecompiledNodes != null) - { - AssemblyTreeModel.SelectNodes(state.DecompiledNodes); - } - else - { - AssemblyTreeModel.NavigateTo(new(state.ViewedUri, null)); - } - } + if (state == null) + return; + + MessageBus.Send(this, new ActiveTabPageChangedEventArgs(value?.GetState())); } } @@ -195,15 +177,18 @@ namespace ICSharpCode.ILSpy.Docking public void InitializeLayout() { - // Make sure there is at least one tab open - AddTabPage(); + if (tabPages.Count == 0) + { + // Make sure there is at least one tab open + AddTabPage(); + } DockingManager.LayoutUpdateStrategy = this; XmlLayoutSerializer serializer = new XmlLayoutSerializer(DockingManager); serializer.LayoutSerializationCallback += LayoutSerializationCallback; try { - SessionSettings.DockLayout.Deserialize(serializer); + sessionSettings.DockLayout.Deserialize(serializer); } finally { @@ -260,9 +245,10 @@ namespace ICSharpCode.ILSpy.Docking pane.IsVisible = false; } CloseAllTabs(); - SessionSettings.DockLayout.Reset(); + sessionSettings.DockLayout.Reset(); InitializeLayout(); - App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, AssemblyTreeModel.RefreshDecompiledView); + + App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, () => MessageBus.Send(this, new ResetLayoutEventArgs())); } static readonly PropertyInfo previousContainerProperty = typeof(LayoutContent).GetProperty("PreviousContainer", BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/ILSpy/Languages/Language.cs b/ILSpy/Languages/Language.cs index 59511f9cb..c0ef74643 100644 --- a/ILSpy/Languages/Language.cs +++ b/ILSpy/Languages/Language.cs @@ -44,11 +44,11 @@ namespace ICSharpCode.ILSpy /// public abstract class Language : ILanguage { - public static SettingsService SettingsService { get; } = App.ExportProvider.GetExportedValue(); + protected static SettingsService SettingsService { get; } = App.ExportProvider.GetExportedValue(); - public static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue(); + protected static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue(); - public static ICollection ResourceFileHandlers { get; } = App.ExportProvider.GetExportedValues().ToArray(); + protected static ICollection ResourceFileHandlers { get; } = App.ExportProvider.GetExportedValues().ToArray(); /// /// Gets the name of the language (as shown in the UI) diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 2aece0f63..177dbe951 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -20,6 +20,7 @@ xmlns:viewModels="clr-namespace:ICSharpCode.ILSpy.ViewModels" xmlns:composition="urn:TomsToolbox.Composition" xmlns:commands="clr-namespace:ICSharpCode.ILSpy.Commands" + xmlns:analyzers="clr-namespace:ICSharpCode.ILSpy.Analyzers" d:DataContext="{d:DesignInstance local:MainWindowViewModel}"> @@ -38,7 +39,7 @@ - + @@ -49,8 +50,9 @@ - - + + + @@ -62,7 +64,7 @@