Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ComboBox.IsEditable #17792

Open
wants to merge 69 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
c22323f
chore: Start IsEditable support for ComboBox
MartinZikmund Jul 30, 2024
1d8a026
chore: KeyDown porting
MartinZikmund Jul 30, 2024
dd58831
chore: Continue adding IsEditable logic
MartinZikmund Jul 31, 2024
65e4aa6
chore: Continued porting of ComboBox logic
MartinZikmund Jul 31, 2024
c084425
feat: ComboBox.TextSubmitted
MartinZikmund Jul 31, 2024
99ecd2d
chore: Continued porting of ComboBox editability
MartinZikmund Aug 1, 2024
b4fd1f6
chore: Porting ComboBox pointer interactions
MartinZikmund Aug 1, 2024
5a54bae
chore: Significant progress in input handling
MartinZikmund Aug 1, 2024
e36647b
chore: Porting overlay code
MartinZikmund Aug 1, 2024
c2b5e8f
feat: ComboBox.IsEditable
MartinZikmund Aug 1, 2024
8353bb0
chore: Porting further relevant code
MartinZikmund Aug 2, 2024
ee6f209
chore: Properly display text for selected item
MartinZikmund Aug 2, 2024
1f3993e
feat: InputPropertyAttribute
MartinZikmund Aug 2, 2024
95eb8e6
chore: Open/Close logic starting
MartinZikmund Aug 2, 2024
f54f779
chore: Additional required dependencies
MartinZikmund Aug 2, 2024
0d90476
chore: Adjustment for keyboard handling
MartinZikmund Aug 5, 2024
11e0654
chore: FocusSelfOrChild on SelectorItem
MartinZikmund Aug 19, 2024
17c931d
chore: Keyboard handling and focus adjustments
MartinZikmund Aug 20, 2024
09a2656
chore: Selector improvements
MartinZikmund Aug 20, 2024
de7ef21
chore: Further cleanup
MartinZikmund Aug 20, 2024
554a1ad
chore: Adjustments
MartinZikmund Aug 20, 2024
63e6e53
chore: Start adding integration tests helper
MartinZikmund Aug 20, 2024
6f4feac
chore: ComboBoxHelper
MartinZikmund Aug 20, 2024
d03b957
test: Basic ComboBox tests
MartinZikmund Aug 20, 2024
39b1f06
feat: Support for ComboBox.SelectionChangedTrigger
MartinZikmund Aug 21, 2024
e35d630
chore: Adjustments to key handling
MartinZikmund Aug 21, 2024
de0e6ad
chore: Add many more integration tests (failing)
MartinZikmund Aug 21, 2024
3832485
chore: Tests now compile and run
MartinZikmund Aug 22, 2024
20f5ed3
chore: Remap Gamepad keys correctly
MartinZikmund Sep 24, 2024
e1b4bf7
chore: Adjust ComboBox tests
MartinZikmund Sep 24, 2024
c8d622b
fix: Track modifiers throughout key sequence
MartinZikmund Sep 24, 2024
0163e16
feat: Allow selecting custom values on Selector
MartinZikmund Sep 24, 2024
5068a06
fix: Adjust IsFocusable on Popup
MartinZikmund Sep 24, 2024
b3a9907
chore: Select ComboBox items using Space
MartinZikmund Sep 24, 2024
942fbff
chore: Adjust control init
MartinZikmund Sep 24, 2024
e1bcc54
chore: Avoid focusing twice
MartinZikmund Sep 24, 2024
5a55e06
chore: Adjust tests based on new focus logic
MartinZikmund Sep 25, 2024
cccb294
chore: Update last input device on keyboard input
MartinZikmund Sep 25, 2024
2a85fe2
chore: Adjust ComboBox.IsEditable to support ComboBoxItem instances
MartinZikmund Sep 25, 2024
1a7a436
chore: Update focused index on custom navigation
MartinZikmund Sep 25, 2024
86f8878
chore: Do not process search for "null" characters
MartinZikmund Sep 25, 2024
1e12cbb
chore: Update Last input type for gamepad
MartinZikmund Sep 25, 2024
2ce53a8
feat: Simulate preview key events for tests
MartinZikmund Sep 25, 2024
ffd79a1
chore: Simulate click on location
MartinZikmund Sep 25, 2024
2034bc0
chore: Tunneling events, fixes
MartinZikmund Sep 25, 2024
3146d4c
chore: Remove legacy key handling on ComboBoxItems
MartinZikmund Sep 25, 2024
cb5f0d9
fix: Make KeyboardHelper use async consistently
MartinZikmund Sep 26, 2024
91571a6
chore: Fix key sequences
MartinZikmund Sep 26, 2024
6cc1bbf
chore: Adjust KeyboardHelper usages to be awaited
MartinZikmund Sep 26, 2024
acb30ab
chore: Adjustments for CI
MartinZikmund Sep 26, 2024
d557f92
chore: CI adjustments
MartinZikmund Sep 26, 2024
bd66b8f
chore: Duplicate global
MartinZikmund Sep 26, 2024
c1d588c
chore: Never null
MartinZikmund Sep 27, 2024
d5727d8
chore: Adjust existing IsEditable tests
MartinZikmund Sep 27, 2024
5798d55
chore: Continue adjusting existing tests to pass with new logic
MartinZikmund Sep 27, 2024
3f1ac8f
fix: Uno specific workaround for invalid assert in ComboBox
MartinZikmund Sep 27, 2024
0bdc555
chore: Wrong ported value
MartinZikmund Sep 27, 2024
e0393d6
chore: Port OnSelectionChanged
MartinZikmund Sep 27, 2024
a724e50
chore: Disable popup arrangment test
MartinZikmund Sep 27, 2024
49c2d4e
chore: Adjust position of ComboBox popup for editable mode
MartinZikmund Sep 27, 2024
2faacc4
chore: Partially port Selector navigation handling
MartinZikmund Sep 27, 2024
a2ca190
chore: Selection handling on Selector for custom values
MartinZikmund Sep 27, 2024
ddb604d
fix: Correcly await async RunOnUIThread calls
MartinZikmund Sep 27, 2024
b38f860
chore: Disable additional test
MartinZikmund Sep 27, 2024
e8f12c6
chore: Adjustments for CI
MartinZikmund Sep 27, 2024
f092699
chore: Android CI
MartinZikmund Sep 27, 2024
86f3640
chore: Avoid measuring on invalid size
MartinZikmund Sep 27, 2024
a4146c0
chore: Avoid raising Preview key events on unsupported targets
MartinZikmund Sep 27, 2024
005ec29
chore: Update diffignore
MartinZikmund Sep 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions build/PackageDiffIgnore.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1914,6 +1914,17 @@

