Browse Source

Merge pull request #3297 from tom-englert/dev/WpfRefactoring

WPF refactoring
pull/3298/head
Siegfried Pammer 11 months ago committed by GitHub
parent
commit
7f46aab344
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      Directory.Packages.props
  2. 14
      ICSharpCode.ILSpyX/AssemblyListManager.cs
  3. 3
      ILSpy/Analyzers/AnalyzerTreeView.xaml
  4. 24
      ILSpy/Analyzers/AnalyzerTreeViewModel.cs
  5. 3
      ILSpy/App.xaml.cs
  6. 5
      ILSpy/AssemblyTree/AssemblyListPane.xaml
  7. 17
      ILSpy/AssemblyTree/AssemblyListPane.xaml.cs
  8. 127
      ILSpy/AssemblyTree/AssemblyTreeModel.cs
  9. 27
      ILSpy/Commands/DecompileInNewViewCommand.cs
  10. 29
      ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs
  11. 9
      ILSpy/Commands/ShowPane.cs
  12. 2
      ILSpy/Controls/TreeView/SharpTreeView.cs
  13. 7
      ILSpy/Docking/CloseAllDocumentsCommand.cs
  14. 67
      ILSpy/Docking/DockWorkspace.cs
  15. 13
      ILSpy/MainWindow.xaml.cs
  16. 5
      ILSpy/Util/MenuService.cs
  17. 5
      ILSpy/Util/SettingsService.cs
  18. 17
      ILSpy/ViewModels/TabPageModel.cs

8
Directory.Packages.props

@ -45,12 +45,12 @@
<PackageVersion Include="System.Reflection.Metadata" Version="8.0.0" /> <PackageVersion Include="System.Reflection.Metadata" Version="8.0.0" />
<PackageVersion Include="System.Resources.Extensions" Version="8.0.0" /> <PackageVersion Include="System.Resources.Extensions" Version="8.0.0" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" /> <PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.18.1" /> <PackageVersion Include="TomsToolbox.Wpf.Composition" Version="2.20.0" />
<PackageVersion Include="TomsToolbox.Wpf.Composition.Mef" Version="2.18.1" /> <PackageVersion Include="TomsToolbox.Wpf.Composition.Mef" Version="2.20.0" />
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.18.1" /> <PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.20.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" /> <PackageVersion Include="coverlet.collector" Version="6.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<GlobalPackageReference Include="TomsToolbox.Composition.Analyzer" Version="2.18.1" /> <GlobalPackageReference Include="TomsToolbox.Composition.Analyzer" Version="2.20.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

14
ICSharpCode.ILSpyX/AssemblyListManager.cs

