diff --git a/ILSpy.Tests/Views/OpenFromGacDialogStructureTests.cs b/ILSpy.Tests/Views/OpenFromGacDialogStructureTests.cs
new file mode 100644
index 000000000..b640b034c
--- /dev/null
+++ b/ILSpy.Tests/Views/OpenFromGacDialogStructureTests.cs
@@ -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;
+
+///
+/// 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
+/// ; 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.
+///
+[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().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"));
+ }
+}
diff --git a/ILSpy/Views/GacEntry.cs b/ILSpy/Views/GacEntry.cs
new file mode 100644
index 000000000..9b49fdbc8
--- /dev/null
+++ b/ILSpy/Views/GacEntry.cs
@@ -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
+{
+ ///
+ /// One row in the GAC browser. Wraps an + 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. + back
+ /// the filter, mirroring the WPF dialog.
+ ///
+ 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;
+ }
+}
diff --git a/ILSpy/Views/OpenFromGacDialog.axaml b/ILSpy/Views/OpenFromGacDialog.axaml
index 0687c75d9..0a7d8c045 100644
--- a/ILSpy/Views/OpenFromGacDialog.axaml
+++ b/ILSpy/Views/OpenFromGacDialog.axaml
@@ -1,25 +1,46 @@
-
+ CanResize="True">
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ILSpy/Views/OpenFromGacDialog.axaml.cs b/ILSpy/Views/OpenFromGacDialog.axaml.cs
index 9279f01cd..abd49a085 100644
--- a/ILSpy/Views/OpenFromGacDialog.axaml.cs
+++ b/ILSpy/Views/OpenFromGacDialog.axaml.cs
@@ -20,6 +20,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
@@ -28,55 +29,124 @@ using Avalonia.Threading;
using ICSharpCode.Decompiler.Metadata;
+// Alias the WPF-shared Resources class — Window inherits an IResourceDictionary Resources
+// property that would otherwise shadow ICSharpCode.ILSpy.Properties.Resources, turning every
+// `Resources.X` into an IResourceDictionary indexer lookup that doesn't compile.
+using Loc = ICSharpCode.ILSpy.Properties.Resources;
+
namespace ILSpy.Views
{
///
/// "Open from GAC" dialog: enumerates the Global Assembly Cache via the shared
/// +
- /// pair, surfaces the assemblies in a filterable list, returns the selected
- /// absolute file paths. Windows-only — the GAC doesn't exist on Linux/macOS, and
- /// the command's CanExecute gates on OperatingSystem.IsWindows().
+ /// pair and surfaces each entry in a five-column DataGrid (Reference Name / Version /
+ /// Culture / Public Key Token / Location) — each header click-to-sort, initial sort
+ /// by name ascending. Multi-select; returns the selected absolute file paths via
+ /// . Windows-only — the GAC doesn't exist on Linux/macOS,
+ /// and the command's CanExecute gates on .
///
public partial class OpenFromGacDialog : Window
{
readonly ObservableCollection allEntries = new();
readonly ObservableCollection filteredEntries = new();
- readonly System.Threading.CancellationTokenSource fetchCts = new();
+ readonly CancellationTokenSource fetchCts = new();
- ListBox entriesList = null!;
+ DataGrid entriesGrid = null!;
TextBox filterBox = null!;
+ Label filterLabel = null!;
ProgressBar loadingBar = null!;
Button okButton = null!;
+ Button cancelButton = null!;
+ DataGridColumn nameColumn = null!;
public OpenFromGacDialog()
{
InitializeComponent();
- entriesList = this.FindControl("EntriesList")!;
+ entriesGrid = this.FindControl("EntriesGrid")!;
filterBox = this.FindControl("FilterBox")!;
+ filterLabel = this.FindControl