Browse Source

Replace the process dialog's grids with ListBoxes

ProDataGrid derives its scroll extent from the current scroll offset, so an
offset that is briefly too large inflates the extent, which permits a larger
offset again. A trackpad reaches that state within a few hundred sub-row
events: instrumented in the running app, a ~1500px list reported 8735px and
kept growing, the thumb collapsed to its minimum, and the end of the list ran
away from the user. A second defect slid the rows sideways by up to 10px - the
star-sized column is measured against the width including the vertical scroll
bar, leaving the grid convinced it has a scroll bar's worth of content to
reach.

Disabling the horizontal scroll bar, giving each grid its own
DefaultRowHeightEstimator, pinning RowHeight, forcing the vertical scroll bar
visible, and disabling scrolling on the template's inner ScrollViewer each
removed a symptom at most; none can break a loop that runs through the scroll
offset, and 12.0.4 is the newest release. A ListBox's virtualizing panel keeps
the extent a function of the items alone. The price is laying out the columns
here - so the header row and the item template have to be kept in step - and
losing sortable, resizable headers.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
pull/3943/head
Christoph Wille 2 months ago
parent
commit
16cd684a09
  1. 156
      ILSpy.Tests/Views/OpenFromProcessDialogScrollingTests.cs
  2. 33
      ILSpy.Tests/Views/OpenFromProcessDialogStructureTests.cs
  3. 125
      ILSpy/Views/OpenFromProcessDialog.axaml
  4. 31
      ILSpy/Views/OpenFromProcessDialog.axaml.cs

156
ILSpy.Tests/Views/OpenFromProcessDialogScrollingTests.cs

