From 0f5e46336f234acb901f7721a4e2dfedd53a60cf Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 11 Sep 2020 11:02:22 +0200 Subject: [PATCH] Fix #2151: Improve performance of Metadata DataGridCell --- ILSpy/Commands/DecompileCommand.cs | 43 -------- ILSpy/ILSpy.csproj | 3 +- ILSpy/Metadata/CoffHeaderTreeNode.cs | 1 + ILSpy/Metadata/DataGridCustomTextColumn.cs | 59 ---------- ILSpy/Metadata/GoToTokenCommand.cs | 101 +++++++++++++++++ ILSpy/Metadata/Helpers.cs | 46 +++----- ILSpy/Metadata/MetaDataGrid.cs | 119 +++++++++++++++++++++ ILSpy/Metadata/MetadataTableViews.xaml | 45 ++++---- ILSpy/Metadata/OptionalHeaderTreeNode.cs | 10 +- 9 files changed, 266 insertions(+), 161 deletions(-) delete mode 100644 ILSpy/Metadata/DataGridCustomTextColumn.cs create mode 100644 ILSpy/Metadata/GoToTokenCommand.cs create mode 100644 ILSpy/Metadata/MetaDataGrid.cs diff --git a/ILSpy/Commands/DecompileCommand.cs b/ILSpy/Commands/DecompileCommand.cs index 0902c03e9..ff0a995df 100644 --- a/ILSpy/Commands/DecompileCommand.cs +++ b/ILSpy/Commands/DecompileCommand.cs @@ -16,15 +16,9 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using System.Linq; -using System.Reflection; -using System.Reflection.Metadata.Ecma335; -using System.Windows.Controls; -using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.ILSpy.Metadata; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TreeNodes; @@ -73,41 +67,4 @@ namespace ICSharpCode.ILSpy.Commands MainWindow.Instance.JumpToReference(selection); } } - - [ExportContextMenuEntry(Header = nameof(Resources.GoToToken), Order = 10)] - class GoToToken : IContextMenuEntry - { - public void Execute(TextViewContext context) - { - int token = GetSelectedToken(context.DataGrid, out PEFile module).Value; - MainWindow.Instance.JumpToReference(new EntityReference("metadata", module, MetadataTokens.Handle(token))); - } - - public bool IsEnabled(TextViewContext context) - { - return true; - } - - public bool IsVisible(TextViewContext context) - { - return context.DataGrid?.Name == "MetadataView" && GetSelectedToken(context.DataGrid, out _) != null; - } - - private int? GetSelectedToken(DataGrid grid, out PEFile module) - { - module = null; - if (grid == null) - return null; - var cell = grid.CurrentCell; - if (!cell.IsValid) - return null; - Type type = cell.Item.GetType(); - var property = type.GetProperty(cell.Column.Header.ToString()); - var moduleField = type.GetField("module", BindingFlags.NonPublic | BindingFlags.Instance); - if (property == null || property.PropertyType != typeof(int) || !property.GetCustomAttributes(false).Any(a => a is StringFormatAttribute sf && sf.Format == "X8")) - return null; - module = (PEFile)moduleField.GetValue(cell.Item); - return (int)property.GetValue(cell.Item); - } - } } diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index f0e88dc0a..fbb27c280 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -118,6 +118,7 @@ + @@ -173,7 +174,6 @@ Code MSBuild:Compile - Code MSBuild:Compile @@ -209,6 +209,7 @@ + diff --git a/ILSpy/Metadata/CoffHeaderTreeNode.cs b/ILSpy/Metadata/CoffHeaderTreeNode.cs index 4a22313f4..906abc8d4 100644 --- a/ILSpy/Metadata/CoffHeaderTreeNode.cs +++ b/ILSpy/Metadata/CoffHeaderTreeNode.cs @@ -59,6 +59,7 @@ namespace ICSharpCode.ILSpy.Metadata AutoGenerateColumns = false, CanUserAddRows = false, CanUserDeleteRows = false, + GridLinesVisibility = DataGridGridLinesVisibility.None, RowDetailsTemplateSelector = new CharacteristicsDataTemplateSelector(), RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.Visible }; diff --git a/ILSpy/Metadata/DataGridCustomTextColumn.cs b/ILSpy/Metadata/DataGridCustomTextColumn.cs deleted file mode 100644 index b16dade3e..000000000 --- a/ILSpy/Metadata/DataGridCustomTextColumn.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2011 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.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Input; -using System.Windows.Media; - -namespace ICSharpCode.ILSpy.Metadata -{ - class DataGridCustomTextColumn : DataGridTextColumn - { - public BindingBase ToolTipBinding { get; set; } - - protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) - { - TextBox textBox = new TextBox() { Style = (Style)MetadataTableViews.Instance["DataGridCustomTextColumnTextBoxStyle"] }; - BindingOperations.SetBinding(textBox, TextBox.TextProperty, Binding); - if (ToolTipBinding != null) - { - textBox.MouseMove += TextBox_MouseMove; - } - textBox.GotFocus += TextBox_GotFocus; - return textBox; - } - - private void TextBox_GotFocus(object sender, RoutedEventArgs e) - { - TextBox tb = (TextBox)sender; - var cell = tb.GetParent(); - var row = cell.GetParent(); - row.IsSelected = cell.IsSelected = true; - } - - private void TextBox_MouseMove(object sender, MouseEventArgs e) - { - e.Handled = true; - var textBox = (TextBox)sender; - BindingOperations.SetBinding(textBox, TextBox.ToolTipProperty, ToolTipBinding); - textBox.MouseMove -= TextBox_MouseMove; - } - } -} diff --git a/ILSpy/Metadata/GoToTokenCommand.cs b/ILSpy/Metadata/GoToTokenCommand.cs new file mode 100644 index 000000000..5bd70926a --- /dev/null +++ b/ILSpy/Metadata/GoToTokenCommand.cs @@ -0,0 +1,101 @@ +// Copyright (c) 2011 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; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata.Ecma335; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.Metadata; +using ICSharpCode.ILSpy.Properties; + +namespace ICSharpCode.ILSpy.Commands +{ + [ExportContextMenuEntry(Header = nameof(Resources.GoToToken), Order = 10)] + class GoToTokenCommand : IContextMenuEntry + { + public void Execute(TextViewContext context) + { + int token = GetSelectedToken(context.DataGrid, out PEFile module).Value; + MainWindow.Instance.JumpToReference(new EntityReference("metadata", module, MetadataTokens.Handle(token))); + } + + public bool IsEnabled(TextViewContext context) + { + return true; + } + + public bool IsVisible(TextViewContext context) + { + return context.DataGrid?.Name == "MetadataView" && GetSelectedToken(context.DataGrid, out _) != null; + } + + private int? GetSelectedToken(DataGrid grid, out PEFile module) + { + module = null; + if (grid == null) + return null; + var cell = grid.CurrentCell; + if (!cell.IsValid) + return null; + Type type = cell.Item.GetType(); + var property = type.GetProperty(cell.Column.Header.ToString()); + var moduleField = type.GetField("module", BindingFlags.NonPublic | BindingFlags.Instance); + if (property == null || property.PropertyType != typeof(int) || !property.GetCustomAttributes(false).Any(a => a is StringFormatAttribute sf && sf.Format == "X8")) + return null; + module = (PEFile)moduleField.GetValue(cell.Item); + return (int)property.GetValue(cell.Item); + } + } + + [ExportContextMenuEntry(Header = nameof(Resources.Copy), Order = 10)] + class CopyCommand : IContextMenuEntry + { + public void Execute(TextViewContext context) + { + string content = GetSelectedCellContent(context.DataGrid, context.MousePosition); + Clipboard.SetText(content); + } + + public bool IsEnabled(TextViewContext context) + { + return true; + } + + public bool IsVisible(TextViewContext context) + { + return context.DataGrid?.Name == "MetadataView" + && GetSelectedCellContent(context.DataGrid, context.MousePosition) != null; + } + + private string GetSelectedCellContent(DataGrid grid, Point position) + { + position = grid.PointFromScreen(position); + var hit = VisualTreeHelper.HitTest(grid, position); + if (hit == null) + return null; + var cell = hit.VisualHit.GetParent(); + return (cell?.Content as TextBlock)?.Text; + } + } +} diff --git a/ILSpy/Metadata/Helpers.cs b/ILSpy/Metadata/Helpers.cs index cc60901db..00db21388 100644 --- a/ILSpy/Metadata/Helpers.cs +++ b/ILSpy/Metadata/Helpers.cs @@ -57,21 +57,15 @@ namespace ICSharpCode.ILSpy.Metadata RowHeaderWidth = 0, EnableColumnVirtualization = true, EnableRowVirtualization = true, + RowHeight = 20, IsReadOnly = true, SelectionMode = DataGridSelectionMode.Single, - SelectionUnit = DataGridSelectionUnit.CellOrRowHeader, + SelectionUnit = DataGridSelectionUnit.FullRow, SelectedTreeNode = selectedNode, - CellStyle = new Style { - Setters = { - new Setter { - Property = Control.BorderThicknessProperty, - Value = new Thickness(0) - } - } - } + VerticalContentAlignment = VerticalAlignment.Center, + CellStyle = (Style)MetadataTableViews.Instance["DataGridCellStyle"] }; ContextMenuProvider.Add(view); - ScrollViewer.SetIsDeferredScrollingEnabled(view, true); DataGridFilter.SetIsAutoFilterEnabled(view, true); DataGridFilter.SetContentFilterFactory(view, new RegexContentFilterFactory()); } @@ -88,18 +82,6 @@ namespace ICSharpCode.ILSpy.Metadata return view; } - class MetaDataGrid : DataGrid, IHaveState - { - public ILSpyTreeNode SelectedTreeNode { get; set; } - - public ViewState GetState() - { - return new ViewState { - DecompiledNodes = SelectedTreeNode == null ? null : new HashSet(new[] { SelectedTreeNode }) - }; - } - } - private static void View_AutoGeneratedColumns(object sender, EventArgs e) { ((DataGrid)sender).AutoGeneratedColumns -= View_AutoGeneratedColumns; @@ -109,24 +91,21 @@ namespace ICSharpCode.ILSpy.Metadata private static void View_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) { var binding = new Binding(e.PropertyName) { Mode = BindingMode.OneWay }; - e.Column = new DataGridCustomTextColumn() { + e.Column = new DataGridTextColumn() { Header = e.PropertyName, - Binding = binding, - ToolTipBinding = new Binding(e.PropertyName + "Tooltip") { Mode = BindingMode.OneWay } + Binding = binding }; switch (e.PropertyName) { case "RID": case "Meaning": e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["DefaultFilter"]); - ((DataGridCustomTextColumn)e.Column).ToolTipBinding = null; break; case "Token": case "Offset": case "RVA": binding.StringFormat = "X8"; e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["HexFilter"]); - ((DataGridCustomTextColumn)e.Column).ToolTipBinding = null; break; default: e.Cancel = e.PropertyName.Contains("Tooltip"); @@ -147,13 +126,15 @@ namespace ICSharpCode.ILSpy.Metadata if (descriptor.PropertyType.IsEnum) { binding.Converter = new UnderlyingEnumValueConverter(); - column.SetTemplate((ControlTemplate)MetadataTableViews.Instance[descriptor.PropertyType.Name + "Filter"]); + string key = descriptor.PropertyType.Name + "Filter"; + column.SetTemplate((ControlTemplate)MetadataTableViews.Instance[key]); } var stringFormat = descriptor.Attributes.OfType().FirstOrDefault(); if (stringFormat != null) { binding.StringFormat = stringFormat.Format; - if (!descriptor.PropertyType.IsEnum && stringFormat.Format.StartsWith("X", StringComparison.OrdinalIgnoreCase)) + if (!descriptor.PropertyType.IsEnum + && stringFormat.Format.StartsWith("X", StringComparison.OrdinalIgnoreCase)) { column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["HexFilter"]); } @@ -210,9 +191,12 @@ namespace ICSharpCode.ILSpy.Metadata return (EntityHandle)fromTypeDefOrRefTag.Invoke(null, new object[] { tag }); } - public static int ComputeCodedTokenSize(this MetadataReader metadata, int largeRowSize, TableMask mask) + public static int ComputeCodedTokenSize(this MetadataReader metadata, int largeRowSize, + TableMask mask) { - return (int)computeCodedTokenSize.Invoke(metadata, new object[] { largeRowSize, rowCounts.GetValue(metadata), (ulong)mask }); + return (int)computeCodedTokenSize.Invoke(metadata, new object[] { + largeRowSize, rowCounts.GetValue(metadata), (ulong)mask } + ); } class UnderlyingEnumValueConverter : IValueConverter diff --git a/ILSpy/Metadata/MetaDataGrid.cs b/ILSpy/Metadata/MetaDataGrid.cs new file mode 100644 index 000000000..a87b065e3 --- /dev/null +++ b/ILSpy/Metadata/MetaDataGrid.cs @@ -0,0 +1,119 @@ +// Copyright (c) 2011 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; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +using ICSharpCode.AvalonEdit.Rendering; +using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.Metadata +{ + class MetaDataGrid : DataGrid, IHaveState + { + private readonly MouseHoverLogic hoverLogic; + private ToolTip toolTip; + + public ILSpyTreeNode SelectedTreeNode { get; set; } + + public MetaDataGrid() + { + this.hoverLogic = new MouseHoverLogic(this); + this.hoverLogic.MouseHover += HoverLogic_MouseHover; + this.hoverLogic.MouseHoverStopped += HoverLogic_MouseHoverStopped; + } + + private void HoverLogic_MouseHoverStopped(object sender, System.Windows.Input.MouseEventArgs e) + { + // Non-popup tooltips get closed as soon as the mouse starts moving again + if (toolTip != null) + { + toolTip.IsOpen = false; + e.Handled = true; + } + } + + private void HoverLogic_MouseHover(object sender, System.Windows.Input.MouseEventArgs e) + { + var position = e.GetPosition(this); + var hit = VisualTreeHelper.HitTest(this, position); + if (hit == null) + { + return; + } + var cell = hit.VisualHit.GetParent(); + if (cell == null) + return; + var data = cell.DataContext; + var name = (string)cell.Column.Header; + if (toolTip == null) + { + toolTip = new ToolTip(); + toolTip.Closed += ToolTipClosed; + } + toolTip.PlacementTarget = this; // required for property inheritance + + var pi = data?.GetType().GetProperty(name + "Tooltip"); + if (pi == null) + return; + object tooltip = pi.GetValue(data); + if (tooltip is string s) + { + if (string.IsNullOrWhiteSpace(s)) + return; + toolTip.Content = new TextBlock { + Text = s, + TextWrapping = TextWrapping.Wrap + }; + } + else if (tooltip != null) + { + toolTip.Content = tooltip; + } + else + { + return; + } + + e.Handled = true; + toolTip.IsOpen = true; + } + + private void ToolTipClosed(object sender, RoutedEventArgs e) + { + if (toolTip == sender) + { + toolTip = null; + } + } + + public ViewState GetState() + { + return new ViewState { + DecompiledNodes = SelectedTreeNode == null + ? null + : new HashSet(new[] { SelectedTreeNode }) + }; + } + } +} diff --git a/ILSpy/Metadata/MetadataTableViews.xaml b/ILSpy/Metadata/MetadataTableViews.xaml index a0b52ecf0..0802dc5fe 100644 --- a/ILSpy/Metadata/MetadataTableViews.xaml +++ b/ILSpy/Metadata/MetadataTableViews.xaml @@ -8,30 +8,31 @@ xmlns:srm="clr-namespace:System.Reflection;assembly=System.Reflection.Metadata" xmlns:dgx="urn:tom-englert.de/DataGridExtensions"> - - + diff --git a/ILSpy/Metadata/OptionalHeaderTreeNode.cs b/ILSpy/Metadata/OptionalHeaderTreeNode.cs index 6aad4c1c1..423924dbb 100644 --- a/ILSpy/Metadata/OptionalHeaderTreeNode.cs +++ b/ILSpy/Metadata/OptionalHeaderTreeNode.cs @@ -51,11 +51,11 @@ namespace ICSharpCode.ILSpy.Metadata dataGrid.RowDetailsTemplateSelector = new DllCharacteristicsDataTemplateSelector(); dataGrid.RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.Visible; dataGrid.AutoGenerateColumns = false; - dataGrid.Columns.Add(new DataGridCustomTextColumn { Header = "Member", Binding = new Binding("Member") { Mode = BindingMode.OneWay } }); - dataGrid.Columns.Add(new DataGridCustomTextColumn { Header = "Offset", Binding = new Binding("Offset") { StringFormat = "X8", Mode = BindingMode.OneWay } }); - dataGrid.Columns.Add(new DataGridCustomTextColumn { Header = "Size", Binding = new Binding("Size") { Mode = BindingMode.OneWay } }); - dataGrid.Columns.Add(new DataGridCustomTextColumn { Header = "Value", Binding = new Binding(".") { Converter = ByteWidthConverter.Instance, Mode = BindingMode.OneWay } }); - dataGrid.Columns.Add(new DataGridCustomTextColumn { Header = "Meaning", Binding = new Binding("Meaning") { Mode = BindingMode.OneWay } }); + dataGrid.Columns.Add(new DataGridTextColumn { Header = "Member", Binding = new Binding("Member") { Mode = BindingMode.OneWay } }); + dataGrid.Columns.Add(new DataGridTextColumn { Header = "Offset", Binding = new Binding("Offset") { StringFormat = "X8", Mode = BindingMode.OneWay } }); + dataGrid.Columns.Add(new DataGridTextColumn { Header = "Size", Binding = new Binding("Size") { Mode = BindingMode.OneWay } }); + dataGrid.Columns.Add(new DataGridTextColumn { Header = "Value", Binding = new Binding(".") { Converter = ByteWidthConverter.Instance, Mode = BindingMode.OneWay } }); + dataGrid.Columns.Add(new DataGridTextColumn { Header = "Meaning", Binding = new Binding("Meaning") { Mode = BindingMode.OneWay } }); var headers = module.Reader.PEHeaders; var reader = module.Reader.GetEntireImage().GetReader(headers.PEHeaderStartOffset, 128);