@ -113,14 +113,16 @@ namespace ICSharpCode.ILSpyX
public void SaveList(AssemblyList list) public void SaveList(AssemblyList list)
{ {
this.settingsProvider.Update( this.settingsProvider.Update(
delegate (XElement root) { root => {
XElement? doc = root.Element("AssemblyLists"); XElement? doc = root.Element("AssemblyLists");
if (doc == null) if (doc == null)
{ {
doc = new XElement("AssemblyLists"); doc = new XElement("AssemblyLists");
root.Add(doc); root.Add(doc);
} }
XElement? listElement = doc.Elements("List").FirstOrDefault(e => (string?)e.Attribute("name") == list.ListName);
XElement? listElement = doc.Elements("List")
.FirstOrDefault(e => (string?)e.Attribute("name") == list.ListName);
if (listElement != null) if (listElement != null)
listElement.ReplaceWith(list.SaveAsXml()); listElement.ReplaceWith(list.SaveAsXml());
else else
@ -163,13 +165,9 @@ namespace ICSharpCode.ILSpyX
{ {
AssemblyLists.Clear(); AssemblyLists.Clear();
this.settingsProvider.Update( this.settingsProvider.Update(
delegate (XElement root) { root => {
XElement? doc = root.Element("AssemblyLists"); XElement? doc = root.Element("AssemblyLists");
if (doc == null) doc?.Remove();
{
return;
}
doc.Remove();
}); });
} }

3
ILSpy/Analyzers/AnalyzerTreeView.xaml

@ -12,8 +12,7 @@
ShowRoot="False" ShowRoot="False"
BorderThickness="0" BorderThickness="0"
Root="{Binding Root}" Root="{Binding Root}"
toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems}" toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectionChanged="AnalyzerTreeView_OnSelectionChanged"> SelectionChanged="AnalyzerTreeView_OnSelectionChanged">
<UIElement.InputBindings> <UIElement.InputBindings>

24
ILSpy/Analyzers/AnalyzerTreeViewModel.cs

@ -17,8 +17,6 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
@ -38,8 +36,6 @@ namespace ICSharpCode.ILSpy.Analyzers
[Export] [Export]
public class AnalyzerTreeViewModel : ToolPaneModel public class AnalyzerTreeViewModel : ToolPaneModel
{ {
private AnalyzerTreeNode selectedItem;
public const string PaneContentId = "analyzerPane"; public const string PaneContentId = "analyzerPane";
public AnalyzerTreeViewModel() public AnalyzerTreeViewModel()
@ -52,14 +48,20 @@ namespace ICSharpCode.ILSpy.Analyzers
public AnalyzerRootNode Root { get; } = new(); public AnalyzerRootNode Root { get; } = new();
public AnalyzerTreeNode SelectedItem {
get => selectedItem;
set => SetProperty(ref selectedItem, value);
}
public ICommand AnalyzeCommand => new DelegateCommand(AnalyzeSelected); public ICommand AnalyzeCommand => new DelegateCommand(AnalyzeSelected);
public ObservableCollection<AnalyzerTreeNode> SelectedItems { get; } = []; private AnalyzerTreeNode[] selectedItems = [];
public AnalyzerTreeNode[] SelectedItems {
get => selectedItems ?? [];
set {
if (SelectedItems.SequenceEqual(value))
return;
selectedItems = value;
OnPropertyChanged();
}
}
private void AnalyzeSelected() private void AnalyzeSelected()
{ {
@ -87,7 +89,7 @@ namespace ICSharpCode.ILSpy.Analyzers
} }
target.IsExpanded = true; target.IsExpanded = true;
this.SelectedItem = target; this.SelectedItems = [target];
} }
public void Analyze(IEntity entity) public void Analyze(IEntity entity)

3
ILSpy/App.xaml.cs

@ -216,9 +216,6 @@ namespace ICSharpCode.ILSpy
} }
MainWindow = new MainWindow(); MainWindow = new MainWindow();
MainWindow.Loaded += (sender, args) => {
ExportProvider.GetExportedValue<AssemblyTreeModel>().Initialize();
};
MainWindow.Show(); MainWindow.Show();
} }

5
ILSpy/AssemblyTree/AssemblyListPane.xaml

@ -7,6 +7,7 @@
xmlns:treeNodes="clr-namespace:ICSharpCode.ILSpy.TreeNodes" xmlns:treeNodes="clr-namespace:ICSharpCode.ILSpy.TreeNodes"
xmlns:assemblyTree="clr-namespace:ICSharpCode.ILSpy.AssemblyTree" xmlns:assemblyTree="clr-namespace:ICSharpCode.ILSpy.AssemblyTree"
xmlns:toms="urn:TomsToolbox" xmlns:toms="urn:TomsToolbox"
xmlns:viewModels="clr-namespace:ICSharpCode.ILSpy.ViewModels"
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance assemblyTree:AssemblyTreeModel}" d:DataContext="{d:DesignInstance assemblyTree:AssemblyTreeModel}"
AutomationProperties.Name="Assemblies and Classes" AutomationProperties.Name="Assemblies and Classes"
@ -15,8 +16,8 @@
AllowDrop="True" AllowDrop="True"
BorderThickness="0" Visibility="Visible" BorderThickness="0" Visibility="Visible"
Root="{Binding Root}" Root="{Binding Root}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems, Mode=TwoWay}"
toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems}"> viewModels:Pane.IsActive="{Binding IsActive}">
<treeView:SharpTreeView.ItemContainerStyle> <treeView:SharpTreeView.ItemContainerStyle>
<Style TargetType="treeView:SharpTreeViewItem"> <Style TargetType="treeView:SharpTreeViewItem">
<Setter Property="Template"> <Setter Property="Template">

17
ILSpy/AssemblyTree/AssemblyListPane.xaml.cs

@ -20,6 +20,9 @@ using System.ComponentModel.Composition;
using System.Windows; using System.Windows;
using System.Windows.Threading; using System.Windows.Threading;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpyX.TreeView;
using TomsToolbox.Wpf.Composition.Mef; using TomsToolbox.Wpf.Composition.Mef;
namespace ICSharpCode.ILSpy.AssemblyTree namespace ICSharpCode.ILSpy.AssemblyTree
@ -59,6 +62,20 @@ namespace ICSharpCode.ILSpy.AssemblyTree
}); });
} }
} }
else if (e.Property == Pane.IsActiveProperty)
{
if (!true.Equals(e.NewValue))
return;
if (SelectedItem is SharpTreeNode selectedItem)
{
FocusNode(selectedItem);
}
else
{
Focus();
}
}
} }
} }
} }

127
ILSpy/AssemblyTree/AssemblyTreeModel.cs

