Skip to content

KeyPress Scope #36

Description

@CraigBelser

Problem Description
The current behavior of the key listener does not align with standard keypress event scoping expectations. Specifically:

  1. When the behavior is attached to a control (e.g., an Entry), only that control should receive the keypress events.
  2. Sibling controls or controls outside the visual scope should not receive these events.
  3. Parent elements in the visual tree should receive the events only if the event is not marked as handled (e.Handled = true).
  4. Keyboard events should bubble up the visual tree, following standard event routing patterns.

Expected Behavior
Keypress events should be scoped to the VisualElement the behavior is attached to, and optionally bubble up to its parent elements. The behavior should not act as a global key event listener where all controls receive all keypresses indiscriminately.

Core Requirements

  1. Keypress events must be routed to the attached control and optionally bubble up.
  2. The behavior must respect event handling (Handled = true) to prevent further propagation.
  3. When multiple behaviors are present, each should receive only the events relevant to its scope.

Ask
Please scope the keystroke handling appropriately, especially in scenarios where multiple behaviors are used. The current implementation behaves like a global listener, which breaks encapsulation and expected UI behavior.

Reproduction
To reproduce the issue, open the KeyListener sample project and replace the contents of MainPage.xaml.cs with the following code:

using Plugin.Maui.KeyListener;
namespace Plugin.Maui.KeyListener.Sample;

public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}

void ClearButton_Clicked(object sender, EventArgs e)
{
    OutputLabel1.Text = string.Empty;
    OutputLabel2.Text = string.Empty;
    EntryTester1.Text = string.Empty;
    EntryTester2.Text = string.Empty;
}

void OnKeyDown_Entry1(object sender, KeyPressedEventArgs e)
{
    string newValue = $"KeyPressed= {e.Keys}, Modifiers={e.Modifiers}";
    EntryTester1.Text = newValue;
    OutputLabel1.Text = newValue + Environment.NewLine + OutputLabel1.Text;
    e.Handled = true;
}

void OnKeyDown_Entry2(object sender, KeyPressedEventArgs e)
{
    string newValue = $"KeyPressed= {e.Keys}, Modifiers={e.Modifiers}";
    EntryTester2.Text = newValue;
    OutputLabel2.Text = newValue + Environment.NewLine + OutputLabel2.Text;
    e.Handled = true;
}

}

Replace MainPage.xaml with:

<ScrollView>
    <VerticalStackLayout Margin="20" Spacing="20">
        <Label Text="Press any key to see the event details" />
        <Button Clicked="ClearButton_Clicked" Text="Clear" />
        <Entry
            x:Name="EntryTester1"
            BackgroundColor="LightBlue"
            MinimumWidthRequest="400"
            Placeholder="Text Test 1"
            TextColor="Black">
            <Entry.Behaviors>
                <keyListener:KeyboardBehavior KeyDown="OnKeyDown_Entry1" />
            </Entry.Behaviors>
        </Entry>
        <Entry
            x:Name="EntryTester2"
            BackgroundColor="LightBlue"
            MinimumWidthRequest="400"
            Placeholder="Text Test 2"
            TextColor="Black">
            <Entry.Behaviors>
                <keyListener:KeyboardBehavior KeyDown="OnKeyDown_Entry2" />
            </Entry.Behaviors>
        </Entry>
        <Button Text="Does Nothing, is tab stop" />
        <Grid ColumnDefinitions="*,50,*" Grid.RowDefinitions="Auto,*">
            <Label
                Grid.Row="0"
                Grid.Column="0"
                FontAttributes="Bold"
                Text="First Entry Output" />
            <Label
                Grid.Row="0"
                Grid.Column="2"
                FontAttributes="Bold"
                Text="Second Entry Output" />
            <Label
                x:Name="OutputLabel1"
                Grid.Row="1"
                Grid.Column="0" />
            <Label
                x:Name="OutputLabel2"
                Grid.Row="1"
                Grid.Column="2" />
        </Grid>

    </VerticalStackLayout>
</ScrollView>

Run the app and focus on one Entry. Typing will trigger both KeyDown handlers, even though only one Entry has focus.

Running it and typing while focused on one entry control will call all of the listeners, not just the scoped entry control.

Here is a Recording of it (Note Testing was done on Windows).

KeyboardScopeIssue.mp4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions