Browse Source

Analyzer pane view-model and ProDataGrid view

Replace the placeholder stub with the live pane.

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
4de7cfe174
  1. 33
      ILSpy/Analyzers/AnalyzerTreeView.axaml
  2. 109
      ILSpy/Analyzers/AnalyzerTreeView.axaml.cs
  3. 72
      ILSpy/Analyzers/AnalyzerTreeViewModel.cs

33
ILSpy/Analyzers/AnalyzerTreeView.axaml

@ -6,7 +6,34 @@ @@ -6,7 +6,34 @@
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="200"
x:Class="ILSpy.Analyzers.AnalyzerTreeView"
x:DataType="analyzers:AnalyzerTreeViewModel">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="Gray" FontStyle="Italic"
Text="Analyzer results will appear here." />
<DataGrid Name="TreeGrid"
Classes="hierarchical"
AutoGenerateColumns="False"
HeadersVisibility="None"
HierarchicalRowsEnabled="True"
GridLinesVisibility="None"
CanUserResizeColumns="False"
SelectionMode="Extended"
SelectionChanged="OnTreeGridSelectionChanged">
<DataGrid.Columns>
<DataGridHierarchicalColumn Header="Name" Width="*"
IsReadOnly="True"
x:CompileBindings="False"
Binding="{Binding Item}">
<DataGridHierarchicalColumn.CellTemplate>
<DataTemplate>
<Grid ColumnDefinitions="Auto,7,*"
VerticalAlignment="Center" Margin="4,0"
ToolTip.Tip="{Binding ToolTip}">
<Image Grid.Column="0" Source="{Binding Icon}"
Width="16" Height="16"
VerticalAlignment="Center" />
<TextBlock Grid.Column="2" Text="{Binding Text}"
VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</DataGridHierarchicalColumn.CellTemplate>
</DataGridHierarchicalColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>

109
ILSpy/Analyzers/AnalyzerTreeView.axaml.cs

@ -16,15 +16,124 @@ @@ -16,15 +16,124 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.DataGridHierarchical;
using ICSharpCode.ILSpyX.TreeView;
namespace ILSpy.Analyzers
{
public partial class AnalyzerTreeView : UserControl
{
bool syncingSelection;
AnalyzerTreeViewModel? boundModel;
public AnalyzerTreeView()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
DetachFromModel();
if (DataContext is AnalyzerTreeViewModel model)
AttachToModel(model);
}
void AttachToModel(AnalyzerTreeViewModel model)
{
boundModel = model;
BindTree(model.Root);
model.SelectedItems.CollectionChanged += OnModelSelectionChanged;
}
void DetachFromModel()
{
if (boundModel == null)
return;
boundModel.SelectedItems.CollectionChanged -= OnModelSelectionChanged;
boundModel = null;
}
void BindTree(SharpTreeNode root)
{
var options = new HierarchicalOptions<SharpTreeNode> {
ChildrenSelector = node => {
node.EnsureLazyChildren();
return node.Children;
},
IsLeafSelector = node => !node.ShowExpander,
VirtualizeChildren = false,
// Two-way sync of SharpTreeNode.IsExpanded with the row chevron.
IsExpandedPropertyPath = nameof(SharpTreeNode.IsExpanded),
};
var hierarchicalModel = new HierarchicalModel<SharpTreeNode>(options);
hierarchicalModel.SetRoots(root.Children);
TreeGrid.HierarchicalModel = hierarchicalModel;
}
void OnTreeGridSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (syncingSelection || boundModel == null)
return;
syncingSelection = true;
try
{
var current = new HashSet<SharpTreeNode>();
foreach (var item in TreeGrid.SelectedItems)
{
var node = item is HierarchicalNode hn && hn.Item is SharpTreeNode t ? t
: item as SharpTreeNode;
if (node != null)
current.Add(node);
}
for (int i = boundModel.SelectedItems.Count - 1; i >= 0; i--)
{
if (!current.Contains(boundModel.SelectedItems[i]))
boundModel.SelectedItems.RemoveAt(i);
}
foreach (var node in current)
{
if (!boundModel.SelectedItems.Contains(node))
boundModel.SelectedItems.Add(node);
}
}
finally
{
syncingSelection = false;
}
}
void OnModelSelectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (syncingSelection || boundModel == null)
return;
syncingSelection = true;
try
{
var current = TreeGrid.SelectedItems.OfType<object>().ToList();
foreach (var item in current)
TreeGrid.SelectedItems.Remove(item);
if (TreeGrid.HierarchicalModel is IHierarchicalModel model)
{
foreach (var node in boundModel.SelectedItems)
{
var hNode = model.FindNode(node);
if (hNode != null)
TreeGrid.SelectedItems.Add(hNode);
}
}
}
finally
{
syncingSelection = false;
}
}
}
}

