Browse Source

Refactoring:

- Decouple services to reduce circular dependencies
- Move update panel to a separate control
- Remove unrelated methods from MainWindow
pull/3314/head
tom-englert 7 months ago
parent
commit
a24e0f96c8
  1. 5
      ICSharpCode.ILSpyX/Analyzers/AnalyzerScope.cs
  2. 1
      ICSharpCode.ILSpyX/Analyzers/IAnalyzer.cs
  3. 43
      ILSpy/AboutPage.cs
  4. 2
      ILSpy/Analyzers/AnalyzeCommand.cs
  5. 4
      ILSpy/Analyzers/AnalyzerSearchTreeNode.cs
  6. 6
      ILSpy/Analyzers/AnalyzerTreeNode.cs
  7. 17
      ILSpy/App.xaml.cs
  8. 86
      ILSpy/AssemblyTree/AssemblyTreeModel.cs
  9. 7
      ILSpy/Commands/CheckForUpdatesCommand.cs
  10. 4
      ILSpy/Commands/ExitCommand.cs
  11. 16
      ILSpy/Commands/ManageAssemblyListsCommand.cs
  12. 13
      ILSpy/Commands/OpenFromGacCommand.cs
  13. 2
      ILSpy/Commands/SearchMsdnContextMenuEntry.cs
  14. 36
      ILSpy/Commands/SimpleCommand.cs
  15. 56
      ILSpy/Docking/DockWorkspace.cs
  16. 6
      ILSpy/Languages/Language.cs
  17. 29
      ILSpy/MainWindow.xaml
  18. 154
      ILSpy/MainWindow.xaml.cs
  19. 6
      ILSpy/MainWindowViewModel.cs
  20. 5
      ILSpy/Options/OptionsDialog.xaml.cs
  21. 8
      ILSpy/Search/SearchPane.xaml.cs
  22. 6
      ILSpy/Search/SearchPaneModel.cs
  23. 47
      ILSpy/TextView/DecompilerTextView.cs
  24. 6
      ILSpy/TextView/DocumentationUIBuilder.cs
  25. 2
      ILSpy/TreeNodes/AssemblyListTreeNode.cs
  26. 6
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  27. 12
      ILSpy/TreeNodes/ILSpyTreeNode.cs
  28. 8
      ILSpy/Updates/UpdateService.cs
  29. 52
      ILSpy/Util/GlobalUtils.cs
  30. 52
      ILSpy/Util/MenuService.cs
  31. 48
      ILSpy/Util/MessageBus.cs
  32. 15
      ILSpy/ViewModels/ManageAssemblyListsViewModel.cs
  33. 5
      ILSpy/ViewModels/PaneModel.cs
  34. 62
      ILSpy/ViewModels/TabPageModel.cs
  35. 100
      ILSpy/ViewModels/UpdatePanelViewModel.cs
  36. 22
      ILSpy/Views/UpdatePanel.xaml
  37. 24
      ILSpy/Views/UpdatePanel.xaml.cs

5
ICSharpCode.ILSpyX/Analyzers/AnalyzerScope.cs

@ -40,17 +40,14 @@ namespace ICSharpCode.ILSpyX.Analyzers
/// </summary> /// </summary>
public bool IsLocal { get; } public bool IsLocal { get; }
public AssemblyList AssemblyList { get; }
public ISymbol AnalyzedSymbol { get; } public ISymbol AnalyzedSymbol { get; }
public ITypeDefinition TypeScope => typeScope; public ITypeDefinition TypeScope => typeScope;
Accessibility effectiveAccessibility; readonly Accessibility effectiveAccessibility;
public AnalyzerScope(AssemblyList assemblyList, IEntity entity) public AnalyzerScope(AssemblyList assemblyList, IEntity entity)
{ {
AssemblyList = assemblyList;
assemblyListSnapshot = assemblyList.GetSnapshot(); assemblyListSnapshot = assemblyList.GetSnapshot();
AnalyzedSymbol = entity; AnalyzedSymbol = entity;
DetermineEffectiveAccessibility(entity, out typeScope, out effectiveAccessibility); DetermineEffectiveAccessibility(entity, out typeScope, out effectiveAccessibility);

1
ICSharpCode.ILSpyX/Analyzers/IAnalyzer.cs

@ -42,6 +42,7 @@ namespace ICSharpCode.ILSpyX.Analyzers
public interface IAnalyzerMetadata public interface IAnalyzerMetadata
{ {
string Header { get; } string Header { get; }
int Order { get; } int Order { get; }
} }
} }

43
ILSpy/AboutPage.cs

@ -26,36 +26,42 @@ using System.Windows.Controls;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Navigation;
using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.Themes;
using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpy.Updates;
using ICSharpCode.ILSpy.ViewModels;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._Help), Header = nameof(Resources._About), MenuOrder = 99999)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._Help), Header = nameof(Resources._About), MenuOrder = 99999)]
[Shared] [Shared]
public sealed class AboutPageCommand(AssemblyTreeModel assemblyTreeModel) : SimpleCommand public sealed class AboutPage : SimpleCommand
{ {
readonly SettingsService settingsService;
readonly IEnumerable<IAboutPageAddition> aboutPageAdditions;
public AboutPage(SettingsService settingsService, IEnumerable<IAboutPageAddition> aboutPageAdditions)
{
this.settingsService = settingsService;
this.aboutPageAdditions = aboutPageAdditions;
MessageBus<ShowAboutPageEventArgs>.Subscribers += (_, e) => ShowAboutPage(e.TabPage);
}
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
assemblyTreeModel.NavigateTo( MessageBus.Send(this, new NavigateToEventArgs(new(new("resource://aboutpage"), null), inNewTabPage: true));
new RequestNavigateEventArgs(new Uri("resource://aboutpage"), null),
inNewTabPage: true
);
} }
}
[Export] private void ShowAboutPage(TabPageModel tabPage)
[Shared] {
public sealed class AboutPage(IEnumerable<IAboutPageAddition> aboutPageAdditions, SettingsService settingsService) tabPage.ShowTextView(Display);
{ }
public void Display(DecompilerTextView textView)
private void Display(DecompilerTextView textView)
{ {
AvalonEditTextOutput output = new AvalonEditTextOutput() { AvalonEditTextOutput output = new AvalonEditTextOutput() {
Title = Resources.About, Title = Resources.About,
@ -72,14 +78,14 @@ namespace ICSharpCode.ILSpy
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
Orientation = Orientation.Horizontal Orientation = Orientation.Horizontal
}; };
if (NotifyOfUpdatesStrategy.LatestAvailableVersion == null) if (UpdateService.LatestAvailableVersion == null)
{ {
AddUpdateCheckButton(stackPanel, textView); AddUpdateCheckButton(stackPanel, textView);
} }
else else
{ {
// we already retrieved the latest version sometime earlier // we already retrieved the latest version sometime earlier
ShowAvailableVersion(NotifyOfUpdatesStrategy.LatestAvailableVersion, stackPanel); ShowAvailableVersion(UpdateService.LatestAvailableVersion, stackPanel);
} }
CheckBox checkBox = new() { CheckBox checkBox = new() {
Margin = new Thickness(4), Margin = new Thickness(4),
@ -104,8 +110,7 @@ namespace ICSharpCode.ILSpy
{ {
using (StreamReader r = new StreamReader(s)) using (StreamReader r = new StreamReader(s))
{ {
string line; while (r.ReadLine() is { } line)
while ((line = r.ReadLine()) != null)
{ {
output.WriteLine(line); output.WriteLine(line);
} }
@ -166,7 +171,7 @@ namespace ICSharpCode.ILSpy
try try
{ {
AvailableVersionInfo vInfo = await NotifyOfUpdatesStrategy.GetLatestVersionAsync(); AvailableVersionInfo vInfo = await UpdateService.GetLatestVersionAsync();
stackPanel.Children.Clear(); stackPanel.Children.Clear();
ShowAvailableVersion(vInfo, stackPanel); ShowAvailableVersion(vInfo, stackPanel);
} }
@ -209,7 +214,7 @@ namespace ICSharpCode.ILSpy
button.Content = Resources.Download; button.Content = Resources.Download;
button.Cursor = Cursors.Arrow; button.Cursor = Cursors.Arrow;
button.Click += delegate { button.Click += delegate {
MainWindow.OpenLink(availableVersion.DownloadUrl); GlobalUtils.OpenLink(availableVersion.DownloadUrl);
}; };
stackPanel.Children.Add(button); stackPanel.Children.Add(button);
} }

2
ILSpy/Analyzers/AnalyzeCommand.cs

@ -73,6 +73,8 @@ namespace ICSharpCode.ILSpy.Analyzers
} }
} }
[Export]
[Shared]
public sealed class AnalyzeCommand(AssemblyTreeModel assemblyTreeModel, AnalyzerTreeViewModel analyzerTreeViewModel) : SimpleCommand public sealed class AnalyzeCommand(AssemblyTreeModel assemblyTreeModel, AnalyzerTreeViewModel analyzerTreeViewModel) : SimpleCommand
{ {
public override bool CanExecute(object parameter) public override bool CanExecute(object parameter)

4
ILSpy/Analyzers/AnalyzerSearchTreeNode.cs

@ -58,10 +58,10 @@ namespace ICSharpCode.ILSpy.Analyzers
{ {
if (symbol is IEntity) if (symbol is IEntity)
{ {
var context = new AnalyzerContext() { var context = new AnalyzerContext {
CancellationToken = ct, CancellationToken = ct,
Language = Language, Language = Language,
AssemblyList = AssemblyTreeModel.AssemblyList AssemblyList = AssemblyList
}; };
var results = analyzer.Analyze(symbol, context).Select(SymbolTreeNodeFactory); var results = analyzer.Analyze(symbol, context).Select(SymbolTreeNodeFactory);
if (context.SortResults) if (context.SortResults)

6
ILSpy/Analyzers/AnalyzerTreeNode.cs

@ -30,7 +30,9 @@ namespace ICSharpCode.ILSpy.Analyzers
{ {
public abstract class AnalyzerTreeNode : SharpTreeNode public abstract class AnalyzerTreeNode : SharpTreeNode
{ {
public static Language Language => App.ExportProvider.GetExportedValue<LanguageService>().Language; protected static Language Language => App.ExportProvider.GetExportedValue<LanguageService>().Language;
protected static AssemblyList AssemblyList => App.ExportProvider.GetExportedValue<AssemblyList>();
public override bool CanDelete() public override bool CanDelete()
{ {
@ -47,8 +49,6 @@ namespace ICSharpCode.ILSpy.Analyzers
DeleteCore(); DeleteCore();
} }
public static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue<AssemblyTreeModel>();
public static ICollection<IExport<IAnalyzer, IAnalyzerMetadata>> Analyzers => App.ExportProvider public static ICollection<IExport<IAnalyzer, IAnalyzerMetadata>> Analyzers => App.ExportProvider
.GetExports<IAnalyzer, IAnalyzerMetadata>("Analyzer") .GetExports<IAnalyzer, IAnalyzerMetadata>("Analyzer")
.OrderBy(item => item.Metadata?.Order) .OrderBy(item => item.Metadata?.Order)

17
ILSpy/App.xaml.cs

@ -25,8 +25,6 @@ using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Documents;
using System.Windows.Navigation;
using System.Windows.Threading; using System.Windows.Threading;
using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.AppEnv;
@ -115,9 +113,6 @@ namespace ICSharpCode.ILSpy
Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new(sessionSettings.CurrentCulture); Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new(sessionSettings.CurrentCulture);
} }
EventManager.RegisterClassHandler(typeof(Window),
Hyperlink.RequestNavigateEvent,
new RequestNavigateEventHandler(Window_RequestNavigate));
ILSpyTraceListener.Install(); ILSpyTraceListener.Install();
if (CommandLineArguments.ArgumentsParser.IsShowingInformation) if (CommandLineArguments.ArgumentsParser.IsShowingInformation)
@ -138,7 +133,7 @@ namespace ICSharpCode.ILSpy
public new MainWindow MainWindow { public new MainWindow MainWindow {
get => (MainWindow)base.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<AssemblyTreeModel>().HandleSingleInstanceCommandLineArguments(e.Args).HandleExceptions(); private static void SingleInstance_NewInstanceDetected(object sender, NewInstanceEventArgs e) => ExportProvider.GetExportedValue<AssemblyTreeModel>().HandleSingleInstanceCommandLineArguments(e.Args).HandleExceptions();
@ -152,7 +147,7 @@ namespace ICSharpCode.ILSpy
return context.LoadFromAssemblyPath(assemblyFileName); return context.LoadFromAssemblyPath(assemblyFileName);
} }
private static bool InitializeDependencyInjection(SettingsService settingsService) private bool InitializeDependencyInjection(SettingsService settingsService)
{ {
// Add custom logic for resolution of dependencies. // Add custom logic for resolution of dependencies.
// This necessary because the AssemblyLoadContext.LoadFromAssemblyPath and related methods, // This necessary because the AssemblyLoadContext.LoadFromAssemblyPath and related methods,
@ -193,11 +188,14 @@ namespace ICSharpCode.ILSpy
services.AddSingleton(_ => ExportProvider); services.AddSingleton(_ => ExportProvider);
// Add the docking manager // Add the docking manager
services.AddSingleton(serviceProvider => serviceProvider.GetService<MainWindow>().DockManager); services.AddSingleton(serviceProvider => serviceProvider.GetService<MainWindow>().DockManager);
services.AddTransient(serviceProvider => serviceProvider.GetService<AssemblyTreeModel>().AssemblyList);
var serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); var serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
ExportProvider = new ExportProviderAdapter(serviceProvider); ExportProvider = new ExportProviderAdapter(serviceProvider);
Exit += (_, _) => serviceProvider.Dispose();
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@ -274,10 +272,5 @@ namespace ICSharpCode.ILSpy
} }
} }
#endregion #endregion
void Window_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
ExportProvider.GetExportedValue<AssemblyTreeModel>().NavigateTo(e);
}
} }
} }

86
ILSpy/AssemblyTree/AssemblyTreeModel.cs

@ -28,6 +28,7 @@ using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335; using System.Reflection.Metadata.Ecma335;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Threading; using System.Windows.Threading;
@ -37,15 +38,12 @@ using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.Search;
using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpy.Updates;
using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.Settings;
using ICSharpCode.ILSpyX.TreeView; using ICSharpCode.ILSpyX.TreeView;
using TomsToolbox.Composition; using TomsToolbox.Composition;
@ -58,7 +56,6 @@ namespace ICSharpCode.ILSpy.AssemblyTree
{ {
[ExportToolPane] [ExportToolPane]
[Shared] [Shared]
[Export]
public class AssemblyTreeModel : ToolPaneModel public class AssemblyTreeModel : ToolPaneModel
{ {
public const string PaneContentId = "assemblyListPane"; public const string PaneContentId = "assemblyListPane";
@ -69,16 +66,12 @@ namespace ICSharpCode.ILSpy.AssemblyTree
private readonly NavigationHistory<NavigationState> history = new(); private readonly NavigationHistory<NavigationState> history = new();
private bool isNavigatingHistory; private bool isNavigatingHistory;
private readonly AboutPage aboutPage;
private readonly SearchPaneModel searchPaneModel;
private readonly SettingsService settingsService; private readonly SettingsService settingsService;
private readonly LanguageService languageService; private readonly LanguageService languageService;
private readonly IExportProvider exportProvider; 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.settingsService = settingsService;
this.languageService = languageService; this.languageService = languageService;
this.exportProvider = exportProvider; this.exportProvider = exportProvider;
@ -90,8 +83,18 @@ namespace ICSharpCode.ILSpy.AssemblyTree
MessageBus<NavigateToReferenceEventArgs>.Subscribers += JumpToReference; MessageBus<NavigateToReferenceEventArgs>.Subscribers += JumpToReference;
MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e);
MessageBus<ApplySessionSettingsEventArgs>.Subscribers += ApplySessionSettings;
MessageBus<ActiveTabPageChangedEventArgs>.Subscribers += ActiveTabPageChanged;
MessageBus<ResetLayoutEventArgs>.Subscribers += ResetLayout;
MessageBus<NavigateToEventArgs>.Subscribers += (_, e) => NavigateTo(e.Request, e.InNewTabPage);
MessageBus<MainWindowLoadedEventArgs>.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(); AssemblyList = settingsService.CreateEmptyAssemblyList();
} }
@ -183,8 +186,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
if (args.Search != null) if (args.Search != null)
{ {
this.searchPaneModel.SearchTerm = args.Search; MessageBus.Send(this, new ShowSearchPageEventArgs(args.Search));
this.searchPaneModel.Show();
} }
} }
@ -301,11 +303,11 @@ namespace ICSharpCode.ILSpy.AssemblyTree
SelectNode(node); SelectNode(node);
// only if not showing the about page, perform the update check: // only if not showing the about page, perform the update check:
await App.Current.MainWindow.ShowMessageIfUpdatesAvailableAsync(updateSettings); MessageBus.Send(this, new CheckIfUpdateAvailableEventArgs());
} }
else 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.CollectionChanged -= assemblyList_CollectionChanged;
AssemblyList = assemblyList; AssemblyList = assemblyList;
assemblyList.CollectionChanged += assemblyList_CollectionChanged; assemblyList.CollectionChanged += assemblyList_CollectionChanged;
assemblyListTreeNode = new(assemblyList) { assemblyListTreeNode = new(assemblyList) {
@ -655,7 +656,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
switch (reference) switch (reference)
{ {
case Decompiler.Disassembler.OpCodeInfo opCode: case Decompiler.Disassembler.OpCodeInfo opCode:
MainWindow.OpenLink(opCode.Link); GlobalUtils.OpenLink(opCode.Link);
break; break;
case EntityReference unresolvedEntity: case EntityReference unresolvedEntity:
string protocol = unresolvedEntity.Protocol; string protocol = unresolvedEntity.Protocol;
@ -817,11 +818,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
public LanguageVersion? CurrentLanguageVersion => languageService.LanguageVersion; public LanguageVersion? CurrentLanguageVersion => languageService.LanguageVersion;
public IEnumerable<ILSpyTreeNode> SelectedNodes { public IEnumerable<ILSpyTreeNode> SelectedNodes => GetTopLevelSelection().OfType<ILSpyTreeNode>();
get {
return GetTopLevelSelection().OfType<ILSpyTreeNode>();
}
}
#endregion #endregion
@ -856,7 +853,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
public bool CanNavigateForward => history.CanNavigateForward; 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") if (e.Uri.Scheme == "resource")
{ {
@ -868,7 +865,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
if (e.Uri.Host == "aboutpage") if (e.Uri.Host == "aboutpage")
{ {
RecordHistory(); RecordHistory();
DockWorkspace.ActiveTabPage.ShowTextView(aboutPage.Display); MessageBus.Send(this, new ShowAboutPageEventArgs(DockWorkspace.ActiveTabPage));
e.Handled = true; e.Handled = true;
return; return;
} }
@ -997,5 +994,48 @@ namespace ICSharpCode.ILSpy.AssemblyTree
LoadAssemblies(fileNames, focusNode: focusNode); 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<AssemblyTreeNode>()
.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();
}
} }
} }

7
ILSpy/Commands/CheckForUpdatesCommand.cs

@ -20,17 +20,16 @@
using System.Composition; using System.Composition;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.Updates;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._Help), Header = nameof(Resources._CheckUpdates), MenuOrder = 5000)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._Help), Header = nameof(Resources._CheckUpdates), MenuOrder = 5000)]
[Shared] [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<UpdateSettings>(), forceCheck: true); MessageBus.Send(this, new CheckIfUpdateAvailableEventArgs(notify: true));
} }
} }
} }

4
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))] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.E_xit), MenuOrder = 99999, MenuCategory = nameof(Resources.Exit))]
[Shared] [Shared]
sealed class ExitCommand : SimpleCommand sealed class ExitCommand(MainWindow mainWindow) : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
App.Current.MainWindow.Close(); mainWindow.Close();
} }
} }
} }

16
ILSpy/Commands/ManageAssemblyListsCommand.cs

@ -18,6 +18,8 @@
using System.Composition; using System.Composition;
using System.Windows;
using System.Windows.Data;
using ICSharpCode.ILSpy.Properties; 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)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.ManageAssembly_Lists), MenuIcon = "Images/AssemblyList", MenuCategory = nameof(Resources.Open), MenuOrder = 1.7)]
[Shared] [Shared]
sealed class ManageAssemblyListsCommand(SettingsService settingsService) : SimpleCommand sealed class ManageAssemblyListsCommand(SettingsService settingsService) : SimpleCommand, IProvideParameterBinding
{ {
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
ManageAssemblyListsDialog dlg = new ManageAssemblyListsDialog(settingsService); ManageAssemblyListsDialog dlg = new(settingsService) {
dlg.Owner = App.Current.MainWindow; Owner = parameter as Window
};
dlg.ShowDialog(); dlg.ShowDialog();
} }
public Binding ParameterBinding => new() {
RelativeSource = new(RelativeSourceMode.FindAncestor) {
AncestorType = typeof(Window)
}
};
} }
} }

