mirror of https://github.com/icsharpcode/ILSpy.git
11 changed files with 626 additions and 25 deletions
@ -0,0 +1,58 @@ |
|||||||
|
// Copyright (c) 2025 AlphaSierraPapa 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 ICSharpCode.ILSpy.AssemblyTree; |
||||||
|
using ICSharpCode.ILSpy.Docking; |
||||||
|
using ICSharpCode.ILSpy.TreeNodes; |
||||||
|
using ICSharpCode.ILSpy.ViewModels; |
||||||
|
using ICSharpCode.ILSpy.Views; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy |
||||||
|
{ |
||||||
|
[ExportContextMenuEntry(Header = "Compare...", Order = 9999)] |
||||||
|
[Shared] |
||||||
|
internal sealed class CompareContextMenuEntry(AssemblyTreeModel assemblyTreeModel, DockWorkspace dockWorkspace) : IContextMenuEntry |
||||||
|
{ |
||||||
|
public void Execute(TextViewContext context) |
||||||
|
{ |
||||||
|
var left = ((AssemblyTreeNode)context.SelectedTreeNodes[0]).LoadedAssembly; |
||||||
|
var right = ((AssemblyTreeNode)context.SelectedTreeNodes[1]).LoadedAssembly; |
||||||
|
|
||||||
|
var tabPage = dockWorkspace.AddTabPage(); |
||||||
|
|
||||||
|
tabPage.Title = $"Compare {left.Text} - {right.Text}"; |
||||||
|
tabPage.SupportsLanguageSwitching = false; |
||||||
|
tabPage.FrozenContent = true; |
||||||
|
var compareView = new CompareView(); |
||||||
|
compareView.DataContext = new CompareViewModel(assemblyTreeModel, left, right); |
||||||
|
tabPage.Content = compareView; |
||||||
|
} |
||||||
|
|
||||||
|
public bool IsEnabled(TextViewContext context) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public bool IsVisible(TextViewContext context) |
||||||
|
{ |
||||||
|
return context.SelectedTreeNodes is [AssemblyTreeNode { LoadedAssembly.IsLoadedAsValidAssembly: true }, AssemblyTreeNode { LoadedAssembly.IsLoadedAsValidAssembly: true }]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,434 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Diagnostics.CodeAnalysis; |
||||||
|
using System.Reflection.Metadata; |
||||||
|
|
||||||
|
using ICSharpCode.Decompiler.CSharp.OutputVisitor; |
||||||
|
using ICSharpCode.ILSpy.AssemblyTree; |
||||||
|
using ICSharpCode.ILSpyX; |
||||||
|
|
||||||
|
using TomsToolbox.Wpf; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.ViewModels |
||||||
|
{ |
||||||
|
using System.Linq; |
||||||
|
|
||||||
|
using ICSharpCode.Decompiler; |
||||||
|
using ICSharpCode.Decompiler.TypeSystem; |
||||||
|
using ICSharpCode.ILSpy.TreeNodes; |
||||||
|
|
||||||
|
class CompareViewModel : ObservableObject |
||||||
|
{ |
||||||
|
private readonly AssemblyTreeModel assemblyTreeModel; |
||||||
|
private LoadedAssembly leftAssembly; |
||||||
|
private LoadedAssembly rightAssembly; |
||||||
|
private LoadedAssembly[] assemblies; |
||||||
|
private ComparisonEntryTreeNode root; |
||||||
|
|
||||||
|
public CompareViewModel(AssemblyTreeModel assemblyTreeModel, LoadedAssembly left, LoadedAssembly right) |
||||||
|
{ |
||||||
|
MessageBus<CurrentAssemblyListChangedEventArgs>.Subscribers += (sender, e) => this.Assemblies = assemblyTreeModel.AssemblyList.GetAssemblies(); |
||||||
|
|
||||||
|
leftAssembly = left; |
||||||
|
rightAssembly = right; |
||||||
|
assemblies = assemblyTreeModel.AssemblyList.GetAssemblies(); |
||||||
|
|
||||||
|
var leftTree = CreateEntityTree(new DecompilerTypeSystem(leftAssembly.GetMetadataFileOrNull(), leftAssembly.GetAssemblyResolver())); |
||||||
|
var rightTree = CreateEntityTree(new DecompilerTypeSystem(rightAssembly.GetMetadataFileOrNull(), rightAssembly.GetAssemblyResolver())); |
||||||
|
|
||||||
|
this.root = new ComparisonEntryTreeNode(MergeTrees(leftTree.Item2, rightTree.Item2)); |
||||||
|
} |
||||||
|
|
||||||
|
public LoadedAssembly[] Assemblies { |
||||||
|
get => assemblies; |
||||||
|
set { |
||||||
|
if (assemblies != value) |
||||||
|
{ |
||||||
|
assemblies = value; |
||||||
|
OnPropertyChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public LoadedAssembly LeftAssembly { |
||||||
|
get => leftAssembly; |
||||||
|
set { |
||||||
|
if (leftAssembly != value) |
||||||
|
{ |
||||||
|
leftAssembly = value; |
||||||
|
OnPropertyChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public LoadedAssembly RightAssembly { |
||||||
|
get => rightAssembly; |
||||||
|
set { |
||||||
|
if (rightAssembly != value) |
||||||
|
{ |
||||||
|
rightAssembly = value; |
||||||
|
OnPropertyChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public ComparisonEntryTreeNode RootEntry { |
||||||
|
get => root; |
||||||
|
set { |
||||||
|
if (root != value) |
||||||
|
{ |
||||||
|
root = value; |
||||||
|
OnPropertyChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Entry MergeTrees(Entry a, Entry b) |
||||||
|
{ |
||||||
|
var m = new Entry() { |
||||||
|
Entity = a.Entity, |
||||||
|
Signature = a.Signature, |
||||||
|
}; |
||||||
|
|
||||||
|
if (a.Children?.Count > 0 && b.Children?.Count > 0) |
||||||
|
{ |
||||||
|
var diff = CalculateDiff(a.Children, b.Children); |
||||||
|
m.Children ??= new(); |
||||||
|
|
||||||
|
foreach (var (left, right) in diff) |
||||||
|
{ |
||||||
|
if (left != null && right != null) |
||||||
|
m.Children.Add(MergeTrees(left, right)); |
||||||
|
else if (left != null) |
||||||
|
m.Children.Add(left); |
||||||
|
else if (right != null) |
||||||
|
m.Children.Add(right); |
||||||
|
else |
||||||
|
Debug.Fail("wh00t?"); |
||||||
|
m.Children[^1].Parent = m; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (a.Children?.Count > 0) |
||||||
|
{ |
||||||
|
m.Children ??= new(); |
||||||
|
foreach (var child in a.Children) |
||||||
|
{ |
||||||
|
child.Parent = m; |
||||||
|
m.Children.Add(child); |
||||||
|
} |
||||||
|
} |
||||||
|
else if (b.Children?.Count > 0) |
||||||
|
{ |
||||||
|
m.Children ??= new(); |
||||||
|
foreach (var child in b.Children) |
||||||
|
{ |
||||||
|
child.Parent = m; |
||||||
|
m.Children.Add(child); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return m; |
||||||
|
} |
||||||
|
|
||||||
|
(List<Entry>, Entry) CreateEntityTree(DecompilerTypeSystem typeSystem) |
||||||
|
{ |
||||||
|
var module = typeSystem.MainModule; |
||||||
|
var metadata = module.MetadataFile.Metadata; |
||||||
|
var ambience = new CSharpAmbience(); |
||||||
|
ambience.ConversionFlags = ICSharpCode.Decompiler.Output.ConversionFlags.All & ~ICSharpCode.Decompiler.Output.ConversionFlags.ShowDeclaringType; |
||||||
|
|
||||||
|
List<Entry> results = new(); |
||||||
|
Dictionary<TypeDefinitionHandle, Entry> typeEntries = new(); |
||||||
|
Dictionary<string, Entry> namespaceEntries = new(StringComparer.Ordinal); |
||||||
|
|
||||||
|
Entry root = new Entry { Entity = null!, Signature = module.FullAssemblyName }; |
||||||
|
|
||||||
|
// typeEntries need a different signature: must include list of base types
|
||||||
|
|
||||||
|
Entry? TryCreateEntry(IEntity entity) |
||||||
|
{ |
||||||
|
if (entity.EffectiveAccessibility() != Accessibility.Public) |
||||||
|
return null; |
||||||
|
|
||||||
|
Entry? parent = null; |
||||||
|
|
||||||
|
if (entity.DeclaringTypeDefinition != null |
||||||
|
&& !typeEntries.TryGetValue((TypeDefinitionHandle)entity.DeclaringTypeDefinition.MetadataToken, out parent)) |
||||||
|
{ |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
var entry = new Entry { |
||||||
|
Signature = ambience.ConvertSymbol(entity), |
||||||
|
Entity = entity, |
||||||
|
Parent = parent, |
||||||
|
}; |
||||||
|
|
||||||
|
if (parent != null) |
||||||
|
{ |
||||||
|
parent.Children ??= new(); |
||||||
|
parent.Children.Add(entry); |
||||||
|
} |
||||||
|
|
||||||
|
return entry; |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var typeDefHandle in metadata.TypeDefinitions) |
||||||
|
{ |
||||||
|
var typeDef = module.GetDefinition(typeDefHandle); |
||||||
|
|
||||||
|
if (typeDef.EffectiveAccessibility() != Accessibility.Public) |
||||||
|
continue; |
||||||
|
|
||||||
|
var entry = typeEntries[typeDefHandle] = new Entry { |
||||||
|
Signature = ambience.ConvertSymbol(typeDef), |
||||||
|
Entity = typeDef |
||||||
|
}; |
||||||
|
|
||||||
|
if (typeDef.DeclaringType == null) |
||||||
|
{ |
||||||
|
if (!namespaceEntries.TryGetValue(typeDef.Namespace, out var nsEntry)) |
||||||
|
{ |
||||||
|
namespaceEntries[typeDef.Namespace] = nsEntry = new Entry { Parent = root, Signature = typeDef.Namespace, Entity = null! }; |
||||||
|
root.Children ??= new(); |
||||||
|
root.Children.Add(nsEntry); |
||||||
|
} |
||||||
|
|
||||||
|
entry.Parent = nsEntry; |
||||||
|
nsEntry.Children ??= new(); |
||||||
|
nsEntry.Children.Add(entry); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var fieldHandle in metadata.FieldDefinitions) |
||||||
|
{ |
||||||
|
var fieldDef = module.GetDefinition(fieldHandle); |
||||||
|
var entry = TryCreateEntry(fieldDef); |
||||||
|
|
||||||
|
if (entry != null) |
||||||
|
results.Add(entry); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var eventHandle in metadata.EventDefinitions) |
||||||
|
{ |
||||||
|
var eventDef = module.GetDefinition(eventHandle); |
||||||
|
var entry = TryCreateEntry(eventDef); |
||||||
|
|
||||||
|
if (entry != null) |
||||||
|
results.Add(entry); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var propertyHandle in metadata.PropertyDefinitions) |
||||||
|
{ |
||||||
|
var propertyDef = module.GetDefinition(propertyHandle); |
||||||
|
var entry = TryCreateEntry(propertyDef); |
||||||
|
|
||||||
|
if (entry != null) |
||||||
|
results.Add(entry); |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var methodHandle in metadata.MethodDefinitions) |
||||||
|
{ |
||||||
|
var methodDef = module.GetDefinition(methodHandle); |
||||||
|
|
||||||
|
if (methodDef.AccessorOwner != null) |
||||||
|
continue; |
||||||
|
|
||||||
|
var entry = TryCreateEntry(methodDef); |
||||||
|
|
||||||
|
if (entry != null) |
||||||
|
results.Add(entry); |
||||||
|
} |
||||||
|
|
||||||
|
return (results, root); |
||||||
|
} |
||||||
|
|
||||||
|
List<(Entry? Left, Entry? Right)> CalculateDiff(List<Entry> left, List<Entry> right) |
||||||
|
{ |
||||||
|
Dictionary<string, List<Entry>> leftMap = new(); |
||||||
|
Dictionary<string, List<Entry>> rightMap = new(); |
||||||
|
|
||||||
|
foreach (var item in left) |
||||||
|
{ |
||||||
|
string key = item.Signature; |
||||||
|
if (leftMap.ContainsKey(key)) |
||||||
|
leftMap[key].Add(item); |
||||||
|
else |
||||||
|
leftMap[key] = [item]; |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var item in right) |
||||||
|
{ |
||||||
|
string key = item.Signature; |
||||||
|
if (rightMap.ContainsKey(key)) |
||||||
|
rightMap[key].Add(item); |
||||||
|
else |
||||||
|
rightMap[key] = [item]; |
||||||
|
} |
||||||
|
|
||||||
|
List<(Entry? Left, Entry? Right)> results = new(); |
||||||
|
|
||||||
|
foreach (var (key, items) in leftMap) |
||||||
|
{ |
||||||
|
if (rightMap.TryGetValue(key, out var rightEntries)) |
||||||
|
{ |
||||||
|
foreach (var item in items) |
||||||
|
{ |
||||||
|
var other = rightEntries.Find(_ => EntryComparer.Instance.Equals(_, item)); |
||||||
|
results.Add((item, other)); |
||||||
|
if (other == null) |
||||||
|
{ |
||||||
|
item.Kind = DiffKind.Remove; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
foreach (var item in items) |
||||||
|
{ |
||||||
|
item.Kind = DiffKind.Remove; |
||||||
|
results.Add((item, null)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var (key, items) in rightMap) |
||||||
|
{ |
||||||
|
if (leftMap.TryGetValue(key, out var leftEntries)) |
||||||
|
{ |
||||||
|
foreach (var item in items) |
||||||
|
{ |
||||||
|
if (!leftEntries.Any(_ => EntryComparer.Instance.Equals(_, item))) |
||||||
|
{ |
||||||
|
results.Add((null, item)); |
||||||
|
item.Kind = DiffKind.Add; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
foreach (var item in items) |
||||||
|
{ |
||||||
|
item.Kind = DiffKind.Add; |
||||||
|
results.Add((null, item)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return results; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] |
||||||
|
public class Entry |
||||||
|
{ |
||||||
|
private DiffKind kind = DiffKind.None; |
||||||
|
|
||||||
|
public DiffKind Kind { |
||||||
|
get { |
||||||
|
if (Children == null || Children.Count == 0) |
||||||
|
{ |
||||||
|
return kind; |
||||||
|
} |
||||||
|
|
||||||
|
int addCount = 0, removeCount = 0, updateCount = 0; |
||||||
|
|
||||||
|
foreach (var item in Children) |
||||||
|
{ |
||||||
|
switch (item.Kind) |
||||||
|
{ |
||||||
|
case DiffKind.Add: |
||||||
|
addCount++; |
||||||
|
break; |
||||||
|
case DiffKind.Remove: |
||||||
|
removeCount++; |
||||||
|
break; |
||||||
|
case DiffKind.Update: |
||||||
|
updateCount++; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (addCount == Children.Count) |
||||||
|
return DiffKind.Add; |
||||||
|
if (removeCount == Children.Count) |
||||||
|
return DiffKind.Remove; |
||||||
|
if (addCount > 0 || removeCount > 0 || updateCount > 0) |
||||||
|
return DiffKind.Update; |
||||||
|
return DiffKind.None; |
||||||
|
} |
||||||
|
set { |
||||||
|
if (Children == null || Children.Count == 0) |
||||||
|
{ |
||||||
|
kind = value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
public required string Signature { get; init; } |
||||||
|
public required IEntity Entity { get; init; } |
||||||
|
|
||||||
|
public Entry? Parent { get; set; } |
||||||
|
public List<Entry>? Children { get; set; } |
||||||
|
|
||||||
|
private string GetDebuggerDisplay() |
||||||
|
{ |
||||||
|
return $"Entry{Kind}{Entity?.ToString() ?? Signature}"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public class EntryComparer : IEqualityComparer<Entry> |
||||||
|
{ |
||||||
|
public static EntryComparer Instance = new(); |
||||||
|
|
||||||
|
public bool Equals(Entry? x, Entry? y) |
||||||
|
{ |
||||||
|
return x?.Signature == y?.Signature; |
||||||
|
} |
||||||
|
|
||||||
|
public int GetHashCode([DisallowNull] Entry obj) |
||||||
|
{ |
||||||
|
return obj.Signature.GetHashCode(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public enum DiffKind |
||||||
|
{ |
||||||
|
None = ' ', |
||||||
|
Add = '+', |
||||||
|
Remove = '-', |
||||||
|
Update = '~' |
||||||
|
} |
||||||
|
|
||||||
|
class ComparisonEntryTreeNode : ILSpyTreeNode |
||||||
|
{ |
||||||
|
private readonly Entry entry; |
||||||
|
|
||||||
|
public ComparisonEntryTreeNode(Entry entry) |
||||||
|
{ |
||||||
|
this.entry = entry; |
||||||
|
this.LazyLoading = entry.Children != null; |
||||||
|
} |
||||||
|
|
||||||
|
protected override void LoadChildren() |
||||||
|
{ |
||||||
|
if (entry.Children == null) |
||||||
|
return; |
||||||
|
|
||||||
|
foreach (var item in entry.Children) |
||||||
|
{ |
||||||
|
this.Children.Add(new ComparisonEntryTreeNode(item)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public override object Text => entry.Signature; |
||||||
|
|
||||||
|
public override object Icon => Images.GetIcon(; |
||||||
|
|
||||||
|
public DiffKind Difference => entry.Kind; |
||||||
|
|
||||||
|
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
<UserControl x:Class="ICSharpCode.ILSpy.Views.CompareView" |
||||||
|
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:local="clr-namespace:ICSharpCode.ILSpy.Views" |
||||||
|
xmlns:stv="clr-namespace:ICSharpCode.ILSpy.Controls.TreeView" |
||||||
|
mc:Ignorable="d" |
||||||
|
d:DesignHeight="450" d:DesignWidth="800"> |
||||||
|
<Grid> |
||||||
|
<Grid.RowDefinitions> |
||||||
|
<RowDefinition Height="Auto" /> |
||||||
|
<RowDefinition Height="*" /> |
||||||
|
</Grid.RowDefinitions> |
||||||
|
<StackPanel Orientation="Horizontal" Margin="5"> |
||||||
|
<Label Content="Left:" /> |
||||||
|
<ComboBox SelectedValue="{Binding LeftAssembly}" ItemsSource="{Binding Assemblies}" DisplayMemberPath="Text" /> |
||||||
|
<Label Content="Right:" /> |
||||||
|
<ComboBox SelectedValue="{Binding RightAssembly}" ItemsSource="{Binding Assemblies}" DisplayMemberPath="Text" /> |
||||||
|
</StackPanel> |
||||||
|
<stv:SharpTreeView Grid.Row="1" Root="{Binding RootEntry}" ShowRoot="True"> |
||||||
|
<stv:SharpTreeView.ItemContainerStyle> |
||||||
|
<Style TargetType="stv:SharpTreeViewItem"> |
||||||
|
<Setter Property="Template"> |
||||||
|
<Setter.Value> |
||||||
|
<ControlTemplate TargetType="{x:Type stv:SharpTreeViewItem}"> |
||||||
|
<Border Background="Transparent"> |
||||||
|
<Border Background="{TemplateBinding Background}"> |
||||||
|
<stv:SharpTreeNodeView x:Name="nodeView" HorizontalAlignment="Left" /> |
||||||
|
</Border> |
||||||
|
</Border> |
||||||
|
<ControlTemplate.Triggers> |
||||||
|
<DataTrigger Binding="{Binding Difference}" Value="Update"> |
||||||
|
<Setter Property="Background" Value="LightBlue" /> |
||||||
|
</DataTrigger> |
||||||
|
<DataTrigger Binding="{Binding Difference}" Value="Add"> |
||||||
|
<Setter Property="Background" Value="LightGreen" /> |
||||||
|
</DataTrigger> |
||||||
|
<DataTrigger Binding="{Binding Difference}" Value="Remove"> |
||||||
|
<Setter Property="Background" Value="LightPink" /> |
||||||
|
</DataTrigger> |
||||||
|
<DataTrigger Binding="{Binding IsPublicAPI}" Value="False"> |
||||||
|
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> |
||||||
|
</DataTrigger> |
||||||
|
<Trigger Property="IsSelected" Value="True"> |
||||||
|
<Setter TargetName="nodeView" Property="TextBackground" |
||||||
|
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" /> |
||||||
|
<Setter TargetName="nodeView" Property="Foreground" |
||||||
|
Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" /> |
||||||
|
</Trigger> |
||||||
|
<Trigger Property="IsEnabled" Value="False"> |
||||||
|
<Setter TargetName="nodeView" Property="Foreground" |
||||||
|
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> |
||||||
|
</Trigger> |
||||||
|
</ControlTemplate.Triggers> |
||||||
|
</ControlTemplate> |
||||||
|
</Setter.Value> |
||||||
|
</Setter> |
||||||
|
</Style> |
||||||
|
</stv:SharpTreeView.ItemContainerStyle> |
||||||
|
</stv:SharpTreeView> |
||||||
|
</Grid> |
||||||
|
</UserControl> |
@ -0,0 +1,26 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Text; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Controls; |
||||||
|
using System.Windows.Data; |
||||||
|
using System.Windows.Documents; |
||||||
|
using System.Windows.Input; |
||||||
|
using System.Windows.Media; |
||||||
|
using System.Windows.Media.Imaging; |
||||||
|
using System.Windows.Navigation; |
||||||
|
using System.Windows.Shapes; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.Views |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for CompareView.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class CompareView : UserControl |
||||||
|
{ |
||||||
|
public CompareView() |
||||||
|
{ |
||||||
|
InitializeComponent(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue