Let a group be right-clicked, as a host already can

The host cards have had a menu since the grid replaced the sidebar; the group
cards above them had a double-click and two buttons beside the heading, and
nothing that named the card under the pointer. Open, Edit and Delete are on them
now, drawn and aimed the same way.

On the list rather than in the item template, for the reason the host grid's is:
the three commands are the vault's, and a ContextMenu inside a DataTemplate has
the row for its data context, so every binding in it would silently resolve to
nothing. The code-behind selects whatever was right-clicked before the menu
opens, and that is what makes one menu act on the card under the pointer rather
than on whichever was selected before.

**Cancelled over the space around the cards, and here that guard is doing more
than the host grid's.** GroupTarget falls back to the group whose contents are on
screen when no card is selected — the right answer for a pair of buttons beside
the heading, which would otherwise have no subject the moment a group with
nothing inside it is opened, and the wrong one for a menu that opened on a card.
Without the guard, right-clicking the gap beside the cards would offer to delete
the group the trail ends with: a question about something the user is not
pointing at, in the one menu where the answer is a deletion.

Only Open takes a parameter, and it has to. OpenGroupCommand's null is a real
argument rather than a missing one — it is the trail's first crumb, ALL HOSTS —
so an entry with no parameter would not open the card, it would leave the group
the user right-clicked and go back to the top level.

Two tests beside the two the host menu already had. What they hold that a build
cannot is the CommandParameter binding: a path that resolves to nothing compiles
and draws, and the entry would then quietly do the opposite of what it says. The
popup itself is still the platform's, so manual-checks 7.9 gained the group half
of the same check.
This commit is contained in:
2026-08-04 16:27:01 +02:00
parent 7f5b871c47
commit be012585b3
4 changed files with 160 additions and 5 deletions
@@ -96,6 +96,11 @@ internal sealed partial class HostsScreen : UserControl
HostGrid.AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
HostGrid.AddHandler(ContextRequestedEvent, OnContextRequested, RoutingStrategies.Tunnel);
// And the group cards answer a right click the same way, for the same reason: their menu's commands
// read the vault's group selection, and without this they would act on whichever card was selected
// before — or, with none, on the group the trail ends with, which is not on screen at all.
GroupGrid.AddHandler(ContextRequestedEvent, OnGroupContextRequested, RoutingStrategies.Tunnel);
HostGrid.PointerMoved += OnPointerMoved;
HostGrid.PointerReleased += OnPointerReleased;
HostGrid.PointerCaptureLost += OnPointerCaptureLost;
@@ -204,6 +209,33 @@ internal sealed partial class HostsScreen : UserControl
vault.SelectedSidebarRow = row;
}
/// <summary>
/// Points the group menu at whatever was right-clicked.
/// </summary>
/// <remarks>
/// <para>
/// The host grid's rule, applied to the cards above it — see <see cref="OnContextRequested"/>. What is
/// different is what an unaimed menu would have done: <c>GroupTarget</c> falls back to the open group
/// when no card is selected, so Edit and Delete over a card would have been offered about the group whose
/// contents are showing rather than the one the pointer is on. That fallback is right for a pair of
/// buttons that sit beside the heading and wrong for a menu that opened on a card.
/// </para>
/// <para>
/// Cancelled outright over the space around the cards, as the host grid's is. That is not a group, and
/// the fallback is exactly what would make the menu look like it worked there.
/// </para>
/// </remarks>
private void OnGroupContextRequested(object? sender, ContextRequestedEventArgs e)
{
if (Vault is not { } vault || RowUnder(e.Source) is not HostGroupRowViewModel row)
{
e.Handled = true;
return;
}
vault.SelectedGroup = row;
}
/// <remarks>
/// Remembered rather than acted on. Whether this press is a click or the start of a drag is not known
/// until the pointer moves, so this is the point at which both are still possible.