72
ILSpy/Analyzers/AnalyzerTreeViewModel.cs

@ -16,8 +16,15 @@ @@ -16,8 +16,15 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.ObjectModel;
using System.Composition;
using System.Linq;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX.TreeView;
using ILSpy.Analyzers.TreeNodes;
using ILSpy.Commands;
using ILSpy.ViewModels;
@ -34,6 +41,71 @@ namespace ILSpy.Analyzers @@ -34,6 +41,71 @@ namespace ILSpy.Analyzers
{
Id = PaneContentId;
Title = "Analyzer";
// IsRoot is computed (Parent == null) — the node is the root because we never
// add it to another node's Children collection.
Root = new AnalyzerRootNode();
}
/// <summary>
/// Root of the analyzer pane's tree. Pre-populated as a child-less node; entries are
/// added through <see cref="Analyze(IEntity)"/>.
/// </summary>
public AnalyzerRootNode Root { get; }
/// <summary>
/// Two-way binding sink for the tree grid's selection. Drives the context-menu's
/// <c>TextViewContext.SelectedTreeNodes</c> so menu entries see the live selection.
/// </summary>
public ObservableCollection<SharpTreeNode> SelectedItems { get; } = new();
/// <summary>
/// Adds <paramref name="entity"/> to the analyzer pane (reusing the existing row if
/// the entity is already analysed) and selects it. The pane's focus / dock-activation
/// is the caller's responsibility — typically the context-menu entry that triggered
/// the analysis.
/// </summary>
public AnalyzerEntityTreeNode Analyze(IEntity entity)
{
ArgumentNullException.ThrowIfNull(entity);
var existing = Root.Children
.OfType<AnalyzerEntityTreeNode>()
.FirstOrDefault(n => IsSameEntity(n.Member, entity));
if (existing != null)
{
SyncSelection(existing);
return existing;
}
var node = Wrap(entity);
Root.Children.Add(node);
SyncSelection(node);
return node;
}
static bool IsSameEntity(IEntity? a, IEntity b)
{
if (a == null)
return false;
return a.MetadataToken == b.MetadataToken
&& ReferenceEquals(a.ParentModule, b.ParentModule);
}
void SyncSelection(SharpTreeNode node)
{
SelectedItems.Clear();
SelectedItems.Add(node);
}
static AnalyzerEntityTreeNode Wrap(IEntity entity)
{
return entity switch {
ITypeDefinition type => new AnalyzedTypeTreeNode(type, source: entity),
IMethod method => new AnalyzedMethodTreeNode(method, source: entity),
IField field => new AnalyzedFieldTreeNode(field, source: entity),
IProperty property => new AnalyzedPropertyTreeNode(property, source: entity),
IEvent ev => new AnalyzedEventTreeNode(ev, source: entity),
_ => throw new ArgumentOutOfRangeException(nameof(entity),
$"Entity {entity.GetType().FullName} is not supported by the analyzer pane.")
};
}
}
}

Loading…
Cancel
Save