Skip to content

Rename/Refactor Command.Select and Selecting Event to Activate #4050

@tig

Description

@tig

Summary

The Command.Select, Selecting event, and related APIs (OnSelecting, RaiseSelecting) should be renamed to Command.Activate, Activating event, OnActivating, and RaiseActivating to improve clarity and consistency in the Command system. The term "select" is ambiguous for stateless views (e.g., Button, MenuItemv2, where it only sets focus) and less precise for non-list-based state changes (e.g., toggling a CheckBox). "Activate" better captures the action of changing a view’s state or preparing it for interaction, aligning with both stateful (e.g., CheckBox, FlagSelector, ListView) and stateless (e.g., Button, MenuItemv2) use cases while clearly distinguishing from Accepting.

Additionally, a targeted propagation model for Command.Activate is needed to support hierarchical components like MenuBarv2, which requires Activating events from subviews (e.g., MenuItemv2) to manage PopoverMenu visibility. To maintain decoupling, subviews should not specify propagation behavior. Instead, superviews should opt-in to receive propagated Activating events via a new PropagateActivating property in View, allowing MenuBarv2 to handle subview activations without subviews knowing superview details.

This issue is related to the planned Handled change in CommandEventArgs (issue #3913), which replaces Cancel with Handled to align with input event semantics.

Motivation

The current Command.Select and Selecting terminology has several issues:

  1. Ambiguity in Stateless Views: For views like Button or MenuItemv2, Command.Select only sets focus, which isn’t typically considered “selection,” leading to developer confusion.
  2. Imprecise for Non-List Contexts: In CheckBox (toggling state) or FlagSelector (toggling flags), “select” doesn’t fully capture the state change, unlike list-based selection in ListView.
  3. Overlap with Accepting: In views like ListView or Menuv2, Selecting and Accepting can feel similar (e.g., Enter triggering both), requiring clearer distinction.
  4. Propagation Needs: MenuBarv2 requires Activating events from subviews to manage PopoverMenu visibility, but the current local handling model relies on view-specific events (e.g., SelectedMenuItemChanged), which isn’t generalizable. Subviews should remain decoupled from superviews, avoiding knowledge of superview implementation details (e.g., MenuBarv2’s popover logic).

The Activate terminology addresses these by:

  • Representing both state changes (e.g., toggling a CheckBox, selecting a ListView item) and preparatory actions (e.g., focusing a Button, navigating a MenuItemv2).
  • Distinguishing clearly from Accepting, which confirms actions (e.g., executing a menu command, submitting a dialog).
  • Supporting a targeted propagation model with PropagateActivating, enabling superviews like MenuBarv2 to opt-in to subview Activating events without coupling subviews to superview details.

Proposed Changes

1. Rename Command.Select and Related APIs to Activate

  • Rename Command.Select to Command.Activate in the Command enum.
  • Rename Selecting event to Activating, OnSelecting to OnActivating, and RaiseSelecting to RaiseActivating in the View class and all derived classes (e.g., Menuv2, MenuItemv2, CheckBox, FlagSelector).
  • Update related code:
    • Modify SetupCommands in View to use Command.Activate.
    • Update Shortcut.DispatchCommand and other command handlers to reference Activating.
    • Ensure all event handlers and documentation reflect the new terminology.
  • Example:
    public enum Command
    {
        Activate, // Previously Select
        Accept,
        HotKey,
        NotBound,
        // ...
    }
    
    // In View
    public event EventHandler<CommandEventArgs>? Activating;
    protected virtual bool OnActivating(CommandEventArgs args) { return false; }
    protected bool? RaiseActivating(ICommandContext? ctx)
    {
        CommandEventArgs args = new() { Context = ctx };
        if (OnActivating(args) || args.Handled)
        {
            return true;
        }
        Activating?.Invoke(this, args);
        if (!args.Handled && SuperView?.PropagateActivating == true)
        {
            return SuperView.InvokeCommand(Command.Activate, ctx);
        }
        return Activating is null ? null : args.Handled;
    }

2. Introduce Targeted Propagation Model with PropagateActivating

  • Add bool PropagateActivating to View to allow superviews (e.g., MenuBarv2) to opt-in to receiving Activating events from subviews, defaulting to false for local handling.
    public class View
    {
        // Existing properties and methods...
        public bool PropagateActivating { get; set; } // Opt-in to receive subview Activating events
    }
  • Implement propagation in RaiseActivating:
    • If SuperView.PropagateActivating is true and args.Handled is false, propagate to the superview.
    • Example: Propagate to MenuBarv2 for menu hierarchies.
  • Handle propagated events in MenuBarv2:
    public class MenuBarv2 : Menuv2
    {
        public MenuBarv2()
        {
            PropagateActivating = true; // Opt-in to subview Activating events
            // Other initialization...
        }
    
        protected override bool? RaiseActivating(ICommandContext? ctx)
        {
            if (ctx?.Source is MenuItemv2 menuItem && menuItem.SuperView is Menuv2 menu)
            {
                HideItem(GetActiveItem()); // Hide current popover
                ShowItem(menu.SuperMenuItem); // Show new popover
                return true;
            }
            return base.RaiseActivating(ctx);
        }
    }
  • Apply to menu hierarchies initially, with potential extension to other components (e.g., Toplevel for dialogs). Views like CheckBox and FlagSelector retain local handling by default.

3. Update Documentation

  • Clarify Activating vs. Accepting:
    • Activating: State changes or interaction preparation (e.g., toggling a CheckBox, focusing a MenuItemv2, selecting a ListView item).
    • Accepting: Action confirmation (e.g., executing a menu command, submitting a dialog).
    • Example: “Activating is raised when a CheckBox is toggled or a MenuItemv2 is focused, while Accepting is raised when a menu command is executed or a Button is activated.”
  • Document Handled: Emphasize that Handled means “processed and complete,” aligning with Key.Handled.
  • Document propagation: Explain the targeted model with PropagateActivating, providing examples for MenuBarv2 and potential Toplevel use cases.
  • Update view-specific docs:
    • Menuv2/MenuBarv2: Highlight Activating for focus/navigation and Accepted for action completion.
    • CheckBox: Clarify Activating for state toggling and Accepting for confirmation.
    • FlagSelector: Address the Selecting/Accepting conflation, recommending separation.

4. Address FlagSelector Design Flaw

  • Issue: FlagSelector’s CheckBox.Selecting handler raises both Selecting and Accepting, conflating state change and confirmation.
  • Fix: Limit Selecting (to be Activating) to subview state changes, reserving Accepting for parent-level confirmation.
    checkbox.Activating += (sender, args) =>
    {
        if (RaiseActivating(args.Context) is true)
        {
            args.Handled = true;
        }
    };

Impact

  • Code Changes: Renaming affects View, Shortcut, Menuv2, MenuItemv2, CheckBox, FlagSelector, and other views using Command.Select. Propagation requires adding PropagateActivating to View and updating RaiseActivating.
  • Documentation: Extensive updates to clarify Activating/Accepting, Handled, and propagation.
  • Compatibility: Breaking change for users relying on Command.Select/Selecting. Provide migration guidance in release notes.
  • Benefits:

TODO

[] - Rename Command.Select to Command.Activate in the Command enum.
[] - Rename Selecting event to Activating, OnSelecting to OnActivating, and RaiseSelecting to RaiseActivating in View and derived classes.
[] - Update all references to Command.Select/Selecting in the codebase (e.g., SetupCommands, Shortcut.DispatchCommand).
[] - Add bool PropagateActivating to View class, defaulting to false.
[] - Implement targeted propagation in RaiseActivating to check SuperView.PropagateActivating and propagate to the superview.
[] - Implement MenuBarv2 handling for propagated Activating events to manage PopoverMenu visibility, setting PropagateActivating = true.
[] - Refactor FlagSelector to separate Activating and Accepting in CheckBox.Selecting handler.
[] - Update unit tests for renamed APIs, propagation behavior, and FlagSelector fix.
[] - Revise Command system documentation to reflect Activate, Handled, and propagation.
[] - Update view-specific documentation for Menuv2, MenuBarv2, CheckBox, and FlagSelector.
[] - Document breaking changes and migration steps in release notes.

Related Issues

Additional Context

This proposal is informed by analysis of Menuv2, MenuBarv2, CheckBox, and FlagSelector, which highlight:

  • Activate’s clarity for state changes (e.g., CheckBox toggling, FlagSelector flags) and preparation (e.g., MenuItemv2 focus).
  • The need for targeted propagation in MenuBarv2 to manage PopoverMenu visibility, with subviews decoupled from superview details.
  • The sufficiency of view-specific post-events (e.g., CheckedStateChanged, ValueChanged, SelectedMenuItemChanged) over generic Activated.
  • The value of Accepted in hierarchical views, suggesting inclusion in Bar or Toplevel rather than View.
  • The FlagSelector design flaw conflating Selecting and Accepting, requiring a fix to align with Activating/Accepting separation.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

Status

🏗 Approved - In progress

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions