Skip to content
46 changes: 46 additions & 0 deletions src/Controls/src/Core/Entry/Entry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ public ClearButtonVisibility ClearButtonVisibility
/// </summary>
public event EventHandler Completed;

/// <summary>
/// Occurs when a key is pressed down while the entry has focus.
/// </summary>
public event EventHandler<KeyEventArgs> KeyDown;

/// <summary>
/// Occurs when a key is released while the entry has focus.
/// </summary>
public event EventHandler<KeyEventArgs> KeyUp;

/// <summary>
/// Internal method to trigger <see cref="Completed"/> and <see cref="ReturnCommand"/>.
/// Should not be called manually outside of .NET MAUI.
Expand All @@ -186,6 +196,32 @@ public void SendCompleted()
}
}

/// <summary>
/// Internal method to trigger <see cref="KeyDown"/> event.
/// Should not be called manually outside of .NET MAUI.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public void SendKeyDown(string key)
{
if (IsEnabled)
{
KeyDown?.Invoke(this, new KeyEventArgs(key));
}
}

/// <summary>
/// Internal method to trigger <see cref="KeyUp"/> event.
/// Should not be called manually outside of .NET MAUI.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public void SendKeyUp(string key)
{
if (IsEnabled)
{
KeyUp?.Invoke(this, new KeyEventArgs(key));
}
}

/// <inheritdoc/>
public IPlatformElementConfiguration<T, Entry> On<T>() where T : IConfigPlatform
{
Expand All @@ -200,5 +236,15 @@ void IEntry.Completed()
{
(this as IEntryController).SendCompleted();
}

void IEntry.KeyDown(string key)
{
SendKeyDown(key);
}

void IEntry.KeyUp(string key)
{
SendKeyUp(key);
}
}
}
24 changes: 24 additions & 0 deletions src/Controls/src/Core/KeyEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace Microsoft.Maui.Controls
{
/// <summary>
/// Provides data for key events.
/// </summary>
public class KeyEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="KeyEventArgs"/> class.
/// </summary>
/// <param name="key">The key that was pressed.</param>
public KeyEventArgs(string key)
{
Key = key;
}

/// <summary>
/// Gets the key that was pressed.
/// </summary>
public string Key { get; }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
#nullable enable
Microsoft.Maui.Controls.Entry.KeyDown.add -> void
Microsoft.Maui.Controls.Entry.KeyDown.remove -> void
Microsoft.Maui.Controls.Entry.KeyUp.add -> void
Microsoft.Maui.Controls.Entry.KeyUp.remove -> void
Microsoft.Maui.Controls.Entry.SendKeyDown(string! key) -> void
Microsoft.Maui.Controls.Entry.SendKeyUp(string! key) -> void
Microsoft.Maui.Controls.KeyEventArgs
Microsoft.Maui.Controls.KeyEventArgs.Key.get -> string!
Microsoft.Maui.Controls.KeyEventArgs.KeyEventArgs(string! key) -> void
override Microsoft.Maui.Controls.ContentPresenter.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.ScrollView.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.TemplatedView.OnSizeAllocated(double width, double height) -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
#nullable enable
Microsoft.Maui.Controls.Entry.KeyDown.add -> void
Microsoft.Maui.Controls.Entry.KeyDown.remove -> void
Microsoft.Maui.Controls.Entry.KeyUp.add -> void
Microsoft.Maui.Controls.Entry.KeyUp.remove -> void
Microsoft.Maui.Controls.Entry.SendKeyDown(string! key) -> void
Microsoft.Maui.Controls.Entry.SendKeyUp(string! key) -> void
Microsoft.Maui.Controls.KeyEventArgs
Microsoft.Maui.Controls.KeyEventArgs.Key.get -> string!
Microsoft.Maui.Controls.KeyEventArgs.KeyEventArgs(string! key) -> void
override Microsoft.Maui.Controls.ContentPresenter.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.ScrollView.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.TemplatedView.OnSizeAllocated(double width, double height) -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
#nullable enable
Microsoft.Maui.Controls.Entry.KeyDown.add -> void
Microsoft.Maui.Controls.Entry.KeyDown.remove -> void
Microsoft.Maui.Controls.Entry.KeyUp.add -> void
Microsoft.Maui.Controls.Entry.KeyUp.remove -> void
Microsoft.Maui.Controls.Entry.SendKeyDown(string! key) -> void
Microsoft.Maui.Controls.Entry.SendKeyUp(string! key) -> void
Microsoft.Maui.Controls.KeyEventArgs
Microsoft.Maui.Controls.KeyEventArgs.Key.get -> string!
Microsoft.Maui.Controls.KeyEventArgs.KeyEventArgs(string! key) -> void
override Microsoft.Maui.Controls.ContentPresenter.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.ScrollView.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.TemplatedView.OnSizeAllocated(double width, double height) -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
#nullable enable
Microsoft.Maui.Controls.Entry.KeyDown.add -> void
Microsoft.Maui.Controls.Entry.KeyDown.remove -> void
Microsoft.Maui.Controls.Entry.KeyUp.add -> void
Microsoft.Maui.Controls.Entry.KeyUp.remove -> void
Microsoft.Maui.Controls.Entry.SendKeyDown(string! key) -> void
Microsoft.Maui.Controls.Entry.SendKeyUp(string! key) -> void
Microsoft.Maui.Controls.KeyEventArgs
Microsoft.Maui.Controls.KeyEventArgs.Key.get -> string!
Microsoft.Maui.Controls.KeyEventArgs.KeyEventArgs(string! key) -> void
override Microsoft.Maui.Controls.ContentPresenter.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.ScrollView.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.TemplatedView.OnSizeAllocated(double width, double height) -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
#nullable enable
Microsoft.Maui.Controls.Entry.KeyDown.add -> void
Microsoft.Maui.Controls.Entry.KeyDown.remove -> void
Microsoft.Maui.Controls.Entry.KeyUp.add -> void
Microsoft.Maui.Controls.Entry.KeyUp.remove -> void
Microsoft.Maui.Controls.Entry.SendKeyDown(string! key) -> void
Microsoft.Maui.Controls.Entry.SendKeyUp(string! key) -> void
Microsoft.Maui.Controls.KeyEventArgs
Microsoft.Maui.Controls.KeyEventArgs.Key.get -> string!
Microsoft.Maui.Controls.KeyEventArgs.KeyEventArgs(string! key) -> void
override Microsoft.Maui.Controls.ContentPresenter.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.ScrollView.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.TemplatedView.OnSizeAllocated(double width, double height) -> void
Expand Down
7 changes: 7 additions & 0 deletions src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
#nullable enable
Microsoft.Maui.Controls.Entry.KeyDown -> System.EventHandler<Microsoft.Maui.Controls.KeyEventArgs>
Microsoft.Maui.Controls.Entry.KeyUp -> System.EventHandler<Microsoft.Maui.Controls.KeyEventArgs>
Microsoft.Maui.Controls.KeyEventArgs
Microsoft.Maui.Controls.KeyEventArgs.Key.get -> string!
Microsoft.Maui.Controls.KeyEventArgs.KeyEventArgs(string! key) -> void
override Microsoft.Maui.Controls.ContentPresenter.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.ScrollView.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.TemplatedView.OnSizeAllocated(double width, double height) -> void
virtual Microsoft.Maui.Controls.BindableProperty.CreateDefaultValueDelegate<TDeclarer, TPropertyType>.Invoke(TDeclarer bindable) -> TPropertyType
~Microsoft.Maui.Controls.Entry.SendKeyDown(string key) -> void
~Microsoft.Maui.Controls.Entry.SendKeyUp(string key) -> void
~virtual Microsoft.Maui.Controls.BindableProperty.BindingPropertyChangedDelegate.Invoke(Microsoft.Maui.Controls.BindableObject bindable, object oldValue, object newValue) -> void
~virtual Microsoft.Maui.Controls.BindableProperty.BindingPropertyChangedDelegate<TPropertyType>.Invoke(Microsoft.Maui.Controls.BindableObject bindable, TPropertyType oldValue, TPropertyType newValue) -> void
~virtual Microsoft.Maui.Controls.BindableProperty.BindingPropertyChangingDelegate.Invoke(Microsoft.Maui.Controls.BindableObject bindable, object oldValue, object newValue) -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
#nullable enable
Microsoft.Maui.Controls.Entry.KeyDown -> System.EventHandler<Microsoft.Maui.Controls.KeyEventArgs>
Microsoft.Maui.Controls.Entry.KeyUp -> System.EventHandler<Microsoft.Maui.Controls.KeyEventArgs>
Microsoft.Maui.Controls.KeyEventArgs
Microsoft.Maui.Controls.KeyEventArgs.Key.get -> string!
Microsoft.Maui.Controls.KeyEventArgs.KeyEventArgs(string! key) -> void
override Microsoft.Maui.Controls.ScrollView.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.TemplatedView.OnSizeAllocated(double width, double height) -> void
override Microsoft.Maui.Controls.ContentPresenter.OnSizeAllocated(double width, double height) -> void
virtual Microsoft.Maui.Controls.BindableProperty.CreateDefaultValueDelegate<TDeclarer, TPropertyType>.Invoke(TDeclarer bindable) -> TPropertyType
~Microsoft.Maui.Controls.Entry.SendKeyDown(string key) -> void
~Microsoft.Maui.Controls.Entry.SendKeyUp(string key) -> void
~virtual Microsoft.Maui.Controls.BindableProperty.BindingPropertyChangedDelegate.Invoke(Microsoft.Maui.Controls.BindableObject bindable, object oldValue, object newValue) -> void
~virtual Microsoft.Maui.Controls.BindableProperty.BindingPropertyChangedDelegate<TPropertyType>.Invoke(Microsoft.Maui.Controls.BindableObject bindable, TPropertyType oldValue, TPropertyType newValue) -> void
~virtual Microsoft.Maui.Controls.BindableProperty.BindingPropertyChangingDelegate.Invoke(Microsoft.Maui.Controls.BindableObject bindable, object oldValue, object newValue) -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.KeyEventsOnEmptyEntry"
Title="Key Events on Empty Entry">
<StackLayout Padding="20">
<Label Text="1. Focus the Entry below" />
<Label Text="2. Press backspace key (Entry should be empty)" />
<Label Text="3. Verify KeyDown and KeyUp events are triggered" />

<Entry x:Name="TestEntry"
AutomationId="TestEntry"
Placeholder="Empty Entry - Press backspace"
KeyDown="OnKeyDown"
KeyUp="OnKeyUp" />

<Label Text="Event Log:" FontAttributes="Bold" Margin="0,20,0,10" />

<Label x:Name="KeyDownLabel"
AutomationId="KeyDownLabel"
Text="KeyDown: None" />

<Label x:Name="KeyUpLabel"
AutomationId="KeyUpLabel"
Text="KeyUp: None" />

<Label x:Name="EventCountLabel"
AutomationId="EventCountLabel"
Text="Total Events: 0" />

<Button Text="Clear Log"
AutomationId="ClearButton"
Clicked="OnClearClicked" />
</StackLayout>
</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Microsoft.Maui.Controls;

namespace Maui.Controls.Sample.Issues
{
public partial class KeyEventsOnEmptyEntry : ContentPage
{
private int _eventCount = 0;

public KeyEventsOnEmptyEntry()
{
InitializeComponent();
}

private void OnKeyDown(object sender, KeyEventArgs e)
{
_eventCount++;
KeyDownLabel.Text = $"KeyDown: {e.Key} (Count: {_eventCount})";
UpdateEventCount();
}

private void OnKeyUp(object sender, KeyEventArgs e)
{
_eventCount++;
KeyUpLabel.Text = $"KeyUp: {e.Key} (Count: {_eventCount})";
UpdateEventCount();
}

private void UpdateEventCount()
{
EventCountLabel.Text = $"Total Events: {_eventCount}";
}

private void OnClearClicked(object sender, System.EventArgs e)
{
_eventCount = 0;
KeyDownLabel.Text = "KeyDown: None";
KeyUpLabel.Text = "KeyUp: None";
EventCountLabel.Text = "Total Events: 0";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class KeyEventsOnEmptyEntry : _IssuesUITest
{
public KeyEventsOnEmptyEntry(TestDevice testDevice) : base(testDevice)
{
}

public override string Issue => "Key events (KeyDown/KeyUp) should trigger when backspace is pressed on empty Entry on iOS/Mac";

[Test]
[Category(UITestCategories.Entry)]
public void KeyEventsTriggeredOnEmptyEntry()
{
App.WaitForElement("TestEntry");

// Focus the entry
App.Tap("TestEntry");

// Clear any existing text to ensure entry is empty
App.ClearText("TestEntry");

// Press backspace on empty entry
// Note: This simulates the backspace key press on an empty field
App.PressKeycode("Backspace");

// Wait a moment for events to process
System.Threading.Thread.Sleep(1000);

// Verify that key events were triggered
var keyDownLabel = App.FindElement("KeyDownLabel");
var keyUpLabel = App.FindElement("KeyUpLabel");
var eventCountLabel = App.FindElement("EventCountLabel");

// Check that KeyDown event was triggered with "Backspace"
Assert.That(keyDownLabel.GetText(), Does.Contain("Backspace"),
"KeyDown event should be triggered with 'Backspace' key when backspace is pressed on empty Entry");

// Check that KeyUp event was triggered with "Backspace"
Assert.That(keyUpLabel.GetText(), Does.Contain("Backspace"),
"KeyUp event should be triggered with 'Backspace' key when backspace is pressed on empty Entry");

// Check that at least 2 events were triggered (KeyDown + KeyUp)
Assert.That(eventCountLabel.GetText(), Does.Not.Contain("Total Events: 0"),
"Both KeyDown and KeyUp events should be triggered when backspace is pressed on empty Entry");
}

[Test]
[Category(UITestCategories.Entry)]
public void KeyEventsTriggeredWhenTypingInEntry()
{
App.WaitForElement("TestEntry");

// Clear log first
App.Tap("ClearButton");

// Focus the entry and type some text
App.Tap("TestEntry");
App.EnterText("TestEntry", "A");

// Wait a moment for events to process
System.Threading.Thread.Sleep(1000);

// Verify that events were triggered for typing
var eventCountLabel = App.FindElement("EventCountLabel");
Assert.That(eventCountLabel.GetText(), Does.Not.Contain("Total Events: 0"),
"Key events should be triggered when typing in Entry");
}
}
}
12 changes: 12 additions & 0 deletions src/Core/src/Core/IEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,17 @@ public interface IEntry : IView, ITextInput, ITextAlignment
/// Occurs when the user finalizes the text in an entry with the return key.
/// </summary>
void Completed();

/// <summary>
/// Triggers a key down event for the entry.
/// </summary>
/// <param name="key">The key that was pressed down.</param>
void KeyDown(string key);

/// <summary>
/// Triggers a key up event for the entry.
/// </summary>
/// <param name="key">The key that was released.</param>
void KeyUp(string key);
}
}
Loading