13
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)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.OpenFrom_GAC), MenuIcon = "Images/AssemblyListGAC", MenuCategory = nameof(Resources.Open), MenuOrder = 1)]
[Shared] [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) public override bool CanExecute(object parameter)
{ {
return AppEnvironment.IsWindows; return AppEnvironment.IsWindows;
@ -42,8 +35,8 @@ namespace ICSharpCode.ILSpy
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
OpenFromGacDialog dlg = new OpenFromGacDialog { OpenFromGacDialog dlg = new() {
Owner = App.Current.MainWindow Owner = mainWindow
}; };
if (dlg.ShowDialog() == true) if (dlg.ShowDialog() == true)

2
ILSpy/Commands/SearchMsdnContextMenuEntry.cs

@ -136,7 +136,7 @@ namespace ICSharpCode.ILSpy
address = address.ToLower(); address = address.ToLower();
if (!string.IsNullOrEmpty(address)) if (!string.IsNullOrEmpty(address))
MainWindow.OpenLink(address); GlobalUtils.OpenLink(address);
} }
} }
} }

36
ILSpy/Commands/SimpleCommand.cs

@ -17,7 +17,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.ComponentModel; using System.Windows.Data;
using System.Windows.Input; using System.Windows.Input;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
@ -37,38 +37,8 @@ namespace ICSharpCode.ILSpy
} }
} }
public abstract class ToggleableCommand : ICommand, INotifyPropertyChanged public interface IProvideParameterBinding
{ {
private bool isChecked; Binding ParameterBinding { get; }
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;
}
} }
} }

56
ILSpy/Docking/DockWorkspace.cs

@ -33,7 +33,6 @@ using AvalonDock.Layout.Serialization;
using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.ILSpy.Analyzers; using ICSharpCode.ILSpy.Analyzers;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.Search;
using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpy.ViewModels;
@ -49,25 +48,18 @@ namespace ICSharpCode.ILSpy.Docking
public class DockWorkspace : ObservableObject, ILayoutUpdateStrategy public class DockWorkspace : ObservableObject, ILayoutUpdateStrategy
{ {
private readonly IExportProvider exportProvider; private readonly IExportProvider exportProvider;
private SettingsService SettingsService { get; }
private LanguageService LanguageService => exportProvider.GetExportedValue<LanguageService>();
private SessionSettings SessionSettings { get; }
private readonly ObservableCollection<TabPageModel> tabPages = []; private readonly ObservableCollection<TabPageModel> tabPages = [];
private DockingManager DockingManager => exportProvider.GetExportedValue<DockingManager>(); readonly SessionSettings sessionSettings;
private AssemblyTreeModel AssemblyTreeModel => exportProvider.GetExportedValue<AssemblyTreeModel>(); private DockingManager DockingManager => exportProvider.GetExportedValue<DockingManager>();
public DockWorkspace(SettingsService settingsService, IExportProvider exportProvider) public DockWorkspace(SettingsService settingsService, IExportProvider exportProvider)
{ {
this.exportProvider = exportProvider; this.exportProvider = exportProvider;
SettingsService = settingsService; sessionSettings = settingsService.SessionSettings;
SessionSettings = settingsService.SessionSettings;
this.tabPages.CollectionChanged += TabPages_CollectionChanged; this.tabPages.CollectionChanged += TabPages_CollectionChanged;
TabPages = new(tabPages); TabPages = new(tabPages);
@ -77,10 +69,9 @@ namespace ICSharpCode.ILSpy.Docking
private void CurrentAssemblyList_Changed(object sender, NotifyCollectionChangedEventArgs e) private void CurrentAssemblyList_Changed(object sender, NotifyCollectionChangedEventArgs e)
{ {
if (e.OldItems == null) if (e.OldItems is not { } oldItems)
{
return; return;
}
foreach (var tab in tabPages.ToArray()) foreach (var tab in tabPages.ToArray())
{ {
var state = tab.GetState(); var state = tab.GetState();
@ -91,7 +82,7 @@ namespace ICSharpCode.ILSpy.Docking
bool found = decompiledNodes bool found = decompiledNodes
.Select(node => node.Ancestors().OfType<TreeNodes.AssemblyTreeNode>().LastOrDefault()) .Select(node => node.Ancestors().OfType<TreeNodes.AssemblyTreeNode>().LastOrDefault())
.ExceptNullItems() .ExceptNullItems()
.Any(assemblyNode => !e.OldItems.Contains(assemblyNode.LoadedAssembly)); .Any(assemblyNode => !oldItems.Contains(assemblyNode.LoadedAssembly));
if (!found) if (!found)
{ {
@ -127,7 +118,7 @@ namespace ICSharpCode.ILSpy.Docking
public void AddTabPage(TabPageModel tabPage = null) public void AddTabPage(TabPageModel tabPage = null)
{ {
tabPages.Add(tabPage ?? new TabPageModel(AssemblyTreeModel, SettingsService, LanguageService)); tabPages.Add(tabPage ?? exportProvider.GetExportedValue<TabPageModel>());
} }
public ReadOnlyObservableCollection<TabPageModel> TabPages { get; } public ReadOnlyObservableCollection<TabPageModel> TabPages { get; }
@ -169,22 +160,13 @@ namespace ICSharpCode.ILSpy.Docking
} }
set { set {
if (!SetProperty(ref activeTabPage, value)) if (!SetProperty(ref activeTabPage, value))
{
return; return;
}
var state = value?.GetState(); var state = value?.GetState();
if (state != null) if (state == null)
{ return;
if (state.DecompiledNodes != null)
{ MessageBus.Send(this, new ActiveTabPageChangedEventArgs(value?.GetState()));
AssemblyTreeModel.SelectNodes(state.DecompiledNodes);
}
else
{
AssemblyTreeModel.NavigateTo(new(state.ViewedUri, null));
}
}
} }
} }
@ -195,15 +177,18 @@ namespace ICSharpCode.ILSpy.Docking
public void InitializeLayout() public void InitializeLayout()
{ {
// Make sure there is at least one tab open if (tabPages.Count == 0)
AddTabPage(); {
// Make sure there is at least one tab open
AddTabPage();
}
DockingManager.LayoutUpdateStrategy = this; DockingManager.LayoutUpdateStrategy = this;
XmlLayoutSerializer serializer = new XmlLayoutSerializer(DockingManager); XmlLayoutSerializer serializer = new XmlLayoutSerializer(DockingManager);
serializer.LayoutSerializationCallback += LayoutSerializationCallback; serializer.LayoutSerializationCallback += LayoutSerializationCallback;
try try
{ {
SessionSettings.DockLayout.Deserialize(serializer); sessionSettings.DockLayout.Deserialize(serializer);
} }
finally finally
{ {
@ -260,9 +245,10 @@ namespace ICSharpCode.ILSpy.Docking
pane.IsVisible = false; pane.IsVisible = false;
} }
CloseAllTabs(); CloseAllTabs();
SessionSettings.DockLayout.Reset(); sessionSettings.DockLayout.Reset();
InitializeLayout(); 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); static readonly PropertyInfo previousContainerProperty = typeof(LayoutContent).GetProperty("PreviousContainer", BindingFlags.NonPublic | BindingFlags.Instance);

6
ILSpy/Languages/Language.cs

@ -44,11 +44,11 @@ namespace ICSharpCode.ILSpy
/// </remarks> /// </remarks>
public abstract class Language : ILanguage public abstract class Language : ILanguage
{ {
public static SettingsService SettingsService { get; } = App.ExportProvider.GetExportedValue<SettingsService>(); protected static SettingsService SettingsService { get; } = App.ExportProvider.GetExportedValue<SettingsService>();
public static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue<AssemblyTreeModel>(); protected static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue<AssemblyTreeModel>();
public static ICollection<IResourceFileHandler> ResourceFileHandlers { get; } = App.ExportProvider.GetExportedValues<IResourceFileHandler>().ToArray(); protected static ICollection<IResourceFileHandler> ResourceFileHandlers { get; } = App.ExportProvider.GetExportedValues<IResourceFileHandler>().ToArray();
/// <summary> /// <summary>
/// Gets the name of the language (as shown in the UI) /// Gets the name of the language (as shown in the UI)

29
ILSpy/MainWindow.xaml

@ -20,6 +20,7 @@
xmlns:viewModels="clr-namespace:ICSharpCode.ILSpy.ViewModels" xmlns:viewModels="clr-namespace:ICSharpCode.ILSpy.ViewModels"
xmlns:composition="urn:TomsToolbox.Composition" xmlns:composition="urn:TomsToolbox.Composition"
xmlns:commands="clr-namespace:ICSharpCode.ILSpy.Commands" xmlns:commands="clr-namespace:ICSharpCode.ILSpy.Commands"
xmlns:analyzers="clr-namespace:ICSharpCode.ILSpy.Analyzers"
d:DataContext="{d:DesignInstance local:MainWindowViewModel}"> d:DataContext="{d:DesignInstance local:MainWindowViewModel}">
<Window.Resources> <Window.Resources>
@ -38,7 +39,7 @@
</b:Interaction.Behaviors> </b:Interaction.Behaviors>
<Window.InputBindings> <Window.InputBindings>
<KeyBinding Key="R" Modifiers="Control" Command="{Binding AnalyzeCommand}" /> <KeyBinding Key="R" Modifiers="Control" Command="{composition:Import analyzers:AnalyzeCommand}" />
<KeyBinding Key="Z" Modifiers="Control" Command="{x:Static NavigationCommands.BrowseBack}" /> <KeyBinding Key="Z" Modifiers="Control" Command="{x:Static NavigationCommands.BrowseBack}" />
</Window.InputBindings> </Window.InputBindings>
@ -49,8 +50,9 @@
<DockPanel> <DockPanel>
<!-- Main menu --> <!-- Main menu -->
<Menu DockPanel.Dock="Top" Name="mainMenu" Height="23" KeyboardNavigation.TabNavigation="None"> <Menu DockPanel.Dock="Top" Name="mainMenu" Height="23" KeyboardNavigation.TabNavigation="None">
<MenuItem Header="{x:Static properties:Resources._File}" Tag="_File" /> <MenuItem Header="{x:Static properties:Resources._File}" Tag="_File">
<!-- contents of file menu are added using MEF --> <!-- content of file menu is added using MEF -->
</MenuItem>
<MenuItem Header="{x:Static properties:Resources._View}" Tag="_View"> <MenuItem Header="{x:Static properties:Resources._View}" Tag="_View">
<MenuItem Header="{x:Static properties:Resources.Show_publiconlyTypesMembers}" IsCheckable="True" <MenuItem Header="{x:Static properties:Resources.Show_publiconlyTypesMembers}" IsCheckable="True"
IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisPublicOnly}" /> IsChecked="{Binding SessionSettings.LanguageSettings.ApiVisPublicOnly}" />
@ -62,7 +64,7 @@
<MenuItem Header="{x:Static properties:Resources.Theme}" ItemsSource="{x:Static themes:ThemeManager.AllThemes}"> <MenuItem Header="{x:Static properties:Resources.Theme}" ItemsSource="{x:Static themes:ThemeManager.AllThemes}">
<MenuItem.ItemContainerStyle> <MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}"> <Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Command" Value="{composition:Import {x:Type commands:SetThemeCommand}}" /> <Setter Property="Command" Value="{composition:Import commands:SetThemeCommand}" />
<Setter Property="CommandParameter" Value="{Binding}" /> <Setter Property="CommandParameter" Value="{Binding}" />
<Setter Property="IsCheckable" Value="True" /> <Setter Property="IsCheckable" Value="True" />
<!-- Required by AvalonDock's MenuItem style to show the checkmark --> <!-- Required by AvalonDock's MenuItem style to show the checkmark -->
@ -86,7 +88,9 @@
IsChecked="{Binding SessionSettings.CurrentCulture, Converter={controls:CultureSelectionConverter}, ConverterParameter=zh-Hans}" /> IsChecked="{Binding SessionSettings.CurrentCulture, Converter={controls:CultureSelectionConverter}, ConverterParameter=zh-Hans}" />
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="{x:Static properties:Resources._Window}" Tag="_Window" /> <MenuItem Header="{x:Static properties:Resources._Window}" Tag="_Window" >
<!-- content of window menu is added using MEF -->
</MenuItem>
</Menu> </Menu>
<!-- ToolBar --> <!-- ToolBar -->
<ToolBar <ToolBar
@ -125,7 +129,8 @@
ToolTip="{x:Static properties:Resources.SelectAssemblyListDropdownTooltip}" ToolTip="{x:Static properties:Resources.SelectAssemblyListDropdownTooltip}"
SelectedItem="{Binding SessionSettings.ActiveAssemblyList}" /> SelectedItem="{Binding SessionSettings.ActiveAssemblyList}" />
</Grid> </Grid>
<Button Command="{composition:Import {x:Type local:ManageAssemblyListsCommand}}" <Button Command="{composition:Import local:ManageAssemblyListsCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
ToolTip="{x:Static properties:Resources.ManageAssemblyLists}"> ToolTip="{x:Static properties:Resources.ManageAssemblyLists}">
<Image Width="16" Height="16" Source="{controls:XamlResource Images/AssemblyList}" <Image Width="16" Height="16" Source="{controls:XamlResource Images/AssemblyList}"
Style="{StaticResource DarkModeAwareImageStyle}" /> Style="{StaticResource DarkModeAwareImageStyle}" />
@ -166,17 +171,7 @@
</Grid> </Grid>
</ToolBar> </ToolBar>
<!-- Update panel --> <!-- Update panel -->
<Border DockPanel.Dock="Top" BorderBrush="Black" BorderThickness="1" Name="updatePanel" Visibility="Collapsed"> <ContentControl DockPanel.Dock="Top" Content="{composition:Import viewModels:UpdatePanelViewModel}" />
<DockPanel KeyboardNavigation.TabNavigation="Contained">
<Button DockPanel.Dock="Right" Click="UpdatePanelCloseButtonClick" MinWidth="0">X</Button>
<StackPanel Orientation="Horizontal">
<TextBlock Name="updatePanelMessage" Margin="4,0" VerticalAlignment="Center"
Text="{x:Static properties:Resources.ILSpyVersionAvailable}" />
<Button Name="downloadOrCheckUpdateButton" Click="DownloadOrCheckUpdateButtonClick"
Content="{x:Static properties:Resources.Download}" />
</StackPanel>
</DockPanel>
</Border>
<!-- Status bar --> <!-- Status bar -->
<StatusBar x:Name="statusBar" DockPanel.Dock="Bottom" Height="26" Visibility="Collapsed"> <StatusBar x:Name="statusBar" DockPanel.Dock="Bottom" Height="26" Visibility="Collapsed">
<StatusBarItem DockPanel.Dock="Right"> <StatusBarItem DockPanel.Dock="Right">

154
ILSpy/MainWindow.xaml.cs

@ -17,27 +17,15 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Composition; using System.Composition;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading; using System.Windows.Threading;
using AvalonDock.Layout.Serialization;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.Updates;
using ICSharpCode.ILSpyX.FileLoaders;
using ICSharpCode.ILSpyX.Settings;
using ICSharpCode.ILSpyX.TreeView;
using Screen = System.Windows.Forms.Screen; using Screen = System.Windows.Forms.Screen;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
@ -50,16 +38,10 @@ namespace ICSharpCode.ILSpy
#pragma warning disable MEF003 // Main window is a singleton #pragma warning disable MEF003 // Main window is a singleton
partial class MainWindow partial class MainWindow
{ {
private readonly AssemblyTreeModel assemblyTreeModel;
private readonly IEnumerable<IFileLoader> fileLoaders;
private readonly MenuService menuService;
private readonly SettingsService settingsService; private readonly SettingsService settingsService;
public MainWindow(MainWindowViewModel mainWindowViewModel, AssemblyTreeModel assemblyTreeModel, IEnumerable<IFileLoader> fileLoaders, MenuService menuService, SettingsService settingsService) public MainWindow(MainWindowViewModel mainWindowViewModel, MenuService menuService, SettingsService settingsService)
{ {
this.assemblyTreeModel = assemblyTreeModel;
this.fileLoaders = fileLoaders;
this.menuService = menuService;
this.settingsService = settingsService; this.settingsService = settingsService;
// Make sure Images are initialized on the UI thread. // Make sure Images are initialized on the UI thread.
@ -69,16 +51,10 @@ namespace ICSharpCode.ILSpy
InitializeComponent(); InitializeComponent();
InitFileLoaders();
Dispatcher.BeginInvoke(DispatcherPriority.Background, () => { Dispatcher.BeginInvoke(DispatcherPriority.Background, () => {
mainWindowViewModel.Workspace.InitializeLayout(); mainWindowViewModel.Workspace.InitializeLayout();
menuService.Init(mainMenu, toolBar, InputBindings); menuService.Init(mainMenu, toolBar, InputBindings);
MessageBus.Send(this, new MainWindowLoadedEventArgs());
Dispatcher.BeginInvoke(DispatcherPriority.Background, () => {
assemblyTreeModel.Initialize();
assemblyTreeModel.Show();
});
}); });
} }
@ -90,20 +66,6 @@ namespace ICSharpCode.ILSpy
this.Height = bounds.Height; this.Height = bounds.Height;
} }
#region File Loader extensibility
void InitFileLoaders()
{
// TODO
foreach (var loader in fileLoaders)
{
}
}
#endregion
#region Message Hook
protected override void OnSourceInitialized(EventArgs e) protected override void OnSourceInitialized(EventArgs e)
{ {
base.OnSourceInitialized(e); base.OnSourceInitialized(e);
@ -123,8 +85,6 @@ namespace ICSharpCode.ILSpy
this.WindowState = sessionSettings.WindowState; this.WindowState = sessionSettings.WindowState;
} }
#endregion
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
base.OnKeyDown(e); base.OnKeyDown(e);
@ -148,93 +108,6 @@ namespace ICSharpCode.ILSpy
} }
} }
#region Update Check
string updateAvailableDownloadUrl;
public async Task ShowMessageIfUpdatesAvailableAsync(UpdateSettings settings, bool forceCheck = false)
{
string downloadUrl;
if (forceCheck)
{
downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(settings);
}
else
{
downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesIfEnabledAsync(settings);
}
// The Update Panel is only available for NotifyOfUpdatesStrategy, AutoUpdate will have differing UI requirements
AdjustUpdateUIAfterCheck(downloadUrl, forceCheck);
}
void UpdatePanelCloseButtonClick(object sender, RoutedEventArgs e)
{
updatePanel.Visibility = Visibility.Collapsed;
}
async void DownloadOrCheckUpdateButtonClick(object sender, RoutedEventArgs e)
{
if (updateAvailableDownloadUrl != null)
{
OpenLink(updateAvailableDownloadUrl);
}
else
{
updatePanel.Visibility = Visibility.Collapsed;
string downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(settingsService.GetSettings<UpdateSettings>());
AdjustUpdateUIAfterCheck(downloadUrl, true);
}
}
void AdjustUpdateUIAfterCheck(string downloadUrl, bool displayMessage)
{
updateAvailableDownloadUrl = downloadUrl;
updatePanel.Visibility = displayMessage ? Visibility.Visible : Visibility.Collapsed;
if (downloadUrl != null)
{
updatePanelMessage.Text = Properties.Resources.ILSpyVersionAvailable;
downloadOrCheckUpdateButton.Content = Properties.Resources.Download;
}
else
{
updatePanelMessage.Text = Properties.Resources.UpdateILSpyFound;
downloadOrCheckUpdateButton.Content = Properties.Resources.CheckAgain;
}
}
#endregion
public static void OpenLink(string link)
{
try
{
Process.Start(new ProcessStartInfo { FileName = link, UseShellExecute = true });
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
}
catch (Exception)
{
#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
// Process.Start can throw several errors (not all of them documented),
// just ignore all of them.
}
}
public static void ExecuteCommand(string fileName, string arguments)
{
try
{
Process.Start(fileName, arguments);
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
}
catch (Exception)
{
#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
// Process.Start can throw several errors (not all of them documented),
// just ignore all of them.
}
}
protected override void OnStateChanged(EventArgs e) protected override void OnStateChanged(EventArgs e)
{ {
base.OnStateChanged(e); base.OnStateChanged(e);
@ -251,27 +124,12 @@ namespace ICSharpCode.ILSpy
var sessionSettings = snapshot.GetSettings<SessionSettings>(); var sessionSettings = snapshot.GetSettings<SessionSettings>();
sessionSettings.ActiveAssemblyList = assemblyTreeModel.AssemblyList.ListName; MessageBus.Send(this, new ApplySessionSettingsEventArgs(sessionSettings));
sessionSettings.ActiveTreeViewPath = assemblyTreeModel.SelectedPath;
sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(assemblyTreeModel.SelectedItem);
sessionSettings.WindowBounds = this.RestoreBounds; sessionSettings.WindowBounds = this.RestoreBounds;
sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(DockManager)); sessionSettings.DockLayout.Serialize(new(DockManager));
snapshot.Save(); snapshot.Save();
} }
private static string GetAutoLoadedAssemblyNode(SharpTreeNode node)
{
var assemblyTreeNode = node?
.AncestorsAndSelf()
.OfType<AssemblyTreeNode>()
.FirstOrDefault();
var loadedAssembly = assemblyTreeNode?.LoadedAssembly;
return loadedAssembly is not { IsLoaded: true, IsAutoLoaded: true }
? null
: loadedAssembly.FileName;
}
} }
} }

6
ILSpy/MainWindowViewModel.cs

@ -18,8 +18,6 @@
using System.Composition; using System.Composition;
using ICSharpCode.ILSpy.Analyzers;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX;
@ -29,7 +27,7 @@ namespace ICSharpCode.ILSpy
{ {
[Export] [Export]
[Shared] [Shared]
public class MainWindowViewModel(AssemblyTreeModel assemblyTreeModel, AnalyzerTreeViewModel analyzerTreeViewModel, SettingsService settingsService, LanguageService languageService, DockWorkspace dockWorkspace) : ObservableObject public class MainWindowViewModel(SettingsService settingsService, LanguageService languageService, DockWorkspace dockWorkspace) : ObservableObject
{ {
public DockWorkspace Workspace { get; } = dockWorkspace; public DockWorkspace Workspace { get; } = dockWorkspace;
@ -38,7 +36,5 @@ namespace ICSharpCode.ILSpy
public LanguageService LanguageService => languageService; public LanguageService LanguageService => languageService;
public AssemblyListManager AssemblyListManager => settingsService.AssemblyListManager; public AssemblyListManager AssemblyListManager => settingsService.AssemblyListManager;
public AnalyzeCommand AnalyzeCommand { get; } = new(assemblyTreeModel, analyzerTreeViewModel);
} }
} }

5
ILSpy/Options/OptionsDialog.xaml.cs

@ -59,13 +59,14 @@ namespace ICSharpCode.ILSpy.Options
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources._Options), MenuCategory = nameof(Resources.Options), MenuOrder = 999)] [ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources._Options), MenuCategory = nameof(Resources.Options), MenuOrder = 999)]
[Shared] [Shared]
sealed class ShowOptionsCommand(AssemblyTreeModel assemblyTreeModel, SettingsService settingsService) : SimpleCommand sealed class ShowOptionsCommand(AssemblyTreeModel assemblyTreeModel, SettingsService settingsService, MainWindow mainWindow) : SimpleCommand
{ {
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
OptionsDialog dlg = new(settingsService) { OptionsDialog dlg = new(settingsService) {
Owner = App.Current.MainWindow, Owner = mainWindow
}; };
if (dlg.ShowDialog() == true) if (dlg.ShowDialog() == true)
{ {
assemblyTreeModel.Refresh(); assemblyTreeModel.Refresh();

8
ILSpy/Search/SearchPane.xaml.cs

@ -262,11 +262,11 @@ namespace ICSharpCode.ILSpy.Search
{ {
searchProgressBar.IsIndeterminate = true; searchProgressBar.IsIndeterminate = true;
startedSearch = new(await assemblyTreeModel.AssemblyList.GetAllAssemblies(), startedSearch = new(await assemblyTreeModel.AssemblyList.GetAllAssemblies(),
searchTerm, searchTerm,
(SearchMode)searchModeComboBox.SelectedIndex, (SearchMode)searchModeComboBox.SelectedIndex,
assemblyTreeModel.CurrentLanguage, assemblyTreeModel.CurrentLanguage,
treeNodeFactory, treeNodeFactory,
settingsService); settingsService);
currentSearch = startedSearch; currentSearch = startedSearch;
@ -298,7 +298,7 @@ namespace ICSharpCode.ILSpy.Search
readonly ApiVisibility apiVisibility; readonly ApiVisibility apiVisibility;
readonly ITreeNodeFactory treeNodeFactory; readonly ITreeNodeFactory treeNodeFactory;
readonly SettingsService settingsService; readonly SettingsService settingsService;
public IProducerConsumerCollection<SearchResult> ResultQueue { get; } = new ConcurrentQueue<SearchResult>(); public IProducerConsumerCollection<SearchResult> ResultQueue { get; } = new ConcurrentQueue<SearchResult>();
public RunningSearch(IList<LoadedAssembly> assemblies, string searchTerm, SearchMode searchMode, public RunningSearch(IList<LoadedAssembly> assemblies, string searchTerm, SearchMode searchMode,

6
ILSpy/Search/SearchPaneModel.cs

@ -34,7 +34,6 @@ namespace ICSharpCode.ILSpy.Search
[ExportToolPane] [ExportToolPane]
[Shared] [Shared]
[Export]
public class SearchPaneModel : ToolPaneModel public class SearchPaneModel : ToolPaneModel
{ {
public const string PaneContentId = "searchPane"; public const string PaneContentId = "searchPane";
@ -50,6 +49,11 @@ namespace ICSharpCode.ILSpy.Search
Icon = "Images/Search"; Icon = "Images/Search";
ShortcutKey = new(Key.F, ModifierKeys.Control | ModifierKeys.Shift); ShortcutKey = new(Key.F, ModifierKeys.Control | ModifierKeys.Shift);
IsCloseable = true; IsCloseable = true;
MessageBus<ShowSearchPageEventArgs>.Subscribers += (_, e) => {
SearchTerm = e.SearchTerm;
Show();
};
} }
public SearchModeModel[] SearchModes { get; } = [ public SearchModeModel[] SearchModes { get; } = [

47
ILSpy/TextView/DecompilerTextView.cs

@ -62,6 +62,7 @@ using ICSharpCode.ILSpyX;
using Microsoft.Win32; using Microsoft.Win32;
using TomsToolbox.Composition;
using TomsToolbox.Wpf; using TomsToolbox.Wpf;
using ResourceKeys = ICSharpCode.ILSpy.Themes.ResourceKeys; using ResourceKeys = ICSharpCode.ILSpy.Themes.ResourceKeys;
@ -74,9 +75,10 @@ namespace ICSharpCode.ILSpy.TextView
/// </summary> /// </summary>
public sealed partial class DecompilerTextView : UserControl, IHaveState, IProgress<DecompilationProgress> public sealed partial class DecompilerTextView : UserControl, IHaveState, IProgress<DecompilationProgress>
{ {
readonly AssemblyTreeModel assemblyTreeModel; readonly IExportProvider exportProvider;
readonly SettingsService settingsService; readonly SettingsService settingsService;
private readonly LanguageService languageService; readonly LanguageService languageService;
readonly MainWindow mainWindow;
readonly ReferenceElementGenerator referenceElementGenerator; readonly ReferenceElementGenerator referenceElementGenerator;
readonly UIElementGenerator uiElementGenerator; readonly UIElementGenerator uiElementGenerator;
readonly List<VisualLineElementGenerator?> activeCustomElementGenerators = new List<VisualLineElementGenerator?>(); readonly List<VisualLineElementGenerator?> activeCustomElementGenerators = new List<VisualLineElementGenerator?>();
@ -97,11 +99,12 @@ namespace ICSharpCode.ILSpy.TextView
readonly List<ITextMarker> localReferenceMarks = new List<ITextMarker>(); readonly List<ITextMarker> localReferenceMarks = new List<ITextMarker>();
#region Constructor #region Constructor
public DecompilerTextView(TabPageModel tabPage) public DecompilerTextView(IExportProvider exportProvider)
{ {
this.assemblyTreeModel = tabPage.AssemblyTreeModel; this.exportProvider = exportProvider;
this.settingsService = tabPage.SettingsService; settingsService = exportProvider.GetExportedValue<SettingsService>();
this.languageService = tabPage.LanguageService; languageService = exportProvider.GetExportedValue<LanguageService>();
mainWindow = exportProvider.GetExportedValue<MainWindow>();
RegisterHighlighting(); RegisterHighlighting();
@ -137,7 +140,7 @@ namespace ICSharpCode.ILSpy.TextView
// SearchPanel // SearchPanel
SearchPanel searchPanel = SearchPanel.Install(textEditor.TextArea); SearchPanel searchPanel = SearchPanel.Install(textEditor.TextArea);
searchPanel.RegisterCommands(App.Current.MainWindow.CommandBindings); searchPanel.RegisterCommands(mainWindow.CommandBindings);
searchPanel.SetResourceReference(SearchPanel.MarkerBrushProperty, ResourceKeys.SearchResultBackgroundBrush); searchPanel.SetResourceReference(SearchPanel.MarkerBrushProperty, ResourceKeys.SearchResultBackgroundBrush);
searchPanel.Loaded += (_, _) => { searchPanel.Loaded += (_, _) => {
// HACK: fix search text box // HACK: fix search text box
@ -412,7 +415,7 @@ namespace ICSharpCode.ILSpy.TextView
if (segment.Reference is ICSharpCode.Decompiler.Disassembler.OpCodeInfo code) if (segment.Reference is ICSharpCode.Decompiler.Disassembler.OpCodeInfo code)
{ {
XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation; XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation;
DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), languageService.Language.SyntaxHighlighting, settingsService.DisplaySettings); DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), languageService.Language.SyntaxHighlighting, settingsService.DisplaySettings, mainWindow);
renderer.AddSignatureBlock($"{code.Name} (0x{code.Code:x})"); renderer.AddSignatureBlock($"{code.Name} (0x{code.Code:x})");
if (docProvider != null) if (docProvider != null)
{ {
@ -422,18 +425,19 @@ namespace ICSharpCode.ILSpy.TextView
renderer.AddXmlDocumentation(documentation, null, null); renderer.AddXmlDocumentation(documentation, null, null);
} }
} }
return new FlowDocumentTooltip(renderer.CreateDocument(), fontSize); return new FlowDocumentTooltip(renderer.CreateDocument(), fontSize, mainWindow.ActualWidth);
} }
else if (segment.Reference is IEntity entity) else if (segment.Reference is IEntity entity)
{ {
var document = CreateTooltipForEntity(entity); var document = CreateTooltipForEntity(entity);
if (document == null) if (document == null)
return null; return null;
return new FlowDocumentTooltip(document, fontSize); return new FlowDocumentTooltip(document, fontSize, mainWindow.ActualWidth);
} }
else if (segment.Reference is EntityReference unresolvedEntity) else if (segment.Reference is EntityReference unresolvedEntity)
{ {
var module = unresolvedEntity.ResolveAssembly(assemblyTreeModel.AssemblyList); var assemblyList = exportProvider.GetExportedValue<AssemblyList>();
var module = unresolvedEntity.ResolveAssembly(assemblyList);
if (module == null) if (module == null)
return null; return null;
var typeSystem = new DecompilerTypeSystem(module, var typeSystem = new DecompilerTypeSystem(module,
@ -450,7 +454,7 @@ namespace ICSharpCode.ILSpy.TextView
var document = CreateTooltipForEntity(resolved); var document = CreateTooltipForEntity(resolved);
if (document == null) if (document == null)
return null; return null;
return new FlowDocumentTooltip(document, fontSize); return new FlowDocumentTooltip(document, fontSize, mainWindow.ActualWidth);
} }
catch (BadImageFormatException) catch (BadImageFormatException)
{ {
@ -463,7 +467,7 @@ namespace ICSharpCode.ILSpy.TextView
FlowDocument? CreateTooltipForEntity(IEntity resolved) FlowDocument? CreateTooltipForEntity(IEntity resolved)
{ {
Language currentLanguage = languageService.Language; Language currentLanguage = languageService.Language;
DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), currentLanguage.SyntaxHighlighting, settingsService.DisplaySettings); DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), currentLanguage.SyntaxHighlighting, settingsService.DisplaySettings, mainWindow);
RichText richText = currentLanguage.GetRichTextTooltip(resolved); RichText richText = currentLanguage.GetRichTextTooltip(resolved);
if (richText == null) if (richText == null)
{ {
@ -493,7 +497,8 @@ namespace ICSharpCode.ILSpy.TextView
IEntity? ResolveReference(string idString) IEntity? ResolveReference(string idString)
{ {
return AssemblyTreeModel.FindEntityInRelevantAssemblies(idString, assemblyTreeModel.AssemblyList.GetAssemblies()); var assemblyList = exportProvider.GetExportedValue<AssemblyList>();
return AssemblyTreeModel.FindEntityInRelevantAssemblies(idString, assemblyList.GetAssemblies());
} }
} }
@ -501,14 +506,14 @@ namespace ICSharpCode.ILSpy.TextView
{ {
readonly FlowDocumentScrollViewer viewer; readonly FlowDocumentScrollViewer viewer;
public FlowDocumentTooltip(FlowDocument document, double fontSize) public FlowDocumentTooltip(FlowDocument document, double fontSize, double maxWith)
{ {
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display); TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
viewer = new FlowDocumentScrollViewer() { viewer = new() {
Width = document.MinPageWidth + fontSize * 5, Width = document.MinPageWidth + fontSize * 5,
MaxWidth = App.Current.MainWindow.ActualWidth MaxWidth = maxWith,
Document = document
}; };
viewer.Document = document;
Border border = new Border { Border border = new Border {
BorderThickness = new Thickness(1), BorderThickness = new Thickness(1),
MaxHeight = 400, MaxHeight = 400,
@ -572,7 +577,7 @@ namespace ICSharpCode.ILSpy.TextView
progressTitle.Text = !string.IsNullOrWhiteSpace(value.Title) ? value.Title : Properties.Resources.Decompiling; progressTitle.Text = !string.IsNullOrWhiteSpace(value.Title) ? value.Title : Properties.Resources.Decompiling;
progressText.Text = value.Status; progressText.Text = value.Status;
progressText.Visibility = !string.IsNullOrWhiteSpace(progressText.Text) ? Visibility.Visible : Visibility.Collapsed; progressText.Visibility = !string.IsNullOrWhiteSpace(progressText.Text) ? Visibility.Visible : Visibility.Collapsed;
var taskBar = App.Current.MainWindow.TaskbarItemInfo; var taskBar = mainWindow.TaskbarItemInfo;
if (taskBar != null) if (taskBar != null)
{ {
taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Normal; taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Normal;
@ -602,7 +607,7 @@ namespace ICSharpCode.ILSpy.TextView
progressText.Text = null; progressText.Text = null;
progressText.Visibility = Visibility.Collapsed; progressText.Visibility = Visibility.Collapsed;
waitAdorner.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(0.5)), FillBehavior.Stop)); waitAdorner.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(0.5)), FillBehavior.Stop));
var taskBar = App.Current.MainWindow.TaskbarItemInfo; var taskBar = mainWindow.TaskbarItemInfo;
if (taskBar != null) if (taskBar != null)
{ {
taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Indeterminate; taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Indeterminate;
@ -641,7 +646,7 @@ namespace ICSharpCode.ILSpy.TextView
progressBar.IsIndeterminate = false; progressBar.IsIndeterminate = false;
progressText.Text = null; progressText.Text = null;
progressText.Visibility = Visibility.Collapsed; progressText.Visibility = Visibility.Collapsed;
var taskBar = App.Current.MainWindow.TaskbarItemInfo; var taskBar = mainWindow.TaskbarItemInfo;
if (taskBar != null) if (taskBar != null)
{ {
taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None; taskBar.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;

6
ILSpy/TextView/DocumentationUIBuilder.cs

@ -45,15 +45,17 @@ namespace ICSharpCode.ILSpy.TextView
readonly IAmbience ambience; readonly IAmbience ambience;
readonly IHighlightingDefinition highlightingDefinition; readonly IHighlightingDefinition highlightingDefinition;
readonly DisplaySettings displaySettings; readonly DisplaySettings displaySettings;
readonly MainWindow mainWindow;
readonly FlowDocument document; readonly FlowDocument document;
BlockCollection blockCollection; BlockCollection blockCollection;
InlineCollection inlineCollection; InlineCollection inlineCollection;
public DocumentationUIBuilder(IAmbience ambience, IHighlightingDefinition highlightingDefinition, DisplaySettings displaySettings) public DocumentationUIBuilder(IAmbience ambience, IHighlightingDefinition highlightingDefinition, DisplaySettings displaySettings, MainWindow mainWindow)
{ {
this.ambience = ambience; this.ambience = ambience;
this.highlightingDefinition = highlightingDefinition; this.highlightingDefinition = highlightingDefinition;
this.displaySettings = displaySettings; this.displaySettings = displaySettings;
this.mainWindow = mainWindow;
this.document = new FlowDocument(); this.document = new FlowDocument();
this.blockCollection = document.Blocks; this.blockCollection = document.Blocks;
@ -119,7 +121,7 @@ namespace ICSharpCode.ILSpy.TextView
}; };
text.Inlines.AddRange(richText.CreateRuns(document)); text.Inlines.AddRange(richText.CreateRuns(document));
text.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); text.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
this.document.MinPageWidth = Math.Min(text.DesiredSize.Width, App.Current.MainWindow.ActualWidth); this.document.MinPageWidth = Math.Min(text.DesiredSize.Width, mainWindow.ActualWidth);
block.Inlines.AddRange(richText.CreateRuns(document)); block.Inlines.AddRange(richText.CreateRuns(document));
block.FontFamily = GetCodeFont(); block.FontFamily = GetCodeFont();
block.TextAlignment = TextAlignment.Left; block.TextAlignment = TextAlignment.Left;

2
ILSpy/TreeNodes/AssemblyListTreeNode.cs

@ -50,7 +50,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
ArgumentNullException.ThrowIfNull(assemblyList); ArgumentNullException.ThrowIfNull(assemblyList);
this.assemblyList = assemblyList; this.assemblyList = assemblyList;
BindToObservableCollection(assemblyList); BindToObservableCollection(assemblyList);
} }

6
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -712,7 +712,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
node.RaisePropertyChanged(nameof(ILSpyTreeNode.IsAutoLoaded)); node.RaisePropertyChanged(nameof(ILSpyTreeNode.IsAutoLoaded));
} }
} }
assemblyTreeModel.AssemblyList.RefreshSave(); assemblyTreeModel.AssemblyList.RefreshSave();
} }
} }
@ -764,7 +764,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
var path = node.LoadedAssembly.FileName; var path = node.LoadedAssembly.FileName;
if (File.Exists(path)) if (File.Exists(path))
{ {
MainWindow.ExecuteCommand("explorer.exe", $"/select,\"{path}\""); GlobalUtils.ExecuteCommand("explorer.exe", $"/select,\"{path}\"");
} }
} }
} }
@ -806,7 +806,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
var path = Path.GetDirectoryName(node.LoadedAssembly.FileName); var path = Path.GetDirectoryName(node.LoadedAssembly.FileName);
if (Directory.Exists(path)) if (Directory.Exists(path))
{ {
MainWindow.ExecuteCommand("cmd.exe", $"/k \"cd {path}\""); GlobalUtils.ExecuteCommand("cmd.exe", $"/k \"cd {path}\"");
} }
} }
} }

12
ILSpy/TreeNodes/ILSpyTreeNode.cs

@ -50,15 +50,15 @@ namespace ICSharpCode.ILSpy.TreeNodes
public Language Language => LanguageService.Language; public Language Language => LanguageService.Language;
public static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue<AssemblyTreeModel>(); protected static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue<AssemblyTreeModel>();
public static ICollection<IResourceNodeFactory> ResourceNodeFactories { get; } = App.ExportProvider.GetExportedValues<IResourceNodeFactory>().ToArray(); protected static ICollection<IResourceNodeFactory> ResourceNodeFactories { get; } = App.ExportProvider.GetExportedValues<IResourceNodeFactory>().ToArray();
public static SettingsService SettingsService { get; } = App.ExportProvider.GetExportedValue<SettingsService>(); protected static SettingsService SettingsService { get; } = App.ExportProvider.GetExportedValue<SettingsService>();
public static LanguageService LanguageService { get; } = App.ExportProvider.GetExportedValue<LanguageService>(); protected static LanguageService LanguageService { get; } = App.ExportProvider.GetExportedValue<LanguageService>();
public static DockWorkspace DockWorkspace { get; } = App.ExportProvider.GetExportedValue<DockWorkspace>(); protected static DockWorkspace DockWorkspace { get; } = App.ExportProvider.GetExportedValue<DockWorkspace>();
public virtual FilterResult Filter(LanguageSettings settings) public virtual FilterResult Filter(LanguageSettings settings)
{ {
@ -85,7 +85,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
var assemblyTreeModel = AssemblyTreeModel; var assemblyTreeModel = AssemblyTreeModel;
assemblyTreeModel.SelectNode(this, inNewTabPage: true); assemblyTreeModel.SelectNode(this, inNewTabPage: true);
App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, assemblyTreeModel.RefreshDecompiledView); App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, assemblyTreeModel.RefreshDecompiledView);
} }

8
ILSpy/Updates/NotifyOfUpdatesStrategy.cs → ILSpy/Updates/UpdateService.cs