@ -18,7 +18,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -82,10 +81,9 @@ 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);
var selectionChangeThrottle = new DispatcherThrottle(DispatcherPriority.Input, TreeView_SelectionChanged);
SelectedItems.CollectionChanged += (_, _) => selectionChangeThrottle.Tick();
refreshThrottle = new DispatcherThrottle(DispatcherPriority.Background, RefreshInternal); refreshThrottle = new DispatcherThrottle(DispatcherPriority.Background, RefreshInternal);
AssemblyList = SettingsService.Instance.CreateEmptyAssemblyList();
} }
private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e) private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e)
@ -121,7 +119,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
} }
} }
public AssemblyList? AssemblyList { get; private set; } public AssemblyList AssemblyList { get; private set; }
private SharpTreeNode? root; private SharpTreeNode? root;
public SharpTreeNode? Root { public SharpTreeNode? Root {
@ -129,13 +127,23 @@ namespace ICSharpCode.ILSpy.AssemblyTree
set => SetProperty(ref root, value); set => SetProperty(ref root, value);
} }
private SharpTreeNode? selectedItem;
public SharpTreeNode? SelectedItem { public SharpTreeNode? SelectedItem {
get => selectedItem; get => SelectedItems.FirstOrDefault();
set => SetProperty(ref selectedItem, value); set => SelectedItems = value is null ? [] : [value];
} }
public ObservableCollection<SharpTreeNode> SelectedItems { get; } = []; private SharpTreeNode[] selectedItems = [];
public SharpTreeNode[] SelectedItems {
get => selectedItems;
set {
if (selectedItems.SequenceEqual(value))
return;
selectedItems = value;
OnPropertyChanged();
TreeView_SelectionChanged();
}
}
public string[]? SelectedPath => GetPathForNode(SelectedItem); public string[]? SelectedPath => GetPathForNode(SelectedItem);
@ -205,7 +213,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
{ {
// FindNamespaceNode() blocks the UI if the assembly is not yet loaded, // FindNamespaceNode() blocks the UI if the assembly is not yet loaded,
// so use an async wait instead. // so use an async wait instead.
await asm.GetMetadataFileAsync().Catch<Exception>(ex => { }); await asm.GetMetadataFileAsync().Catch<Exception>(_ => { });
NamespaceTreeNode nsNode = asmNode.FindNamespaceNode(namespaceName); NamespaceTreeNode nsNode = asmNode.FindNamespaceNode(namespaceName);
if (nsNode != null) if (nsNode != null)
{ {
@ -228,9 +236,11 @@ namespace ICSharpCode.ILSpy.AssemblyTree
else else
{ {
IEntity? mr = await Task.Run(() => FindEntityInRelevantAssemblies(navigateTo, relevantAssemblies)); IEntity? mr = await Task.Run(() => FindEntityInRelevantAssemblies(navigateTo, relevantAssemblies));
// Make sure we wait for assemblies being loaded... // Make sure we wait for assemblies being loaded...
// BeginInvoke in LoadedAssembly.LookupReferencedAssemblyInternal // BeginInvoke in LoadedAssembly.LookupReferencedAssemblyInternal
await Dispatcher.InvokeAsync(delegate { }, DispatcherPriority.Normal); await Dispatcher.InvokeAsync(delegate { }, DispatcherPriority.Normal);
if (mr is { ParentModule.MetadataFile: not null }) if (mr is { ParentModule.MetadataFile: not null })
{ {
found = true; found = true;
@ -260,7 +270,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
else if (spySettings != null) else if (spySettings != null)
{ {
SharpTreeNode? node = null; SharpTreeNode? node = null;
if (activeTreeViewPath?.Length > 0 && AssemblyList != null) if (activeTreeViewPath?.Length > 0)
{ {
foreach (var asm in AssemblyList.GetAssemblies()) foreach (var asm in AssemblyList.GetAssemblies())
{ {
@ -268,7 +278,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
{ {
// FindNodeByPath() blocks the UI if the assembly is not yet loaded, // FindNodeByPath() blocks the UI if the assembly is not yet loaded,
// so use an async wait instead. // so use an async wait instead.
await asm.GetMetadataFileAsync().Catch<Exception>(ex => { }); await asm.GetMetadataFileAsync().Catch<Exception>(_ => { });
} }
} }
node = FindNodeByPath(activeTreeViewPath, true); node = FindNodeByPath(activeTreeViewPath, true);
@ -401,7 +411,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
{ {
AssemblyList list = SettingsService.Instance.AssemblyListManager.LoadList(name); AssemblyList list = SettingsService.Instance.AssemblyListManager.LoadList(name);
//Only load a new list when it is a different one //Only load a new list when it is a different one
if (list.ListName != AssemblyList?.ListName) if (list.ListName != AssemblyList.ListName)
{ {
ShowAssemblyList(list); ShowAssemblyList(list);
SelectNode(Root); SelectNode(Root);
@ -411,12 +421,9 @@ namespace ICSharpCode.ILSpy.AssemblyTree
private void ShowAssemblyList(AssemblyList assemblyList) private void ShowAssemblyList(AssemblyList assemblyList)
{ {
history.Clear(); history.Clear();
if (this.AssemblyList != null)
{
this.AssemblyList.CollectionChanged -= assemblyList_CollectionChanged;
}
this.AssemblyList = assemblyList; AssemblyList.CollectionChanged -= assemblyList_CollectionChanged;
AssemblyList = assemblyList;
assemblyList.CollectionChanged += assemblyList_CollectionChanged; assemblyList.CollectionChanged += assemblyList_CollectionChanged;
@ -512,6 +519,10 @@ namespace ICSharpCode.ILSpy.AssemblyTree
{ {
activeView?.ScrollIntoView(node); activeView?.ScrollIntoView(node);
SelectedItem = node; SelectedItem = node;
Dispatcher.BeginInvoke(DispatcherPriority.Background, () => {
activeView?.ScrollIntoView(node);
});
} }
} }
@ -527,32 +538,12 @@ namespace ICSharpCode.ILSpy.AssemblyTree
return; return;
} }
if (SelectedItems.SequenceEqual(nodesList)) foreach (var node in nodesList)
{ {
return; activeView?.ScrollIntoView(node);
} }
if (this.isNavigatingHistory) SelectedItems = nodesList.ToArray();
{
SelectedItems.Clear();
foreach (var node in nodesList)
{
activeView?.ScrollIntoView(node);
SelectedItems.Add(node);
}
}
else
{
// defer selection change, so it does not interfere with the focus of the tab page.
Dispatcher.BeginInvoke(() => {
SelectedItems.Clear();
foreach (var node in nodesList)
{
activeView?.ScrollIntoView(node);
SelectedItems.Add(node);
}
});
}
} }
/// <summary> /// <summary>
@ -649,8 +640,6 @@ namespace ICSharpCode.ILSpy.AssemblyTree
MainWindow.OpenLink(opCode.Link); MainWindow.OpenLink(opCode.Link);
break; break;
case EntityReference unresolvedEntity: case EntityReference unresolvedEntity:
if (AssemblyList is null)
break;
string protocol = unresolvedEntity.Protocol; string protocol = unresolvedEntity.Protocol;
var file = unresolvedEntity.ResolveAssembly(AssemblyList); var file = unresolvedEntity.ResolveAssembly(AssemblyList);
if (file == null) if (file == null)
@ -696,8 +685,6 @@ namespace ICSharpCode.ILSpy.AssemblyTree
AssemblyTreeNode? lastNode = null; AssemblyTreeNode? lastNode = null;
var assemblyList = AssemblyList; var assemblyList = AssemblyList;
if (assemblyList is null)
return;
foreach (string file in fileNames) foreach (string file in fileNames)
{ {
@ -714,7 +701,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
{ {
lastNode = node; lastNode = node;
activeView?.ScrollIntoView(node); activeView?.ScrollIntoView(node);
SelectedItems.Add(node); SelectedItems = [.. SelectedItems, node];
} }
} }
} }
@ -729,7 +716,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
private void TreeView_SelectionChanged() private void TreeView_SelectionChanged()
{ {
if (SelectedItems.Count > 0) if (SelectedItems.Length > 0)
{ {
var activeTabPage = DockWorkspace.Instance.ActiveTabPage; var activeTabPage = DockWorkspace.Instance.ActiveTabPage;
@ -777,13 +764,13 @@ namespace ICSharpCode.ILSpy.AssemblyTree
} }
} }
private void DecompileSelectedNodes(DecompilerTextViewState? newState = null) public void DecompileSelectedNodes(DecompilerTextViewState? newState = null)
{ {
var activeTabPage = DockWorkspace.Instance.ActiveTabPage; var activeTabPage = DockWorkspace.Instance.ActiveTabPage;
activeTabPage.SupportsLanguageSwitching = true; activeTabPage.SupportsLanguageSwitching = true;
if (SelectedItems.Count == 1) if (SelectedItems.Length == 1)
{ {
if (SelectedItem is ILSpyTreeNode node && node.View(activeTabPage)) if (SelectedItem is ILSpyTreeNode node && node.View(activeTabPage))
return; return;
@ -818,23 +805,29 @@ namespace ICSharpCode.ILSpy.AssemblyTree
public void NavigateHistory(bool forward) public void NavigateHistory(bool forward)
{ {
isNavigatingHistory = true; try
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, () => isNavigatingHistory = false); {
isNavigatingHistory = true;
TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage;
var state = tabPage.GetState(); var state = tabPage.GetState();
if (state != null) if (state != null)
history.UpdateCurrent(new NavigationState(tabPage, state)); history.UpdateCurrent(new NavigationState(tabPage, state));
var newState = forward ? history.GoForward() : history.GoBack(); var newState = forward ? history.GoForward() : history.GoBack();
TabPageModel activeTabPage = newState.TabPage; TabPageModel activeTabPage = newState.TabPage;
if (!DockWorkspace.Instance.TabPages.Contains(activeTabPage)) if (!DockWorkspace.Instance.TabPages.Contains(activeTabPage))
DockWorkspace.Instance.AddTabPage(activeTabPage); DockWorkspace.Instance.AddTabPage(activeTabPage);
else else
DockWorkspace.Instance.ActiveTabPage = activeTabPage; DockWorkspace.Instance.ActiveTabPage = activeTabPage;
SelectNodes(newState.TreeNodes); SelectNodes(newState.TreeNodes);
}
finally
{
isNavigatingHistory = false;
}
} }
public bool CanNavigateBack => history.CanNavigateBack; public bool CanNavigateBack => history.CanNavigateBack;
@ -907,11 +900,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
{ {
var path = GetPathForNode(SelectedItem); var path = GetPathForNode(SelectedItem);
if (AssemblyList != null) ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(AssemblyList.ListName));
{
ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(AssemblyList.ListName));
}
SelectNode(FindNodeByPath(path, true), inNewTabPage: false); SelectNode(FindNodeByPath(path, true), inNewTabPage: false);
RefreshDecompiledView(); RefreshDecompiledView();
@ -920,7 +909,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
private void UnselectAll() private void UnselectAll()
{ {
SelectedItems.Clear(); SelectedItems = [];
} }
private IEnumerable<SharpTreeNode> GetTopLevelSelection() private IEnumerable<SharpTreeNode> GetTopLevelSelection()
@ -940,7 +929,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
{ {
using (activeView?.LockUpdates()) using (activeView?.LockUpdates())
{ {
AssemblyList?.Sort(AssemblyComparer.Instance); AssemblyList.Sort(AssemblyComparer.Instance);
} }
} }

27
ILSpy/Commands/DecompileInNewViewCommand.cs

@ -23,9 +23,11 @@ using System.Linq;
using System.Windows.Threading; using System.Windows.Threading;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.ViewModels;
using TomsToolbox.Essentials; using TomsToolbox.Essentials;
@ -35,6 +37,14 @@ namespace ICSharpCode.ILSpy.Commands
[PartCreationPolicy(CreationPolicy.Shared)] [PartCreationPolicy(CreationPolicy.Shared)]
internal sealed class DecompileInNewViewCommand : IContextMenuEntry internal sealed class DecompileInNewViewCommand : IContextMenuEntry
{ {
private readonly AssemblyTreeModel assemblyTreeModel;
[ImportingConstructor]
public DecompileInNewViewCommand(AssemblyTreeModel assemblyTreeModel)
{
this.assemblyTreeModel = assemblyTreeModel;
}
public bool IsVisible(TextViewContext context) public bool IsVisible(TextViewContext context)
{ {
return context.SelectedTreeNodes != null || context.Reference?.Reference is IEntity; return context.SelectedTreeNodes != null || context.Reference?.Reference is IEntity;
@ -47,14 +57,18 @@ namespace ICSharpCode.ILSpy.Commands
public void Execute(TextViewContext context) public void Execute(TextViewContext context)
{ {
var activePane = DockWorkspace.Instance.ActivePane;
DecompileNodes(GetNodes(context).ToArray()); DecompileNodes(GetNodes(context).ToArray());
DockWorkspace.Instance.ActivePane = activePane;
} }
IEnumerable<ILSpyTreeNode> GetNodes(TextViewContext context) IEnumerable<ILSpyTreeNode> GetNodes(TextViewContext context)
{ {
if (context.SelectedTreeNodes != null) if (context.SelectedTreeNodes != null)
{ {
if (context.TreeView.DataContext != MainWindow.Instance.AssemblyTreeModel) if (context.TreeView.DataContext != assemblyTreeModel)
{ {
return context.SelectedTreeNodes.OfType<IMemberTreeNode>().Select(FindTreeNode).ExceptNullItems(); return context.SelectedTreeNodes.OfType<IMemberTreeNode>().Select(FindTreeNode).ExceptNullItems();
} }
@ -65,7 +79,7 @@ namespace ICSharpCode.ILSpy.Commands
} }
else if (context.Reference?.Reference is IEntity entity) else if (context.Reference?.Reference is IEntity entity)
{ {
if (MainWindow.Instance.AssemblyTreeModel.FindTreeNode(entity) is { } node) if (assemblyTreeModel.FindTreeNode(entity) is { } node)
{ {
return new[] { node }; return new[] { node };
} }
@ -76,18 +90,21 @@ namespace ICSharpCode.ILSpy.Commands
{ {
if (node is ILSpyTreeNode ilspyNode) if (node is ILSpyTreeNode ilspyNode)
return ilspyNode; return ilspyNode;
return MainWindow.Instance.AssemblyTreeModel.FindTreeNode(node.Member); return assemblyTreeModel.FindTreeNode(node.Member);
} }
} }
static void DecompileNodes(ILSpyTreeNode[] nodes) void DecompileNodes(ILSpyTreeNode[] nodes)
{ {
if (nodes.Length == 0) if (nodes.Length == 0)
return; return;
DockWorkspace.Instance.AddTabPage(); DockWorkspace.Instance.AddTabPage();
MainWindow.Instance.AssemblyTreeModel.SelectNodes(nodes); if (assemblyTreeModel.SelectedItems.SequenceEqual(nodes))
assemblyTreeModel.DecompileSelectedNodes();
else
assemblyTreeModel.SelectNodes(nodes);
} }
} }
} }

29
ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs

@ -19,6 +19,7 @@
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Linq; using System.Linq;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
@ -27,18 +28,26 @@ namespace ICSharpCode.ILSpy
[PartCreationPolicy(CreationPolicy.Shared)] [PartCreationPolicy(CreationPolicy.Shared)]
class RemoveAssembliesWithLoadErrors : SimpleCommand class RemoveAssembliesWithLoadErrors : SimpleCommand
{ {
private readonly AssemblyTreeModel assemblyTreeModel;
[ImportingConstructor]
public RemoveAssembliesWithLoadErrors(AssemblyTreeModel assemblyTreeModel)
{
this.assemblyTreeModel = assemblyTreeModel;
}
public override bool CanExecute(object parameter) public override bool CanExecute(object parameter)
{ {
return MainWindow.Instance.AssemblyTreeModel.AssemblyList?.GetAssemblies().Any(l => l.HasLoadError) == true; return assemblyTreeModel.AssemblyList.GetAssemblies().Any(l => l.HasLoadError);
} }
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
foreach (var asm in MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies()) foreach (var assembly in assemblyTreeModel.AssemblyList.GetAssemblies())
{ {
if (!asm.HasLoadError) if (!assembly.HasLoadError)
continue; continue;
var node = MainWindow.Instance.AssemblyTreeModel.FindAssemblyNode(asm); var node = MainWindow.Instance.AssemblyTreeModel.FindAssemblyNode(assembly);
if (node != null && node.CanDelete()) if (node != null && node.CanDelete())
node.Delete(); node.Delete();
} }
@ -49,14 +58,22 @@ namespace ICSharpCode.ILSpy
[PartCreationPolicy(CreationPolicy.Shared)] [PartCreationPolicy(CreationPolicy.Shared)]
class ClearAssemblyList : SimpleCommand class ClearAssemblyList : SimpleCommand
{ {
private readonly AssemblyTreeModel assemblyTreeModel;
[ImportingConstructor]
public ClearAssemblyList(AssemblyTreeModel assemblyTreeModel)
{
this.assemblyTreeModel = assemblyTreeModel;
}
public override bool CanExecute(object parameter) public override bool CanExecute(object parameter)
{ {
return MainWindow.Instance.AssemblyTreeModel.AssemblyList?.Count > 0; return assemblyTreeModel.AssemblyList.Count > 0;
} }
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
MainWindow.Instance.AssemblyTreeModel.AssemblyList?.Clear(); assemblyTreeModel.AssemblyList.Clear();
} }
} }
} }

9
ILSpy/Commands/ShowPane.cs

@ -1,5 +1,4 @@
using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpy.ViewModels;
namespace ICSharpCode.ILSpy.Commands namespace ICSharpCode.ILSpy.Commands
@ -30,7 +29,13 @@ namespace ICSharpCode.ILSpy.Commands
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
DockWorkspace.Instance.ActiveTabPage = model; var workspace = DockWorkspace.Instance;
// ensure the tab control is focused before setting the active tab page, else the tab will not be focused
workspace.ActiveTabPage?.Focus();
// reset first, else clicking on the already active tab will not focus the tab and the menu checkmark will not be updated
workspace.ActiveTabPage = null;
workspace.ActiveTabPage = model;
} }
} }
} }

2
ILSpy/Controls/TreeView/SharpTreeView.cs

@ -416,7 +416,9 @@ namespace ICSharpCode.ILSpy.Controls.TreeView
throw new ArgumentNullException("node"); throw new ArgumentNullException("node");
doNotScrollOnExpanding = true; doNotScrollOnExpanding = true;
foreach (SharpTreeNode ancestor in node.Ancestors()) foreach (SharpTreeNode ancestor in node.Ancestors())
{
ancestor.IsExpanded = true; ancestor.IsExpanded = true;
}
doNotScrollOnExpanding = false; doNotScrollOnExpanding = false;
base.ScrollIntoView(node); base.ScrollIntoView(node);
} }

7
ILSpy/Docking/CloseAllDocumentsCommand.cs

@ -1,9 +1,4 @@
using System; using System.ComponentModel.Composition;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;

67
ILSpy/Docking/DockWorkspace.cs

@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
@ -34,7 +35,6 @@ using ICSharpCode.ILSpy.Analyzers;
using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.Search;
using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpyX.Extensions;
using TomsToolbox.Composition; using TomsToolbox.Composition;
using TomsToolbox.Essentials; using TomsToolbox.Essentials;
@ -51,13 +51,19 @@ namespace ICSharpCode.ILSpy.Docking
public static readonly DockWorkspace Instance = new(); public static readonly DockWorkspace Instance = new();
private readonly ObservableCollection<TabPageModel> tabPages = []; private readonly ObservableCollection<TabPageModel> tabPages = [];
private readonly ObservableCollection<ToolPaneModel> toolPanes = [];
private DockingManager dockingManager;
private DockWorkspace() private DockWorkspace()
{ {
this.tabPages.CollectionChanged += TabPages_CollectionChanged; this.tabPages.CollectionChanged += TabPages_CollectionChanged;
TabPages = new(tabPages); TabPages = new(tabPages);
ToolPanes = new(toolPanes);
ToolPanes = exportProvider
.GetExportedValues<ToolPaneModel>("ToolPane")
.OrderBy(item => item.Title)
.ToArray()
.AsReadOnly();
// Make sure there is at least one tab open // Make sure there is at least one tab open
AddTabPage(); AddTabPage();
@ -83,11 +89,16 @@ namespace ICSharpCode.ILSpy.Docking
.ExceptNullItems() .ExceptNullItems()
.Any(assemblyNode => !e.OldItems.Contains(assemblyNode.LoadedAssembly)); .Any(assemblyNode => !e.OldItems.Contains(assemblyNode.LoadedAssembly));
if (!found && tabPages.Count > 1) if (!found)
{ {
tabPages.Remove(tab); tabPages.Remove(tab);
} }
} }
if (tabPages.Count == 0)
{
AddTabPage();
}
} }
private void TabPages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) private void TabPages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
@ -117,11 +128,11 @@ namespace ICSharpCode.ILSpy.Docking
public ReadOnlyObservableCollection<TabPageModel> TabPages { get; } public ReadOnlyObservableCollection<TabPageModel> TabPages { get; }
public ReadOnlyObservableCollection<ToolPaneModel> ToolPanes { get; } public ReadOnlyCollection<ToolPaneModel> ToolPanes { get; }
public bool ShowToolPane(string contentId) public bool ShowToolPane(string contentId)
{ {
var pane = toolPanes.FirstOrDefault(p => p.ContentId == contentId); var pane = ToolPanes.FirstOrDefault(p => p.ContentId == contentId);
if (pane != null) if (pane != null)
{ {
pane.Show(); pane.Show();
@ -132,10 +143,15 @@ namespace ICSharpCode.ILSpy.Docking
public void Remove(PaneModel model) public void Remove(PaneModel model)
{ {
if (model is TabPageModel document) switch (model)
tabPages.Remove(document); {
if (model is ToolPaneModel tool) case TabPageModel document:
tool.IsVisible = false; tabPages.Remove(document);
break;
case ToolPaneModel tool:
tool.IsVisible = false;
break;
}
} }
private TabPageModel activeTabPage = null; private TabPageModel activeTabPage = null;
@ -164,12 +180,17 @@ namespace ICSharpCode.ILSpy.Docking
} }
} }
public PaneModel ActivePane {
get => dockingManager?.ActiveContent as PaneModel;
set {
if (dockingManager is not null)
dockingManager.ActiveContent = value;
}
}
public void InitializeLayout(DockingManager manager) public void InitializeLayout(DockingManager manager)
{ {
var panes = exportProvider.GetExportedValues<ToolPaneModel>("ToolPane").OrderBy(item => item.Title); this.dockingManager = manager;
this.toolPanes.AddRange(panes);
manager.LayoutUpdateStrategy = this; manager.LayoutUpdateStrategy = this;
XmlLayoutSerializer serializer = new XmlLayoutSerializer(manager); XmlLayoutSerializer serializer = new XmlLayoutSerializer(manager);
serializer.LayoutSerializationCallback += LayoutSerializationCallback; serializer.LayoutSerializationCallback += LayoutSerializationCallback;
@ -188,13 +209,13 @@ namespace ICSharpCode.ILSpy.Docking
switch (e.Model) switch (e.Model)
{ {
case LayoutAnchorable la: case LayoutAnchorable la:
e.Content = this.toolPanes.FirstOrDefault(p => p.ContentId == la.ContentId); e.Content = this.ToolPanes.FirstOrDefault(p => p.ContentId == la.ContentId);
e.Cancel = e.Content == null; e.Cancel = e.Content == null;
la.CanDockAsTabbedDocument = false; la.CanDockAsTabbedDocument = false;
if (!e.Cancel) if (e.Content is ToolPaneModel toolPaneModel)
{ {
e.Cancel = ((ToolPaneModel)e.Content).IsVisible; e.Cancel = toolPaneModel.IsVisible;
((ToolPaneModel)e.Content).IsVisible = true; toolPaneModel.IsVisible = true;
} }
break; break;
default: default:
@ -220,16 +241,14 @@ namespace ICSharpCode.ILSpy.Docking
internal void CloseAllTabs() internal void CloseAllTabs()
{ {
foreach (var doc in tabPages.ToArray()) var activePage = ActiveTabPage;
{
if (doc.IsCloseable) tabPages.RemoveWhere(page => page != activePage);
tabPages.Remove(doc);
}
} }
internal void ResetLayout() internal void ResetLayout()
{ {
foreach (var pane in toolPanes) foreach (var pane in ToolPanes)
{ {
pane.IsVisible = false; pane.IsVisible = false;
} }

13
ILSpy/MainWindow.xaml.cs

@ -25,6 +25,7 @@ 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 AvalonDock.Layout.Serialization; using AvalonDock.Layout.Serialization;
@ -70,11 +71,17 @@ namespace ICSharpCode.ILSpy
InitializeComponent(); InitializeComponent();
mainWindowViewModel.Workspace.InitializeLayout(dockManager); InitFileLoaders();
MenuService.Instance.Init(mainMenu, toolBar, InputBindings); Dispatcher.BeginInvoke(DispatcherPriority.Background, () => {
mainWindowViewModel.Workspace.InitializeLayout(dockManager);
MenuService.Instance.Init(mainMenu, toolBar, InputBindings);
InitFileLoaders(); Dispatcher.BeginInvoke(DispatcherPriority.Background, () => {
AssemblyTreeModel.Initialize();
AssemblyTreeModel.Show();
});
});
} }
void SetWindowBounds(Rect bounds) void SetWindowBounds(Rect bounds)

5
ILSpy/Util/MenuService.cs

@ -142,7 +142,7 @@ namespace ICSharpCode.ILSpy.Util
windowMenuItem.Items.Clear(); windowMenuItem.Items.Clear();
var toolItems = dockWorkspace.ToolPanes.ObservableSelect(toolPane => CreateMenuItem(toolPane, inputBindings)); var toolItems = dockWorkspace.ToolPanes.Select(toolPane => CreateMenuItem(toolPane, inputBindings)).ToArray();
var tabItems = dockWorkspace.TabPages.ObservableSelect(tabPage => CreateMenuItem(tabPage, dockWorkspace)); var tabItems = dockWorkspace.TabPages.ObservableSelect(tabPage => CreateMenuItem(tabPage, dockWorkspace));
var allItems = new ObservableCompositeCollection<Control>(defaultItems, [new Separator()], toolItems, [new Separator()], tabItems); var allItems = new ObservableCompositeCollection<Control>(defaultItems, [new Separator()], toolItems, [new Separator()], tabItems);
@ -207,7 +207,8 @@ namespace ICSharpCode.ILSpy.Util
menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding(nameof(dock.ActiveTabPage)) { menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding(nameof(dock.ActiveTabPage)) {
Source = dock, Source = dock,
ConverterParameter = pane, ConverterParameter = pane,
Converter = BinaryOperationConverter.Equality Converter = BinaryOperationConverter.Equality,
Mode = BindingMode.OneWay
}); });
return menuItem; return menuItem;

5
ILSpy/Util/SettingsService.cs

@ -152,6 +152,11 @@ namespace ICSharpCode.ILSpy.Util
} }
} }
public AssemblyList CreateEmptyAssemblyList()
{
return AssemblyListManager.CreateList(string.Empty);
}
private bool reloading; private bool reloading;
public void Reload() public void Reload()

17
ILSpy/ViewModels/TabPageModel.cs

@ -17,10 +17,14 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TextView;
using TomsToolbox.Wpf;
namespace ICSharpCode.ILSpy.ViewModels namespace ICSharpCode.ILSpy.ViewModels
{ {
public class TabPageModel : PaneModel public class TabPageModel : PaneModel
@ -96,6 +100,19 @@ namespace ICSharpCode.ILSpy.ViewModels
tabPage.Title = Properties.Resources.Decompiling; tabPage.Title = Properties.Resources.Decompiling;
action(textView); action(textView);
} }
public static void Focus(this TabPageModel tabPage)
{
if (tabPage.Content is not FrameworkElement content)
return;
var focusable = content
.VisualDescendantsAndSelf()
.OfType<FrameworkElement>()
.FirstOrDefault(item => item.Focusable);
focusable?.Focus();
}
} }
public interface IHaveState public interface IHaveState

Loading…
Cancel
Save