mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
WPF's Open-from-GAC dialog uses a ListView with five SortableGridViewColumns (Reference Name / Version / Culture / Public Key Token / Location), each click-to-sort, with the initial sort by name ascending. The Avalonia port had reduced this to a one-column ListBox showing the raw ToString() — all field-level information mashed into a single TextBlock, no per-field sort, no Location column, no localised strings. Assisted-by: Claude:claude-opus-4-7:Claude Codepull/3755/head
4 changed files with 282 additions and 48 deletions
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2026 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.Linq; |
||||
|
||||
using Avalonia.Controls; |
||||
using Avalonia.Headless.NUnit; |
||||
using Avalonia.VisualTree; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ICSharpCode.ILSpy.Properties; |
||||
|
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests.Views; |
||||
|
||||
/// <summary>
|
||||
/// Pins the GAC browser's grid shape against the WPF dialog. WPF uses a ListView with a
|
||||
/// GridView of five SortableGridViewColumns — Reference Name, Version, Culture,
|
||||
/// Public Key Token, Location — each click-to-sort. The Avalonia port started with a
|
||||
/// single-column ListBox that mashed every field into one TextBlock via
|
||||
/// <see cref="object.ToString"/>; this fixture asserts the rebuilt dialog matches the
|
||||
/// WPF column set so a future "let's go back to a simple list" doesn't quietly remove
|
||||
/// the per-field sort and column widths users came to rely on.
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class OpenFromGacDialogStructureTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public void Dialog_Window_Title_Comes_From_Localised_Resources() |
||||
{ |
||||
var dialog = new OpenFromGacDialog(); |
||||
dialog.Title.Should().Be(Resources.OpenFrom, |
||||
"the dialog title must match WPF's localised Resources.OpenFrom (currently 'Open From GAC')"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public void Dialog_Uses_A_DataGrid_With_Five_Sortable_Columns_Matching_WPF() |
||||
{ |
||||
var dialog = new OpenFromGacDialog(); |
||||
// Force layout so the DataGrid's internal column templates are realised.
|
||||
dialog.Measure(global::Avalonia.Size.Infinity); |
||||
dialog.Arrange(new global::Avalonia.Rect(dialog.DesiredSize)); |
||||
|
||||
var grid = dialog.GetVisualDescendants().OfType<DataGrid>().FirstOrDefault(); |
||||
grid.Should().NotBeNull( |
||||
"the GAC browser must use a DataGrid (not a ListBox) so each field gets its own " |
||||
+ "sortable column — that's the whole reason WPF picked ListView+GridView"); |
||||
|
||||
var headers = grid!.Columns.Select(c => c.Header as string).ToList(); |
||||
headers.Should().Contain(new[] { |
||||
Resources.ReferenceName, |
||||
Resources.Version, |
||||
Resources.CultureLabel, |
||||
Resources.PublicToken, |
||||
Resources.Location, |
||||
}, "the five WPF column headers must all be present and localised"); |
||||
|
||||
grid.Columns.Should().HaveCount(5, |
||||
"exactly five columns — no extras, no missing fields"); |
||||
grid.Columns.Should().AllSatisfy(c => c.CanUserSort.Should().BeTrue( |
||||
"every column must be sortable to match WPF's SortableGridViewColumn.SortMode=Automatic")); |
||||
} |
||||
} |
||||
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2026 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.Text; |
||||
|
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
namespace ILSpy.Views |
||||
{ |
||||
/// <summary>
|
||||
/// One row in the GAC browser. Wraps an <see cref="AssemblyNameReference"/> + the
|
||||
/// resolved on-disk path, exposing each WPF column's bound property (ShortName,
|
||||
/// Version, Culture, PublicKeyToken, FileName) so the DataGrid can sort by any of
|
||||
/// them independently. <see cref="FullName"/> + <see cref="FormattedVersion"/> back
|
||||
/// the filter, mirroring the WPF dialog.
|
||||
/// </summary>
|
||||
public sealed class GacEntry |
||||
{ |
||||
readonly AssemblyNameReference reference; |
||||
string? formattedVersion; |
||||
string? publicKeyToken; |
||||
|
||||
public GacEntry(AssemblyNameReference reference, string fileName) |
||||
{ |
||||
this.reference = reference; |
||||
FileName = fileName; |
||||
} |
||||
|
||||
public string FileName { get; } |
||||
|
||||
public string FullName => reference.FullName; |
||||
|
||||
public string ShortName => reference.Name; |
||||
|
||||
public System.Version? Version => reference.Version; |
||||
|
||||
public string FormattedVersion => formattedVersion ??= (Version?.ToString() ?? string.Empty); |
||||
|
||||
public string Culture |
||||
=> string.IsNullOrEmpty(reference.Culture) ? "neutral" : reference.Culture!; |
||||
|
||||
public string PublicKeyToken => publicKeyToken ??= FormatPublicKeyToken(reference.PublicKeyToken); |
||||
|
||||
static string FormatPublicKeyToken(byte[]? token) |
||||
{ |
||||
if (token == null || token.Length == 0) |
||||
return "null"; |
||||
var sb = new StringBuilder(token.Length * 2); |
||||
foreach (var b in token) |
||||
sb.AppendFormat("{0:x2}", b); |
||||
return sb.ToString(); |
||||
} |
||||
|
||||
public override string ToString() => FullName; |
||||
} |
||||
} |
||||
@ -1,25 +1,46 @@
@@ -1,25 +1,46 @@
|
||||
<Window xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
x:Class="ILSpy.Views.OpenFromGacDialog" |
||||
Width="600" Height="450" MinWidth="500" MinHeight="300" |
||||
Width="750" Height="350" MinWidth="200" MinHeight="150" |
||||
WindowStartupLocation="CenterOwner" |
||||
Title="Open from GAC"> |
||||
<Grid Margin="12,8" RowDefinitions="Auto,*,Auto,Auto" RowSpacing="6"> |
||||
CanResize="True"> |
||||
<Grid Margin="12,8" RowDefinitions="Auto,*,Auto" RowSpacing="6"> |
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*"> |
||||
<TextBlock Grid.Column="0" Text="Filter:" VerticalAlignment="Center" Margin="0,0,8,0" /> |
||||
<Label Grid.Column="0" Name="FilterLabel" Target="{Binding #FilterBox}" |
||||
VerticalAlignment="Center" Margin="0,0,8,0" /> |
||||
<TextBox Grid.Column="1" Name="FilterBox" /> |
||||
</Grid> |
||||
<ListBox Grid.Row="1" Name="EntriesList" SelectionMode="Multiple"> |
||||
<ListBox.ItemTemplate> |
||||
<DataTemplate> |
||||
<TextBlock Text="{Binding}" /> |
||||
</DataTemplate> |
||||
</ListBox.ItemTemplate> |
||||
</ListBox> |
||||
<ProgressBar Grid.Row="2" Name="LoadingBar" Height="6" IsIndeterminate="True" /> |
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="6"> |
||||
<Button Name="OkButton" Content="Open" IsDefault="True" IsEnabled="False" MinWidth="72" /> |
||||
<Button Name="CancelButton" Content="Cancel" IsCancel="True" MinWidth="72" /> |
||||
<DataGrid Grid.Row="1" Name="EntriesGrid" |
||||
AutoGenerateColumns="False" |
||||
CanUserResizeColumns="True" |
||||
CanUserSortColumns="True" |
||||
GridLinesVisibility="None" |
||||
HeadersVisibility="Column" |
||||
IsReadOnly="True" |
||||
SelectionMode="Extended"> |
||||
<DataGrid.Columns> |
||||
<DataGridTextColumn Width="300" CanUserSort="True" |
||||
x:CompileBindings="False" |
||||
Binding="{Binding ShortName}" /> |
||||
<DataGridTextColumn Width="75" CanUserSort="True" |
||||
x:CompileBindings="False" |
||||
Binding="{Binding FormattedVersion}" /> |
||||
<DataGridTextColumn Width="65" CanUserSort="True" |
||||
x:CompileBindings="False" |
||||
Binding="{Binding Culture}" /> |
||||
<DataGridTextColumn Width="115" CanUserSort="True" |
||||
x:CompileBindings="False" |
||||
Binding="{Binding PublicKeyToken}" /> |
||||
<DataGridTextColumn Width="*" CanUserSort="True" |
||||
x:CompileBindings="False" |
||||
Binding="{Binding FileName}" /> |
||||
</DataGrid.Columns> |
||||
</DataGrid> |
||||
<ProgressBar Grid.Row="1" Name="LoadingBar" Height="6" |
||||
VerticalAlignment="Bottom" Margin="0,0,0,0" /> |
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="6"> |
||||
<Button Name="OkButton" IsDefault="True" IsEnabled="False" MinWidth="72" /> |
||||
<Button Name="CancelButton" IsCancel="True" MinWidth="72" /> |
||||
</StackPanel> |
||||
</Grid> |
||||
</Window> |
||||
|
||||
Loading…
Reference in new issue