@ -23,11 +23,9 @@ using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using ICSharpCode.ILSpyX.Settings;
namespace ICSharpCode.ILSpy.Updates namespace ICSharpCode.ILSpy.Updates
{ {
internal static class NotifyOfUpdatesStrategy internal static class UpdateService
{ {
static readonly Uri UpdateUrl = new Uri("https://ilspy.net/updates.xml"); static readonly Uri UpdateUrl = new Uri("https://ilspy.net/updates.xml");
const string band = "stable"; const string band = "stable";
@ -68,8 +66,8 @@ namespace ICSharpCode.ILSpy.Updates
// perform update check if we never did one before; // perform update check if we never did one before;
// or if the last check wasn't in the past 7 days // or if the last check wasn't in the past 7 days
if (settings.LastSuccessfulUpdateCheck == null if (settings.LastSuccessfulUpdateCheck == null
|| settings.LastSuccessfulUpdateCheck < DateTime.UtcNow.AddDays(-7) || settings.LastSuccessfulUpdateCheck < DateTime.UtcNow.AddDays(-7)
|| settings.LastSuccessfulUpdateCheck > DateTime.UtcNow) || settings.LastSuccessfulUpdateCheck > DateTime.UtcNow)
{ {
return await CheckForUpdateInternal(settings).ConfigureAwait(false); return await CheckForUpdateInternal(settings).ConfigureAwait(false);
} }

52
ILSpy/Util/GlobalUtils.cs

@ -0,0 +1,52 @@
// Copyright (c) 2024 Tom Englert for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
namespace ICSharpCode.ILSpy.Util
{
static class GlobalUtils
{
public static void OpenLink(string link)
{
try
{
Process.Start(new ProcessStartInfo { FileName = link, UseShellExecute = true });
}
catch (Exception)
{
// Process.Start can throw several errors (not all of them documented),
// just ignore all of them.
}
}
public static void ExecuteCommand(string fileName, string arguments)
{
try
{
Process.Start(fileName, arguments);
}
catch (Exception)
{
// Process.Start can throw several errors (not all of them documented),
// just ignore all of them.
}
}
}
}

52
ILSpy/Util/MenuService.cs

@ -22,6 +22,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Input; using System.Windows.Input;
@ -74,28 +75,31 @@ namespace ICSharpCode.ILSpy.Util
} }
else else
{ {
var command = entry.Value;
var menuItem = new MenuItem { var menuItem = new MenuItem {
Command = CommandWrapper.Unwrap(entry.Value), Command = CommandWrapper.Unwrap(command),
Tag = entry.Metadata?.MenuID, Tag = entry.Metadata?.MenuID,
Header = ResourceHelper.GetString(entry.Metadata?.Header) Header = ResourceHelper.GetString(entry.Metadata?.Header)
}; };
if (!string.IsNullOrEmpty(entry.Metadata?.MenuIcon)) if (!string.IsNullOrEmpty(entry.Metadata?.MenuIcon))
{ {
menuItem.Icon = new Image { menuItem.Icon = new Image {
Width = 16, Width = 16,
Height = 16, Height = 16,
Source = Images.Load(entry.Value, entry.Metadata.MenuIcon) Source = Images.Load(command, entry.Metadata.MenuIcon)
}; };
} }
menuItem.IsEnabled = entry.Metadata?.IsEnabled ?? false; menuItem.IsEnabled = entry.Metadata?.IsEnabled ?? false;
if (entry.Value is ToggleableCommand) menuItem.InputGestureText = entry.Metadata?.InputGestureText;
if (command is IProvideParameterBinding parameterBinding)
{ {
menuItem.IsCheckable = true; BindingOperations.SetBinding(menuItem, MenuItem.CommandParameterProperty, parameterBinding.ParameterBinding);
menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding("IsChecked") { Source = entry.Value, Mode = BindingMode.OneWay });
} }
menuItem.InputGestureText = entry.Metadata?.InputGestureText;
parentMenuItem.Items.Add(menuItem); parentMenuItem.Items.Add(menuItem);
} }
} }
@ -153,23 +157,23 @@ namespace ICSharpCode.ILSpy.Util
{ {
int navigationPos = 0; int navigationPos = 0;
int openPos = 1; int openPos = 1;
var toolbarCommandsByTitle = exportProvider.GetExports<ICommand, IToolbarCommandMetadata>("ToolbarCommand") var toolbarCommandsByCategory = exportProvider.GetExports<ICommand, IToolbarCommandMetadata>("ToolbarCommand")
.OrderBy(c => c.Metadata?.ToolbarOrder) .OrderBy(c => c.Metadata?.ToolbarOrder)
.GroupBy(c => c.Metadata?.ToolbarCategory); .GroupBy(c => c.Metadata?.ToolbarCategory);
foreach (var commandGroup in toolbarCommandsByTitle) foreach (var commandCategory in toolbarCommandsByCategory)
{ {
if (commandGroup.Key == nameof(Properties.Resources.Navigation)) if (commandCategory.Key == nameof(Properties.Resources.Navigation))
{ {
foreach (var command in commandGroup) foreach (var command in commandCategory)
{ {
toolBar.Items.Insert(navigationPos++, CreateToolbarItem(command)); toolBar.Items.Insert(navigationPos++, CreateToolbarItem(command));
openPos++; openPos++;
} }
} }
else if (commandGroup.Key == nameof(Properties.Resources.Open)) else if (commandCategory.Key == nameof(Properties.Resources.Open))
{ {
foreach (var command in commandGroup) foreach (var command in commandCategory)
{ {
toolBar.Items.Insert(openPos++, CreateToolbarItem(command)); toolBar.Items.Insert(openPos++, CreateToolbarItem(command));
} }
@ -177,13 +181,12 @@ namespace ICSharpCode.ILSpy.Util
else else
{ {
toolBar.Items.Add(new Separator()); toolBar.Items.Add(new Separator());
foreach (var command in commandGroup) foreach (var command in commandCategory)
{ {
toolBar.Items.Add(CreateToolbarItem(command)); toolBar.Items.Add(CreateToolbarItem(command));
} }
} }
} }
} }
Control CreateMenuItem(TabPageModel pane) Control CreateMenuItem(TabPageModel pane)
@ -237,19 +240,28 @@ namespace ICSharpCode.ILSpy.Util
return menuItem; return menuItem;
} }
static Button CreateToolbarItem(IExport<ICommand, IToolbarCommandMetadata> command) static Button CreateToolbarItem(IExport<ICommand, IToolbarCommandMetadata> commandExport)
{ {
return new() { var command = commandExport.Value;
Button toolbarItem = new() {
Style = ThemeManager.Current.CreateToolBarButtonStyle(), Style = ThemeManager.Current.CreateToolBarButtonStyle(),
Command = CommandWrapper.Unwrap(command.Value), Command = CommandWrapper.Unwrap(command),
ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata?.ToolTip), ToolTip = Properties.Resources.ResourceManager.GetString(commandExport.Metadata?.ToolTip ?? string.Empty),
Tag = command.Metadata?.Tag, Tag = commandExport.Metadata?.Tag,
Content = new Image { Content = new Image {
Width = 16, Width = 16,
Height = 16, Height = 16,
Source = Images.Load(command.Value, command.Metadata?.ToolbarIcon) Source = Images.Load(command, commandExport.Metadata?.ToolbarIcon)
} }
}; };
if (command is IProvideParameterBinding parameterBinding)
{
BindingOperations.SetBinding(toolbarItem, ButtonBase.CommandParameterProperty, parameterBinding.ParameterBinding);
}
return toolbarItem;
} }
} }
} }

48
ILSpy/Util/MessageBus.cs

@ -19,14 +19,20 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Navigation;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.ViewModels;
using TomsToolbox.Essentials; using TomsToolbox.Essentials;
#nullable enable
namespace ICSharpCode.ILSpy.Util namespace ICSharpCode.ILSpy.Util
{ {
public static class MessageBus public static class MessageBus
{ {
public static void Send<T>(object sender, T e) public static void Send<T>(object? sender, T e)
where T : EventArgs where T : EventArgs
{ {
MessageBus<T>.Send(sender, e); MessageBus<T>.Send(sender, e);
@ -47,9 +53,9 @@ namespace ICSharpCode.ILSpy.Util
remove => subscriptions.Unsubscribe(value); remove => subscriptions.Unsubscribe(value);
} }
public static void Send(object sender, T e) public static void Send(object? sender, T e)
{ {
subscriptions.Raise(sender, e); subscriptions.Raise(sender!, e);
} }
} }
@ -79,5 +85,41 @@ namespace ICSharpCode.ILSpy.Util
public bool InNewTabPage { get; } = inNewTabPage; public bool InNewTabPage { get; } = inNewTabPage;
} }
public class NavigateToEventArgs(RequestNavigateEventArgs request, bool inNewTabPage = false) : EventArgs
{
public RequestNavigateEventArgs Request { get; } = request;
public bool InNewTabPage { get; } = inNewTabPage;
}
public class AssemblyTreeSelectionChangedEventArgs() : EventArgs; public class AssemblyTreeSelectionChangedEventArgs() : EventArgs;
public class ApplySessionSettingsEventArgs(SessionSettings sessionSettings) : EventArgs
{
public SessionSettings SessionSettings { get; } = sessionSettings;
}
public class MainWindowLoadedEventArgs() : EventArgs;
public class ActiveTabPageChangedEventArgs(ViewState? viewState) : EventArgs
{
public ViewState? ViewState { get; } = viewState;
}
public class ResetLayoutEventArgs : EventArgs;
public class ShowAboutPageEventArgs(TabPageModel tabPage) : EventArgs
{
public TabPageModel TabPage { get; } = tabPage;
}
public class ShowSearchPageEventArgs(string? searchTerm) : EventArgs
{
public string? SearchTerm { get; } = searchTerm;
}
public class CheckIfUpdateAvailableEventArgs(bool notify = false) : EventArgs
{
public bool Notify { get; } = notify;
}
} }

15
ILSpy/ViewModels/ManageAssemblyListsViewModel.cs