<Methods>
<Member fullName="System.Void Microsoft.Web.WebView2.Core.CoreWebView2.set_DocumentTitle(System.String value)" reason="Does not exist in UWP" />

<!-- BEGIN ComboBox.IsEditable -->
<Member fullName="System.Void Windows.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean forceFocus, Windows.UI.Xaml.FocusState focusState, System.Boolean animateIfBringIntoView)" reason="Does not exist in UWP" />
<Member fullName="System.Void Windows.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean forceFocus, Windows.UI.Xaml.FocusState focusState, System.Boolean animateIfBringIntoView, Windows.UI.Xaml.Input.FocusNavigationDirection focusNavigationDirection)" reason="Does not exist in UWP" />
<Member fullName="System.Void Windows.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean animateIfBringIntoView, Windows.UI.Xaml.Input.FocusNavigationDirection focusNavigationDirection)" reason="Does not exist in UWP" />
<Member fullName="System.Void Windows.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView)" reason="Does not exist in UWP" />
<Member fullName="System.Void Microsoft.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean forceFocus, Microsoft.UI.Xaml.FocusState focusState, System.Boolean animateIfBringIntoView)" reason="Does not exist in WinUI" />
<Member fullName="System.Void Microsoft.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean forceFocus, Microsoft.UI.Xaml.FocusState focusState, System.Boolean animateIfBringIntoView, Microsoft.UI.Xaml.Input.FocusNavigationDirection focusNavigationDirection)" reason="Does not exist in WinUI" />
<Member fullName="System.Void Microsoft.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean animateIfBringIntoView, Microsoft.UI.Xaml.Input.FocusNavigationDirection focusNavigationDirection)" reason="Does not exist in WinUI" />
<Member fullName="System.Void Microsoft.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView)" reason="Does not exist in WinUI" />
<!-- END ComboBox.IsEditable -->
</Methods>
</IgnoreSet>

Expand Down
187 changes: 187 additions & 0 deletions src/Uno.UI.RuntimeTests/IntegrationTests/common/ComboBoxHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Tests.Enterprise;
using Private.Infrastructure;
using Uno.UI.RuntimeTests.MUX.Helpers;
using Windows.Foundation;
using static Private.Infrastructure.TestServices;

namespace Microsoft.UI.Xaml.Tests.Common;

internal static class ComboBoxHelper
{
public enum OpenMethod
{
Mouse,
Touch,
Keyboard,
Gamepad,
Programmatic
};

public enum CloseMethod
{
Mouse,
Touch,
Keyboard,
Gamepad,
Programmatic
};

public static async Task FocusComboBoxIfNecessary(ComboBox comboBox)
{
var comboBoxGotFocusEvent = new Event();
var gotFocusRegistration = CreateSafeEventRegistration<ComboBox, RoutedEventHandler>("GotFocus");
gotFocusRegistration.Attach(comboBox, (s, e) =>
{
LOG_OUTPUT("[ComboBox]: Got Focus Event Fired.");
comboBoxGotFocusEvent.Set();
});

bool alreadyHasFocus = false;
await RunOnUIThread(() =>
{
alreadyHasFocus = comboBox.FocusState != FocusState.Unfocused;
comboBox.Focus(FocusState.Keyboard);
});

if (!alreadyHasFocus)
{
await comboBoxGotFocusEvent.WaitForDefault();
}
}

public static async Task OpenComboBox(ComboBox comboBox, OpenMethod openMethod)
{
var comboBoxOpenedEvent = new Event();
var openedRegistration = CreateSafeEventRegistration<ComboBox, EventHandler<object>>("DropDownOpened");
openedRegistration.Attach(comboBox, (s, e) => { comboBoxOpenedEvent.Set(); });

if (openMethod == OpenMethod.Mouse)
{
TestServices.InputHelper.LeftMouseClick(comboBox);
}
else if (openMethod == OpenMethod.Touch)
{
TestServices.InputHelper.Tap(comboBox);
}
else if (openMethod == OpenMethod.Keyboard)
{
await FocusComboBoxIfNecessary(comboBox);
await TestServices.KeyboardHelper.PressKeySequence(" ");
}
else if (openMethod == OpenMethod.Gamepad)
{
await FocusComboBoxIfNecessary(comboBox);
await CommonInputHelper.Accept(InputDevice.Gamepad);
}
else if (openMethod == OpenMethod.Programmatic)
{
await RunOnUIThread(() =>
{
comboBox.IsDropDownOpen = true;
});
}
await TestServices.WindowHelper.WaitForIdle();

await comboBoxOpenedEvent.WaitForDefault();
}

public static async Task CloseComboBox(ComboBox comboBox)
{
await CloseComboBox(comboBox, CloseMethod.Programmatic);
}

public static async Task CloseComboBox(ComboBox comboBox, CloseMethod closeMethod)
{
var dropDownClosedEvent = new Event();
var dropDownClosedRegistration = CreateSafeEventRegistration<ComboBox, EventHandler<object>>("DropDownClosed");
dropDownClosedRegistration.Attach(comboBox, (s, e) => { dropDownClosedEvent.Set(); });

if (closeMethod == CloseMethod.Touch || closeMethod == CloseMethod.Mouse)
{
Rect dropdownBounds = await GetBoundsOfOpenDropdown(comboBox);
int outsideBuffer = 10; // Tap at least this far away from the dropdown in order to ensure that it closes.
var closeTapPoint = new Point(dropdownBounds.X + dropdownBounds.Width + outsideBuffer, dropdownBounds.Y + dropdownBounds.Height + outsideBuffer);

if (closeMethod == CloseMethod.Touch)
{
TestServices.InputHelper.Tap(closeTapPoint);
}
else
{
TestServices.InputHelper.LeftMouseClick(closeTapPoint);
}
}
else if (closeMethod == CloseMethod.Keyboard)
{
await CommonInputHelper.Cancel(InputDevice.Keyboard);
}
else if (closeMethod == CloseMethod.Gamepad)
{
await CommonInputHelper.Cancel(InputDevice.Gamepad);
}
else if (closeMethod == CloseMethod.Programmatic)
{
await RunOnUIThread(() =>
{
comboBox.IsDropDownOpen = false;
});
}

await dropDownClosedEvent.WaitForDefault();
await TestServices.WindowHelper.WaitForIdle();

dropDownClosedRegistration.Detach();
}

private static async Task<Rect> GetBoundsOfOpenDropdown(DependencyObject element)
{
Rect dropdownBounds = new();

await RunOnUIThread(async () =>
{
var dropdownScrollViewer = (ScrollViewer)(TreeHelper.GetVisualChildByNameFromOpenPopups("ScrollViewer", element));
Assert.IsNotNull(dropdownScrollViewer, "DropDown not found.");
dropdownBounds = await ControlHelper.GetBounds(dropdownScrollViewer);

LOG_OUTPUT("dropdownBounds: (%f, %f, %f, %f)", dropdownBounds.X, dropdownBounds.Y, dropdownBounds.Width, dropdownBounds.Height);
});
await TestServices.WindowHelper.WaitForIdle();

return dropdownBounds;
}

// Verify the selected Index on the ComboBox.
public static async Task VerifySelectedIndex(ComboBox comboBox, int expected)
{
await SelectorHelper.VerifySelectedIndex(comboBox, expected);
}

// Uses touch to select the ComboBoxItem with the specified index.
// Note: The function does not currently scroll the popup, so it won't work if the item to be selected
// is not immediately visible.
public static async Task SelectItemWithTap(ComboBox comboBox, int index)
{
await OpenComboBox(comboBox, OpenMethod.Touch);
await TestServices.WindowHelper.WaitForIdle();

Event selectionChangedEvent = new();
var selectionChangedRegistration = CreateSafeEventRegistration<ComboBox, SelectionChangedEventHandler>("SelectionChanged");
selectionChangedRegistration.Attach(comboBox, (s, e) => selectionChangedEvent.Set());

ComboBoxItem comboBoxItemToSelect = null;
await RunOnUIThread(() =>
{
comboBoxItemToSelect = (ComboBoxItem)comboBox.ContainerFromIndex(index);
THROW_IF_NULL(comboBoxItemToSelect);
});

TestServices.InputHelper.Tap(comboBoxItemToSelect);
await selectionChangedEvent.WaitForDefault();
}
}
68 changes: 68 additions & 0 deletions src/Uno.UI.RuntimeTests/IntegrationTests/common/FocusTestHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Tests.Enterprise;
using static Private.Infrastructure.TestServices;

namespace Microsoft.UI.Xaml.Tests.Common;

internal static class FocusTestHelper
{
public static async Task EnsureFocus(UIElement element, FocusState focusState, int Attempts = 1)
{
bool gotFocus = false;

while (Attempts > 0 && !gotFocus)
{
Attempts--;

// On desktop, check if there is any other window currently having the window focus
// If so, try to bring the test app to foreground before attempting focus on the element
//if (!Private.Infrastructure.TestServices.Utilities.IsOneCore &&
// !Private.Infrastructure.TestServices.Utilities.IsXBox)
//{
// if (!Private.Infrastructure.TestServices.WindowHelper.IsFocusedWindow)
// {
// LOG_OUTPUT("Test app does not have window focus, bring it to foreground and try again!");
// Private.Infrastructure.TestServices.WindowHelper.RestoreForegroundWindow();
// Private.Infrastructure.TestServices.WindowHelper.WaitForIdle();
// }
//}

var gotFocusEvent = new Event();
// gotFocusEvent MUST be declared before gotFocusRegistration
// Otherwise the event handler could execute after gotFocusEvent has been destroyed.
var gotFocusRegistration = CreateSafeEventRegistration<UIElement, RoutedEventHandler>("GotFocus");

await RunOnUIThread(() =>
{
gotFocusRegistration.Attach(element, (s, e) =>

{
LOG_OUTPUT("Element has received focus.");
gotFocusEvent.Set();
});

if (element.FocusState != FocusState.Unfocused && FocusManager.GetFocusedElement(WindowHelper.WindowContent.XamlRoot).Equals(element))
{
// The element is already focused
LOG_OUTPUT("Focus was already set on this element");
gotFocusEvent.Set();
}
else
{
LOG_OUTPUT("Setting focus to the element...");
element.Focus(focusState);
}
});

await gotFocusEvent.WaitForNoThrow(4000);
gotFocus = gotFocusEvent.HasFired();
}

if (!gotFocus)
{
//Private.Infrastructure.TestServices.Utilities.CaptureScreen("FocusTestHelper");

Check warning on line 64 in src/Uno.UI.RuntimeTests/IntegrationTests/common/FocusTestHelper.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI.RuntimeTests/IntegrationTests/common/FocusTestHelper.cs#L64

Remove this commented out code.
}
VERIFY_IS_TRUE(gotFocus);
}
}
21 changes: 21 additions & 0 deletions src/Uno.UI.RuntimeTests/IntegrationTests/common/SelectorHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls.Primitives;
using static Private.Infrastructure.TestServices;

namespace Microsoft.UI.Xaml.Tests.Common;

internal static class SelectorHelper
{
public static async Task VerifySelectedIndex(Selector selector, int expected)
{
await RunOnUIThread(() =>
{
LOG_OUTPUT("Selected Index = %d", selector.SelectedIndex);
VERIFY_ARE_EQUAL(selector.SelectedIndex, expected);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,13 @@ public static void Tap(Point point)
#endif
}

public static void ScrollMouseWheel(CalendarView cv, int i)
public static void ScrollMouseWheel(UIElement cv, int i)
{
throw new System.NotImplementedException();
}

public static void LeftMouseClick(UIElement element) => Tap(element);
public static void LeftMouseClick(Point point) => Tap(point);

public static void PenBarrelTap(FrameworkElement pElement)
{
Expand Down
Loading
Loading