Look the panel up, because the field the generator declares is never filled
ci / api image (push) Successful in 34s
ci / build and test (push) Successful in 1m17s
ci / android head (push) Failing after 5s

The last commit crashed the phone before its first frame. `PhoneShell` took the
`Body` field that Avalonia's name generator declares for `x:Name`, and that
field is assigned in the generated `InitializeComponent` — which no view on
this head calls, all of them loading their XAML directly instead. It compiles,
it is null at run time, and this control dereferences it in its constructor.

`TerminalScreen` has been looking its two named controls up with `FindControl`
since it was written, for exactly this reason. This does the same, and the
remark on the field now says why so the next person reaching for the shorter
spelling finds the answer before the device does.

Confirmed rather than guessed: the generator's output for this control is one
method, and the assignment is inside it.
This commit is contained in:
2026-08-03 14:01:07 +02:00
parent 35387b1c9d
commit ce86a4ff72
@@ -17,6 +17,17 @@ internal sealed partial class PhoneShell : UserControl
{
private MainWindowViewModel? shell;
/// <summary>
/// Everything the phone draws, which is the element the software keyboard is kept off.
/// </summary>
/// <remarks>
/// Looked up rather than read off the field the name generator declares for <c>x:Name</c>, and
/// <c>TerminalScreen</c> does the same for the same reason: that field is assigned by the generated
/// <c>InitializeComponent</c>, and no view on this head calls it — they load their XAML directly. Using
/// it compiles and is null at run time, which on this control means a crash before the first frame.
/// </remarks>
private readonly Panel body;
/// <summary>The software keyboard, while this control is attached. Null on a platform without one.</summary>
private IInputPane? keyboard;
@@ -37,10 +48,12 @@ internal sealed partial class PhoneShell : UserControl
{
AvaloniaXamlLoader.Load(this);
// Subscribed once, for the life of the control, rather than in OnAttachedToVisualTree: Body is this
// control's own child and cannot outlive it, and re-subscribing on every attach is how a handler
// ends up registered twice.
Body.SizeChanged += OnBodyResized;
body = this.FindControl<Panel>("Body")!;
// Subscribed once, for the life of the control, rather than in OnAttachedToVisualTree: the panel is
// this control's own child and cannot outlive it, and re-subscribing on every attach is how a
// handler ends up registered twice.
body.SizeChanged += OnBodyResized;
DataContextChanged += (_, _) =>
{
@@ -208,9 +221,9 @@ internal sealed partial class PhoneShell : UserControl
{
var inset = double.IsFinite(occluded) ? Math.Max(occluded, 0) : 0;
if (Math.Abs(Body.Margin.Bottom - inset) > 0.5)
if (Math.Abs(body.Margin.Bottom - inset) > 0.5)
{
Body.Margin = new Thickness(0, 0, 0, inset);
body.Margin = new Thickness(0, 0, 0, inset);
}
}