@ -25,13 +25,16 @@ using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.ILSpy.Commands;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX;
using TomsToolbox.Wpf;
using DelegateCommand = ICSharpCode.ILSpy.Commands.DelegateCommand;
namespace ICSharpCode.ILSpy.ViewModels namespace ICSharpCode.ILSpy.ViewModels
{ {
public class ManageAssemblyListsViewModel : ViewModelBase public class ManageAssemblyListsViewModel : ObservableObject
{ {
private readonly AssemblyListManager manager; private readonly AssemblyListManager manager;
private readonly Window parent; private readonly Window parent;
@ -106,13 +109,7 @@ namespace ICSharpCode.ILSpy.ViewModels
public string SelectedAssemblyList { public string SelectedAssemblyList {
get => selectedAssemblyList; get => selectedAssemblyList;
set { set => SetProperty(ref selectedAssemblyList, value);
if (selectedAssemblyList != value)
{
selectedAssemblyList = value;
RaisePropertyChanged();
}
}
} }
public ICommand NewCommand { get; } public ICommand NewCommand { get; }

5
ILSpy/ViewModels/PaneModel.cs

@ -21,7 +21,6 @@ using System.ComponentModel;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Docking;
using TomsToolbox.Wpf; using TomsToolbox.Wpf;
@ -30,9 +29,9 @@ namespace ICSharpCode.ILSpy.ViewModels
{ {
public abstract class PaneModel : ObservableObject public abstract class PaneModel : ObservableObject
{ {
private Throttle titleChangeThrottle; private readonly Throttle titleChangeThrottle;
public static DockWorkspace DockWorkspace => App.ExportProvider.GetExportedValue<DockWorkspace>(); protected static DockWorkspace DockWorkspace => App.ExportProvider.GetExportedValue<DockWorkspace>();
protected PaneModel() protected PaneModel()
{ {

62
ILSpy/ViewModels/TabPageModel.cs

@ -17,60 +17,48 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Composition;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TextView;
using TomsToolbox.Composition;
using TomsToolbox.Wpf; using TomsToolbox.Wpf;
#nullable enable
namespace ICSharpCode.ILSpy.ViewModels namespace ICSharpCode.ILSpy.ViewModels
{ {
[Export]
[NonShared]
public class TabPageModel : PaneModel public class TabPageModel : PaneModel
{ {
public AssemblyTreeModel AssemblyTreeModel { get; } public IExportProvider ExportProvider { get; }
public SettingsService SettingsService { get; }
public LanguageService LanguageService { get; }
public TabPageModel(AssemblyTreeModel assemblyTreeModel, SettingsService settingsService, LanguageService languageService) public TabPageModel(IExportProvider exportProvider)
{ {
AssemblyTreeModel = assemblyTreeModel; ExportProvider = exportProvider;
SettingsService = settingsService; Title = Properties.Resources.NewTab;
LanguageService = languageService;
this.Title = Properties.Resources.NewTab;
} }
private bool supportsLanguageSwitching = true; private bool supportsLanguageSwitching = true;
public bool SupportsLanguageSwitching { public bool SupportsLanguageSwitching {
get => supportsLanguageSwitching; get => supportsLanguageSwitching;
set { set => SetProperty(ref supportsLanguageSwitching, value);
if (supportsLanguageSwitching != value)
{
supportsLanguageSwitching = value;
OnPropertyChanged(nameof(SupportsLanguageSwitching));
}
}
} }
private object content; private object? content;
public object Content { public object? Content {
get => content; get => content;
set { set => SetProperty(ref content, value);
if (content != value)
{
content = value;
OnPropertyChanged(nameof(Content));
}
}
} }
public ViewState GetState() public ViewState? GetState()
{ {
return (Content as IHaveState)?.GetState(); return (Content as IHaveState)?.GetState();
} }
@ -80,9 +68,9 @@ namespace ICSharpCode.ILSpy.ViewModels
{ {
public static Task<T> ShowTextViewAsync<T>(this TabPageModel tabPage, Func<DecompilerTextView, Task<T>> action) public static Task<T> ShowTextViewAsync<T>(this TabPageModel tabPage, Func<DecompilerTextView, Task<T>> action)
{ {
if (!(tabPage.Content is DecompilerTextView textView)) if (tabPage.Content is not DecompilerTextView textView)
{ {
textView = new DecompilerTextView(tabPage); textView = new DecompilerTextView(tabPage.ExportProvider);
tabPage.Content = textView; tabPage.Content = textView;
} }
tabPage.Title = Properties.Resources.Decompiling; tabPage.Title = Properties.Resources.Decompiling;
@ -91,9 +79,9 @@ namespace ICSharpCode.ILSpy.ViewModels
public static Task ShowTextViewAsync(this TabPageModel tabPage, Func<DecompilerTextView, Task> action) public static Task ShowTextViewAsync(this TabPageModel tabPage, Func<DecompilerTextView, Task> action)
{ {
if (!(tabPage.Content is DecompilerTextView textView)) if (tabPage.Content is not DecompilerTextView textView)
{ {
textView = new DecompilerTextView(tabPage); textView = new DecompilerTextView(tabPage.ExportProvider);
tabPage.Content = textView; tabPage.Content = textView;
} }
string oldTitle = tabPage.Title; string oldTitle = tabPage.Title;
@ -113,9 +101,9 @@ namespace ICSharpCode.ILSpy.ViewModels
public static void ShowTextView(this TabPageModel tabPage, Action<DecompilerTextView> action) public static void ShowTextView(this TabPageModel tabPage, Action<DecompilerTextView> action)
{ {
if (!(tabPage.Content is DecompilerTextView textView)) if (tabPage.Content is not DecompilerTextView textView)
{ {
textView = new DecompilerTextView(tabPage); textView = new DecompilerTextView(tabPage.ExportProvider);
tabPage.Content = textView; tabPage.Content = textView;
} }
string oldTitle = tabPage.Title; string oldTitle = tabPage.Title;
@ -142,12 +130,16 @@ namespace ICSharpCode.ILSpy.ViewModels
public static DecompilationOptions CreateDecompilationOptions(this TabPageModel tabPage) public static DecompilationOptions CreateDecompilationOptions(this TabPageModel tabPage)
{ {
return new(tabPage.LanguageService.LanguageVersion, tabPage.SettingsService.DecompilerSettings, tabPage.SettingsService.DisplaySettings) { Progress = tabPage.Content as IProgress<DecompilationProgress> }; var exportProvider = tabPage.ExportProvider;
var languageService = exportProvider.GetExportedValue<LanguageService>();
var settingsService = exportProvider.GetExportedValue<SettingsService>();
return new(languageService.LanguageVersion, settingsService.DecompilerSettings, settingsService.DisplaySettings) { Progress = tabPage.Content as IProgress<DecompilationProgress> };
} }
} }
public interface IHaveState public interface IHaveState
{ {
ViewState GetState(); ViewState? GetState();
} }
} }

100
ILSpy/ViewModels/UpdatePanelViewModel.cs

@ -0,0 +1,100 @@
// Copyright (c) 2024 Tom Englert for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Composition;
using System.Threading.Tasks;
using System.Windows.Input;
using ICSharpCode.ILSpy.Updates;
using TomsToolbox.Wpf;
namespace ICSharpCode.ILSpy.ViewModels;
[Export]
[NonShared]
public class UpdatePanelViewModel : ObservableObject
{
bool isPanelVisible;
string updateAvailableDownloadUrl;
readonly SettingsService settingsService;
public UpdatePanelViewModel(SettingsService settingsService)
{
this.settingsService = settingsService;
MessageBus<CheckIfUpdateAvailableEventArgs>.Subscribers += (_, e) => CheckIfUpdatesAvailableAsync(e.Notify).IgnoreExceptions();
}
public bool IsPanelVisible {
get => isPanelVisible;
set => SetProperty(ref isPanelVisible, value);
}
public string UpdateAvailableDownloadUrl {
get => updateAvailableDownloadUrl;
set => SetProperty(ref updateAvailableDownloadUrl, value);
}
public ICommand CloseCommand => new DelegateCommand(() => IsPanelVisible = false);
public ICommand DownloadOrCheckUpdateCommand => new DelegateCommand(DownloadOrCheckUpdate);
[PropertyDependency(nameof(UpdateAvailableDownloadUrl))]
public string ButtonText => UpdateAvailableDownloadUrl != null
? Properties.Resources.Download
: Properties.Resources.CheckAgain;
[PropertyDependency(nameof(UpdateAvailableDownloadUrl))]
public string Message => UpdateAvailableDownloadUrl != null
? Properties.Resources.ILSpyVersionAvailable
: Properties.Resources.UpdateILSpyFound;
async Task CheckIfUpdatesAvailableAsync(bool notify = false)
{
var settings = settingsService.GetSettings<UpdateSettings>();
string downloadUrl = notify
? await UpdateService.CheckForUpdatesAsync(settings)
: await UpdateService.CheckForUpdatesIfEnabledAsync(settings);
AdjustUpdateUIAfterCheck(downloadUrl, notify);
}
async void DownloadOrCheckUpdate()
{
if (updateAvailableDownloadUrl != null)
{
GlobalUtils.OpenLink(updateAvailableDownloadUrl);
}
else
{
IsPanelVisible = false;
string downloadUrl = await UpdateService.CheckForUpdatesAsync(settingsService.GetSettings<UpdateSettings>());
AdjustUpdateUIAfterCheck(downloadUrl, true);
}
}
void AdjustUpdateUIAfterCheck(string downloadUrl, bool notify)
{
UpdateAvailableDownloadUrl = downloadUrl;
IsPanelVisible = notify;
}
}

22
ILSpy/Views/UpdatePanel.xaml

@ -0,0 +1,22 @@
<UserControl x:Class="ICSharpCode.ILSpy.Views.UpdatePanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModels="clr-namespace:ICSharpCode.ILSpy.ViewModels"
xmlns:toms="urn:TomsToolbox"
mc:Ignorable="d" d:DesignWidth="500" d:DataContext="{d:DesignInstance viewModels:UpdatePanelViewModel}">
<Border BorderBrush="Black" BorderThickness="1" Visibility="{Binding IsPanelVisible, Converter={toms:BooleanToVisibilityConverter} }">
<DockPanel KeyboardNavigation.TabNavigation="Contained">
<Button DockPanel.Dock="Right" MinWidth="0" Command="{Binding CloseCommand}" Content="X" />
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="4,0" VerticalAlignment="Center"
Text="{Binding Message}" />
<Button Content="{Binding ButtonText}"
Command="{Binding DownloadOrCheckUpdateCommand}"/>
</StackPanel>
</StackPanel>
</DockPanel>
</Border>
</UserControl>

24
ILSpy/ViewModels/ViewModelBase.cs → ILSpy/Views/UpdatePanel.xaml.cs

@ -1,4 +1,4 @@
// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team // Copyright (c) 2024 Tom Englert for the SharpDevelop Team
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of this // Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software // software and associated documentation files (the "Software"), to deal in the Software
@ -16,18 +16,24 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System.ComponentModel; using System.Composition;
using System.Runtime.CompilerServices;
namespace ICSharpCode.ILSpy.ViewModels using ICSharpCode.ILSpy.ViewModels;
using TomsToolbox.Wpf.Composition.AttributedModel;
namespace ICSharpCode.ILSpy.Views
{ {
public abstract class ViewModelBase : INotifyPropertyChanged /// <summary>
/// Interaction logic for UpdatePanel.xaml
/// </summary>
[DataTemplate(typeof(UpdatePanelViewModel))]
[NonShared]
public partial class UpdatePanel
{ {
public event PropertyChangedEventHandler PropertyChanged; public UpdatePanel()
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); InitializeComponent();
} }
} }
} }
Loading…
Cancel
Save