@ -0,0 +1,156 @@ @@ -0,0 +1,156 @@
// Copyright (c) 2026 Christoph Wille
//
// 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Headless;
using Avalonia.Headless.NUnit;
using Avalonia.Threading;
using Avalonia.VisualTree;
using AwesomeAssertions;
using ICSharpCode.ILSpy.Tests.Processes;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpy.Views;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Views;
/// <summary>
/// Scrolling the process list with a trackpad must be boring: the list may not resize
/// itself, drift sideways, or move its own end around. A DataGrid could not manage that -
/// its scroll extent was derived from the current scroll offset, so an offset that was
/// briefly too large inflated the extent, which permitted a larger offset still, and the
/// end of the list ran away from the user (measured in the running app: a 1500px list
/// reporting 8735px and still growing). Hence the plain ListBox, whose virtualizing panel
/// keeps the extent a function of the items alone. These tests pin the properties that made
/// the grid unusable, so a future switch back has to answer for them.
/// </summary>
[TestFixture]
public class OpenFromProcessDialogScrollingTests
{
const int RowCount = 60;
static OpenFromProcessDialog CreateDialogWithManyProcesses(int count = RowCount)
{
var explorer = new FakeProcessExplorer();
for (int i = 0; i < count; i++)
{
explorer.ProcessesToReturn.Add(FakeProcessExplorer.Process(
1000 + i, "dotnet", i % 7 == 3
? $"Avalonia.BuildServices.486cb255276d45e9bb0ff8b0{i:D4}"
: "MSBuild"));
}
return new OpenFromProcessDialog(explorer);
}
// The deltas a macOS trackpad actually sends: many tiny fractional events, each with a
// small sideways component next to the vertical one. Nothing here is a whole row, which
// is the point - whole-row steps hid every defect these tests cover.
static readonly double[] StepsY = { 0.02, 0.04, 0.06, 0.04, 0.02, 0.08, 0.12, 0.1, 0.06, 0.04 };
static readonly double[] StepsX = { 0, 0, 0.02, 0, -0.02, 0, 0.04, 0, 0, -0.02 };
[AvaloniaTest]
public async Task Trackpad_Scrolling_The_Process_List_Keeps_The_List_Steady()
{
var dialog = CreateDialogWithManyProcesses();
dialog.Show();
var vm = (OpenFromProcessDialogViewModel)dialog.DataContext!;
await Waiters.WaitForAsync(() => vm.Processes.Count == RowCount);
var list = dialog.FindControl<ListBox>("ProcessesList")!;
var item = await list.WaitForComponent<ListBoxItem>();
Dispatcher.UIThread.RunJobs();
var scroller = list.GetVisualDescendants().OfType<ScrollViewer>().First();
var horizontalScrollBar = list.GetVisualDescendants().OfType<ScrollBar>()
.Single(s => s.Name == "PART_HorizontalScrollBar");
double rowHeight = item.Bounds.Height;
var contentHeights = new HashSet<double>();
var viewportHeights = new HashSet<double>();
var horizontalOffsets = new HashSet<double>();
int horizontalScrollBarShown = 0;
double maxOffsetY = 0;
var point = list.Bounds.Center;
// Far more events than the list is long, so it spends most of them pushed against
// the end - where an extent that follows the offset would run away.
for (int i = 0; i < 900; i++)
{
dialog.MouseWheel(point, new Vector(StepsX[i % StepsX.Length], -StepsY[i % StepsY.Length]));
Dispatcher.UIThread.RunJobs();
if (horizontalScrollBar.IsVisible)
horizontalScrollBarShown++;
contentHeights.Add(scroller.Extent.Height);
viewportHeights.Add(scroller.Viewport.Height);
horizontalOffsets.Add(scroller.Offset.X);
maxOffsetY = System.Math.Max(maxOffsetY, scroller.Offset.Y);
}
horizontalScrollBarShown.Should().Be(0,
"the columns are laid out to fit the viewport, so there is nothing to reach sideways");
horizontalOffsets.Should().Equal(new[] { 0.0 },
"a vertical scroll must not shift the rows sideways, however much the fingers drift");
viewportHeights.Should().HaveCount(1,
"a scroll bar appearing and disappearing resizes the rows area under the pointer");
contentHeights.Should().HaveCount(1,
"the scrollable range must not move while the user is scrolling through it");
contentHeights.Single().Should().BeApproximately(RowCount * rowHeight, 1.0,
"the list is exactly as tall as its rows - anything larger lets the view scroll "
+ "past the last row and snap back");
maxOffsetY.Should().BeApproximately(RowCount * rowHeight - scroller.Viewport.Height, 1.0,
"scrolling to the end must land on the last row and stop there");
}
[AvaloniaTest]
public async Task The_Last_Process_Is_Reachable_And_Fully_Visible()
{
var dialog = CreateDialogWithManyProcesses();
dialog.Show();
var vm = (OpenFromProcessDialogViewModel)dialog.DataContext!;
await Waiters.WaitForAsync(() => vm.Processes.Count == RowCount);
var list = dialog.FindControl<ListBox>("ProcessesList")!;
await list.WaitForComponent<ListBoxItem>();
Dispatcher.UIThread.RunJobs();
var point = list.Bounds.Center;
for (int i = 0; i < 900; i++)
{
dialog.MouseWheel(point, new Vector(StepsX[i % StepsX.Length], -StepsY[i % StepsY.Length]));
Dispatcher.UIThread.RunJobs();
}
var scroller = list.GetVisualDescendants().OfType<ScrollViewer>().First();
var lastRow = list.GetRealizedContainers().OfType<ListBoxItem>()
.SingleOrDefault(c => ReferenceEquals(c.DataContext, vm.Processes[^1]));
lastRow.Should().NotBeNull("the last process must be reachable by scrolling");
var bottom = lastRow!.TranslatePoint(new Point(0, lastRow.Bounds.Height), scroller)!.Value.Y;
bottom.Should().BeLessThanOrEqualTo(scroller.Viewport.Height + 1,
"the last row must come to rest inside the viewport, not half under its edge");
}
}

33
ILSpy.Tests/Views/OpenFromProcessDialogStructureTests.cs

@ -35,9 +35,9 @@ using NUnit.Framework; @@ -35,9 +35,9 @@ using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Views;
/// <summary>
/// Pins the process-explorer dialog's shape: a filter box, a process grid above an
/// assembly grid, and the buttons that drive it. Everything is reachable by button - the
/// dialog carries no context menu - and the assembly grid allows multi-select so several
/// Pins the process-explorer dialog's shape: a filter box, a process list above an
/// assembly list, and the buttons that drive it. Everything is reachable by button - the
/// dialog carries no context menu - and the assembly list allows multi-select so several
/// assemblies can be added in one go.
/// </summary>
[TestFixture]
@ -67,8 +67,8 @@ public class OpenFromProcessDialogStructureTests @@ -67,8 +67,8 @@ public class OpenFromProcessDialogStructureTests
var dialog = CreateDialog();
dialog.FindControl<TextBox>("FilterBox").Should().NotBeNull("long process lists need filtering");
dialog.FindControl<DataGrid>("ProcessesGrid").Should().NotBeNull();
dialog.FindControl<DataGrid>("ModulesGrid").Should().NotBeNull("the selected process's assemblies are listed");
dialog.FindControl<ListBox>("ProcessesList").Should().NotBeNull();
dialog.FindControl<ListBox>("ModulesList").Should().NotBeNull("the selected process's assemblies are listed");
dialog.FindControl<TextBlock>("VisibilityHint").Should().NotBeNull(
"the dialog states which processes it cannot show");
@ -80,11 +80,12 @@ public class OpenFromProcessDialogStructureTests @@ -80,11 +80,12 @@ public class OpenFromProcessDialogStructureTests
[AvaloniaTest]
public void Several_Assemblies_Can_Be_Selected_At_Once()
{
var grid = CreateDialog().FindControl<DataGrid>("ModulesGrid")!;
var dialog = CreateDialog();
grid.SelectionMode.Should().Be(DataGridSelectionMode.Extended,
dialog.FindControl<ListBox>("ModulesList")!.SelectionMode.Should().Be(SelectionMode.Multiple,
"adding several assemblies of one process in one go is the common case");
grid.IsReadOnly.Should().BeTrue("the grid lists assemblies, it does not edit them");
dialog.FindControl<ListBox>("ProcessesList")!.SelectionMode.Should().Be(SelectionMode.Single,
"one process at a time drives the assembly list below");
}
[AvaloniaTest]
@ -99,7 +100,7 @@ public class OpenFromProcessDialogStructureTests @@ -99,7 +100,7 @@ public class OpenFromProcessDialogStructureTests
await Waiters.WaitForAsync(() => explorer.ProcessCalls > 0);
var vm = (OpenFromProcessDialogViewModel)dialog.DataContext!;
await Waiters.WaitForAsync(() => vm.Processes.Count == 1);
dialog.FindControl<DataGrid>("ProcessesGrid")!.ItemsSource.Should().BeSameAs(vm.Processes);
dialog.FindControl<ListBox>("ProcessesList")!.ItemsSource.Should().BeSameAs(vm.Processes);
}
[AvaloniaTest]
@ -119,19 +120,19 @@ public class OpenFromProcessDialogStructureTests @@ -119,19 +120,19 @@ public class OpenFromProcessDialogStructureTests
vm.SelectedProcess = vm.Processes[0];
await Waiters.WaitForAsync(() => vm.Modules.Count == 2);
var grid = dialog.FindControl<DataGrid>("ModulesGrid")!;
grid.SelectedItems.Add(vm.Modules[0]);
var list = dialog.FindControl<ListBox>("ModulesList")!;
list.SelectedItems!.Add(vm.Modules[0]);
await Waiters.WaitForAsync(() => vm.SelectedModules.Count == 1);
vm.SelectedModules.Single().Name.Should().Be("A.dll",
"the grid's selection is what the Add button acts on");
"the list's selection is what the Add button acts on");
}
[AvaloniaTest]
public async Task Filtering_Keeps_A_Selected_Process_That_Still_Matches()
{
// Only reproducible with the grid attached: the view model alone never sees the
// selection being written back, because it is the grid that pushes it.
// Only reproducible with the list attached: the view model alone never sees the
// selection being written back, because it is the list that pushes it.
var explorer = new FakeProcessExplorer();
explorer.ProcessesToReturn.Add(FakeProcessExplorer.Process(100, "ILSpy", "ILSpy"));
explorer.ProcessesToReturn.Add(FakeProcessExplorer.Process(200, "dotnet", "MyTool"));
@ -142,7 +143,7 @@ public class OpenFromProcessDialogStructureTests @@ -142,7 +143,7 @@ public class OpenFromProcessDialogStructureTests
dialog.Show();
var vm = (OpenFromProcessDialogViewModel)dialog.DataContext!;
await Waiters.WaitForAsync(() => vm.Processes.Count == 2);
dialog.FindControl<DataGrid>("ProcessesGrid")!.SelectedItem = vm.Processes[0];
dialog.FindControl<ListBox>("ProcessesList")!.SelectedItem = vm.Processes[0];
await Waiters.WaitForAsync(() => vm.Modules.Count == 1);
// One keystroke that narrows the list without excluding the selected row.
@ -166,7 +167,7 @@ public class OpenFromProcessDialogStructureTests @@ -166,7 +167,7 @@ public class OpenFromProcessDialogStructureTests
dialog.Show();
var vm = (OpenFromProcessDialogViewModel)dialog.DataContext!;
await Waiters.WaitForAsync(() => vm.Processes.Count == 2);
dialog.FindControl<DataGrid>("ProcessesGrid")!.SelectedItem = vm.Processes[0];
dialog.FindControl<ListBox>("ProcessesList")!.SelectedItem = vm.Processes[0];
await Waiters.WaitForAsync(() => vm.Modules.Count == 1);
vm.FilterText = "dotnet";

125
ILSpy/Views/OpenFromProcessDialog.axaml

@ -6,6 +6,28 @@ @@ -6,6 +6,28 @@
Width="850" Height="560" MinWidth="500" MinHeight="360"
WindowStartupLocation="CenterOwner"
CanResize="True">
<!-- Both lists are ListBoxes rather than DataGrids. A DataGrid derives its scroll extent
from the current scroll offset, so an offset that is briefly too large inflates the
extent, which permits a larger offset again: with a trackpad, whose events are
fractions of a row, the end of the list runs away from the user and the rows jump.
A ListBox's virtualizing panel keeps the extent a function of the items alone.
The cost is that the columns are laid out here instead of by the control, so the
header row below repeats the item templates' column widths - keep them in step. Only
the last column is star-sized, so the vertical scroll bar's width comes off that
column's right edge, where a left-aligned string shows no seam. -->
<Window.Styles>
<!-- Rows sit flush under the header: the item padding here is the header's Margin. -->
<Style Selector="ListBox.columns > ListBoxItem">
<Setter Property="Padding" Value="4,2" />
</Style>
<!-- A cell's text is shorter than the row height the MinHeight below reserves. Left to
stretch, it renders against the top of the row and leaves all of the slack under
itself, which the selection highlight makes obvious. -->
<Style Selector="ListBox.columns TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Window.Styles>
<Grid Margin="12,8" RowDefinitions="Auto,2*,Auto,Auto,3*,Auto,Auto,Auto" RowSpacing="6">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto">
<Label Grid.Column="0" Name="FilterLabel" Target="{Binding #FilterBox}"
@ -15,29 +37,42 @@ @@ -15,29 +37,42 @@
Command="{Binding RefreshCommand}" />
</Grid>
<DataGrid Grid.Row="1" Name="ProcessesGrid"
ItemsSource="{Binding Processes}"
SelectedItem="{Binding SelectedProcess}"
AutoGenerateColumns="False"
CanUserResizeColumns="True"
CanUserSortColumns="True"
GridLinesVisibility="None"
HeadersVisibility="Column"
IsReadOnly="True"
SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn Width="200" CanUserSort="True" x:CompileBindings="False"
Binding="{Binding ProcessName}" />
<DataGridTextColumn Width="70" CanUserSort="True" x:CompileBindings="False"
Binding="{Binding Pid}" />
<DataGridTextColumn Width="150" CanUserSort="True" x:CompileBindings="False"
Binding="{Binding Runtime}" />
<DataGridTextColumn Width="90" CanUserSort="True" x:CompileBindings="False"
Binding="{Binding Architecture}" />
<DataGridTextColumn Width="*" CanUserSort="True" x:CompileBindings="False"
Binding="{Binding EntryAssembly}" />
</DataGrid.Columns>
</DataGrid>
<Grid Grid.Row="1" RowDefinitions="Auto,*">
<Border Grid.Row="0" Background="{DynamicResource ILSpy.WindowBackground}"
BorderBrush="{DynamicResource ILSpy.InputBorder}" BorderThickness="1">
<Grid ColumnDefinitions="200,70,150,90,*" Margin="4,2">
<TextBlock Grid.Column="0" Name="ProcessColumnHeader" />
<TextBlock Grid.Column="1" Name="PidColumnHeader" />
<TextBlock Grid.Column="2" Name="RuntimeColumnHeader" />
<TextBlock Grid.Column="3" Name="ArchitectureColumnHeader" />
<TextBlock Grid.Column="4" Name="EntryAssemblyColumnHeader" />
</Grid>
</Border>
<ListBox Grid.Row="1" Name="ProcessesList" Classes="columns"
ItemsSource="{Binding Processes}"
SelectedItem="{Binding SelectedProcess, Mode=TwoWay}"
SelectionMode="Single"
Background="{DynamicResource ILSpy.PaneBackground}"
BorderBrush="{DynamicResource ILSpy.InputBorder}"
BorderThickness="1,0,1,1"
Padding="0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ProcessRowViewModel">
<!-- Trimming keeps a long value inside its column: a Grid does not clip
its children, so untrimmed text would paint over the next column.
MinHeight holds the row height the dialog had as a grid. -->
<Grid ColumnDefinitions="200,70,150,90,*" MinHeight="20">
<TextBlock Grid.Column="0" Text="{Binding ProcessName}" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="1" Text="{Binding Pid}" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="2" Text="{Binding Runtime}" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="3" Text="{Binding Architecture}" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="4" Text="{Binding EntryAssembly}" TextTrimming="CharacterEllipsis" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<ProgressBar Grid.Row="1" Name="ProcessesLoadingBar" Height="6"
VerticalAlignment="Bottom" IsIndeterminate="True"
@ -45,24 +80,32 @@ @@ -45,24 +80,32 @@
<TextBlock Grid.Row="2" Name="ModulesHeader" Margin="0,4,0,0" />
<Grid Grid.Row="4" RowDefinitions="*,Auto">
<DataGrid Grid.Row="0" Name="ModulesGrid"
ItemsSource="{Binding Modules}"
AutoGenerateColumns="False"
CanUserResizeColumns="True"
CanUserSortColumns="True"
GridLinesVisibility="None"
HeadersVisibility="Column"
IsReadOnly="True"
SelectionMode="Extended">
<DataGrid.Columns>
<DataGridTextColumn Width="260" CanUserSort="True" x:CompileBindings="False"
Binding="{Binding Name}" />
<DataGridTextColumn Width="*" CanUserSort="True" x:CompileBindings="False"
Binding="{Binding Location}" />
</DataGrid.Columns>
</DataGrid>
<ProgressBar Grid.Row="0" Name="ModulesLoadingBar" Height="6"
<Grid Grid.Row="4" RowDefinitions="Auto,*">
<Border Grid.Row="0" Background="{DynamicResource ILSpy.WindowBackground}"
BorderBrush="{DynamicResource ILSpy.InputBorder}" BorderThickness="1">
<Grid ColumnDefinitions="260,*" Margin="4,2">
<TextBlock Grid.Column="0" Name="AssemblyColumnHeader" />
<TextBlock Grid.Column="1" Name="LocationColumnHeader" />
</Grid>
</Border>
<ListBox Grid.Row="1" Name="ModulesList" Classes="columns"
ItemsSource="{Binding Modules}"
SelectionMode="Multiple"
Background="{DynamicResource ILSpy.PaneBackground}"
BorderBrush="{DynamicResource ILSpy.InputBorder}"
BorderThickness="1,0,1,1"
Padding="0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ProcessModuleRowViewModel">
<Grid ColumnDefinitions="260,*" MinHeight="20">
<TextBlock Grid.Column="0" Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="1" Text="{Binding Location}" TextTrimming="CharacterEllipsis" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ProgressBar Grid.Row="1" Name="ModulesLoadingBar" Height="6"
VerticalAlignment="Bottom" IsIndeterminate="True"
IsVisible="{Binding IsLoadingModules}" />
</Grid>

31
ILSpy/Views/OpenFromProcessDialog.axaml.cs

@ -33,7 +33,7 @@ namespace ICSharpCode.ILSpy.Views @@ -33,7 +33,7 @@ namespace ICSharpCode.ILSpy.Views
/// <summary>
/// "Open from Running Process" dialog: lists the running .NET processes, shows the
/// assemblies loaded in the selected one, and closes with the paths to open - either the
/// assemblies picked in the grid or the process's entry assembly, which for a modern app
/// assemblies picked in the list or the process's entry assembly, which for a modern app
/// is the dll behind its native host. Closes with null when cancelled; all behavior lives
/// in <see cref="OpenFromProcessDialogViewModel"/>.
/// </summary>
@ -67,12 +67,13 @@ namespace ICSharpCode.ILSpy.Views @@ -67,12 +67,13 @@ namespace ICSharpCode.ILSpy.Views
SetColumnHeaders();
// A DataGrid's multi-selection is not bindable, so the grid pushes it into the
// view model, which is what the Add button's command acts on.
var modulesGrid = this.FindControl<DataGrid>("ModulesGrid")!;
modulesGrid.SelectionChanged += (_, _) => {
// A multi-selection is not bindable, so the list pushes it into the view model,
// which is what the Add button's command acts on.
var modulesList = this.FindControl<ListBox>("ModulesList")!;
modulesList.SelectionChanged += (_, _) => {
viewModel.SelectedModules.Clear();
foreach (var module in modulesGrid.SelectedItems.OfType<ProcessModuleRowViewModel>())
foreach (var module in modulesList.SelectedItems?.OfType<ProcessModuleRowViewModel>()
?? Enumerable.Empty<ProcessModuleRowViewModel>())
viewModel.SelectedModules.Add(module);
};
@ -88,16 +89,14 @@ namespace ICSharpCode.ILSpy.Views @@ -88,16 +89,14 @@ namespace ICSharpCode.ILSpy.Views
void SetColumnHeaders()
{
var processes = this.FindControl<DataGrid>("ProcessesGrid")!;
processes.Columns[0].Header = Loc.OpenFromProcess_Process;
processes.Columns[1].Header = Loc.OpenFromProcess_Pid;
processes.Columns[2].Header = Loc.OpenFromProcess_Runtime;
processes.Columns[3].Header = Loc.OpenFromProcess_Architecture;
processes.Columns[4].Header = Loc.OpenFromProcess_EntryAssembly;
var modules = this.FindControl<DataGrid>("ModulesGrid")!;
modules.Columns[0].Header = Loc.Assembly;
modules.Columns[1].Header = Loc.OpenFromProcess_Location;
this.FindControl<TextBlock>("ProcessColumnHeader")!.Text = Loc.OpenFromProcess_Process;
this.FindControl<TextBlock>("PidColumnHeader")!.Text = Loc.OpenFromProcess_Pid;
this.FindControl<TextBlock>("RuntimeColumnHeader")!.Text = Loc.OpenFromProcess_Runtime;
this.FindControl<TextBlock>("ArchitectureColumnHeader")!.Text = Loc.OpenFromProcess_Architecture;
this.FindControl<TextBlock>("EntryAssemblyColumnHeader")!.Text = Loc.OpenFromProcess_EntryAssembly;
this.FindControl<TextBlock>("AssemblyColumnHeader")!.Text = Loc.Assembly;
this.FindControl<TextBlock>("LocationColumnHeader")!.Text = Loc.OpenFromProcess_Location;
}
void InitializeComponent() => AvaloniaXamlLoader.Load(this);

Loading…
Cancel
Save