[Windows] Fix SearchHandler does not focus when ShowSoftInputAsync is called#35079
[Windows] Fix SearchHandler does not focus when ShowSoftInputAsync is called#35079praveenkumarkarunanithi wants to merge 6 commits intodotnet:mainfrom
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35079Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35079" |
There was a problem hiding this comment.
Pull request overview
Adds Windows support for SearchHandler.ShowSoftInputAsync() / HideSoftInputAsync() by wiring the corresponding internal events in the Windows ShellItemHandler, and introduces a UI test + HostApp repro page to validate focus behavior.
Changes:
- Subscribe/unsubscribe to
ShowSoftInputRequested/HideSoftInputRequestedinShellItemHandler.Windows.csand implement focus/unfocus behavior via theAutoSuggestBox. - Add HostApp issue page
Issue34930with buttons and labels to exercise/observe focus behavior. - Add Appium/NUnit UI test
Issue34930to validate focus occurs after invokingShowSoftInputAsync().
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| src/Controls/src/Core/Handlers/Shell/ShellItemHandler.Windows.cs | Wires ShowSoftInputRequested/HideSoftInputRequested to focus/unfocus the underlying AutoSuggestBox on Windows. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue34930.cs | Adds a HostApp repro page that triggers Show/Hide soft-input requests and displays focus state. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34930.cs | Adds a UI test which taps the button and asserts focus state/event indicators. |
| var focusedEvent = App.WaitForElement("focusedEventLabel").GetText(); | ||
| Assert.That(focusedEvent, Is.EqualTo("FocusedEvent: True")); |
There was a problem hiding this comment.
Same timing issue as above: focusedEventLabel exists from the start, so WaitForElement doesn't wait for its text to change. Consider waiting for the label text to become FocusedEvent: True before asserting to avoid race conditions.
| } | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.SearchBar)] |
There was a problem hiding this comment.
This test is exercising the ShowSoftInputAsync/HideSoftInputAsync behavior; UITestCategories.SoftInput (or Shell) looks like a better fit than SearchBar and is already used for other soft-input tests. Using the more specific category helps CI categorization and targeted runs.
| [Category(UITestCategories.SearchBar)] | |
| [Category(UITestCategories.SoftInput)] |
| public void SearchHandlerShowSoftInputShouldFocusSearchHandlerOnWindows() | ||
| { | ||
| App.WaitForElement("ShowKeyboardButton"); | ||
| App.Tap("ShowKeyboardButton"); | ||
|
|
There was a problem hiding this comment.
The PR wires up both Show/Hide soft-input requests on Windows, but this test only validates the "show" path. To prevent regressions in the new HideSoftInputRequested wiring, consider extending the test to tap the hide button and assert focus is cleared (e.g., IsFocused: False and/or an unfocused indicator).
| showKeyboardButton.Clicked += async (s, e) => | ||
| { | ||
| searchHandler.ShowSoftInputAsync(); | ||
| await Task.Delay(200); | ||
| isFocusedLabel.Text = $"IsFocused: {searchHandler.IsFocused}"; |
There was a problem hiding this comment.
The Task.Delay(200) here is a fixed timing guess to wait for focus to propagate, which can be flaky across devices/CI load. Prefer updating isFocusedLabel in response to searchHandler.Focused/Unfocused (or another deterministic signal) so the UI and the UITest can wait on state instead of time.
| AutomationId = "isFocusedLabel" | ||
| }; | ||
|
|
||
| var focusedEventLabel = new Label | ||
| { | ||
| Text = "FocusedEvent: False", | ||
| AutomationId = "focusedEventLabel" | ||
| }; | ||
|
|
||
| var searchHandler = new SearchHandler | ||
| { | ||
| AutomationId = "searchHandler", |
There was a problem hiding this comment.
AutomationId values in this file are mostly PascalCase (e.g., ShowKeyboardButton), but isFocusedLabel is lower camelCase. Consider using a consistent naming style for AutomationIds to make tests easier to read and maintain.
| AutomationId = "isFocusedLabel" | |
| }; | |
| var focusedEventLabel = new Label | |
| { | |
| Text = "FocusedEvent: False", | |
| AutomationId = "focusedEventLabel" | |
| }; | |
| var searchHandler = new SearchHandler | |
| { | |
| AutomationId = "searchHandler", | |
| AutomationId = "IsFocusedLabel" | |
| }; | |
| var focusedEventLabel = new Label | |
| { | |
| Text = "FocusedEvent: False", | |
| AutomationId = "FocusedEventLabel" | |
| }; | |
| var searchHandler = new SearchHandler | |
| { | |
| AutomationId = "SearchHandler", |
| AutomationId = "isFocusedLabel" | ||
| }; | ||
|
|
||
| var focusedEventLabel = new Label | ||
| { | ||
| Text = "FocusedEvent: False", | ||
| AutomationId = "focusedEventLabel" | ||
| }; | ||
|
|
||
| var searchHandler = new SearchHandler | ||
| { | ||
| AutomationId = "searchHandler", |
There was a problem hiding this comment.
AutomationId is lower camelCase here (focusedEventLabel) while other IDs in the page use PascalCase (e.g., ShowKeyboardButton). Aligning on one style for AutomationIds improves consistency across HostApp issue pages and UI tests.
| AutomationId = "isFocusedLabel" | |
| }; | |
| var focusedEventLabel = new Label | |
| { | |
| Text = "FocusedEvent: False", | |
| AutomationId = "focusedEventLabel" | |
| }; | |
| var searchHandler = new SearchHandler | |
| { | |
| AutomationId = "searchHandler", | |
| AutomationId = "IsFocusedLabel" | |
| }; | |
| var focusedEventLabel = new Label | |
| { | |
| Text = "FocusedEvent: False", | |
| AutomationId = "FocusedEventLabel" | |
| }; | |
| var searchHandler = new SearchHandler | |
| { | |
| AutomationId = "SearchHandler", |
| AutomationId = "isFocusedLabel" | ||
| }; | ||
|
|
||
| var focusedEventLabel = new Label | ||
| { | ||
| Text = "FocusedEvent: False", | ||
| AutomationId = "focusedEventLabel" | ||
| }; | ||
|
|
||
| var searchHandler = new SearchHandler | ||
| { | ||
| AutomationId = "searchHandler", |
There was a problem hiding this comment.
AutomationId = "searchHandler" is lower camelCase while other AutomationIds in the page use PascalCase. Consider renaming for consistency (and update the corresponding UITest IDs if it becomes referenced there).
| AutomationId = "isFocusedLabel" | |
| }; | |
| var focusedEventLabel = new Label | |
| { | |
| Text = "FocusedEvent: False", | |
| AutomationId = "focusedEventLabel" | |
| }; | |
| var searchHandler = new SearchHandler | |
| { | |
| AutomationId = "searchHandler", | |
| AutomationId = "IsFocusedLabel" | |
| }; | |
| var focusedEventLabel = new Label | |
| { | |
| Text = "FocusedEvent: False", | |
| AutomationId = "FocusedEventLabel" | |
| }; | |
| var searchHandler = new SearchHandler | |
| { | |
| AutomationId = "SearchHandler", |
|
|
||
| var isFocused = App.WaitForElement("isFocusedLabel").GetText(); | ||
| Assert.That(isFocused, Is.EqualTo("IsFocused: True")); | ||
|
|
There was a problem hiding this comment.
The test reads isFocusedLabel text immediately after tapping the button. Since the label exists before the tap, WaitForElement doesn't guarantee the text has updated yet, which can make this assertion flaky. Prefer waiting for the expected text (e.g., via WaitForTextToBePresentInElement) before asserting.
| var isFocused = App.WaitForElement("isFocusedLabel").GetText(); | |
| Assert.That(isFocused, Is.EqualTo("IsFocused: True")); | |
| App.WaitForTextToBePresentInElement("isFocusedLabel", "IsFocused: True"); | |
| var isFocused = App.WaitForElement("isFocusedLabel").GetText(); | |
| Assert.That(isFocused, Is.EqualTo("IsFocused: True")); | |
| App.WaitForTextToBePresentInElement("focusedEventLabel", "FocusedEvent: True"); |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the ai's summary and verify the failing tests?
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's summary?
|
|
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue.
Thank you!
Root Cause
PR #29600 added
ShowSoftInputRequestedandHideSoftInputRequestedevent handling for Android and iOS via their respectiveSearchHandlerAppearanceTrackerimplementations.However, Windows uses
ShellItemHandler.Windows.cs, which was not updated to handle these events. As a result, although the events are raised bySearchHandler.ShowSoftInputAsync(), there are no subscribers on Windows, leading to no action.Description of Change
Extended support for
ShowSoftInputAsync()/HideSoftInputAsync()to Windows by wiring the corresponding events inShellItemHandler.Windows.cs.Event subscriptions are handled in
UpdateSearchHandlerwhen a handler is assigned, and are properly cleaned up when the handler is replaced or duringDisconnectHandler.Since Windows does not use an on-screen keyboard, the implementation focuses on focus management. Focus is applied using
autoSuggestBox.Focus(FocusState.Programmatic), aligning with standard WinUI behavior. Unfocus is handled using the existingIsEnabledtoggle pattern (consistent withViewExtensions.UnfocusControl), with proper preservation ofIsTabStop.A safety guard is included to ensure that disabled controls are not unintentionally re-enabled during the unfocus process.
Issues Fixed
Fixes #34930
Tested the behaviour in the following platforms
Screenshots