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

NumberBox: Forward information to the inner TextBox for better UIA experience #3775

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 51 additions & 1 deletion dev/NumberBox/APITests/NumberBoxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
using Microsoft.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using MUXControls.TestAppUtils;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Automation;

#if USING_TAEF
using WEX.TestExecution;
Expand Down Expand Up @@ -49,7 +52,7 @@ public void VerifyTextAlignmentPropogates()
[TestMethod]
public void VerifyNumberBoxCornerRadius()
{
if (PlatformConfiguration.IsOSVersionLessThan(OSVersion.Redstone5))
if (Common.PlatformConfiguration.IsOSVersionLessThan(Common.OSVersion.Redstone5))
marcelwgn marked this conversation as resolved.
Show resolved Hide resolved
{
Log.Warning("NumberBox CornerRadius property is not available pre-rs5");
return;
Expand Down Expand Up @@ -171,6 +174,53 @@ public void VerifyIsEnabledChangeUpdatesVisualState()
});
}

[TestMethod]
public void VerifyUIANameBehavior()
{
NumberBox numberBox = null;
TextBox textBox = null;

RunOnUIThread.Execute(() =>
{
numberBox = new NumberBox();
Content = numberBox;
Content.UpdateLayout();

textBox = TestPage.FindVisualChildrenByType<TextBox>(numberBox)[0];
Verify.IsNotNull(textBox);
numberBox.Header = "Some header";
});

IdleSynchronizer.Wait();

RunOnUIThread.Execute(() =>
{
VerifyUIAName("Some header");
numberBox.Header = new Button();
AutomationProperties.SetName(numberBox, "Some UIA name");
});

IdleSynchronizer.Wait();

RunOnUIThread.Execute(() =>
{
VerifyUIAName("Some UIA name");
numberBox.Header = new Button();
});

IdleSynchronizer.Wait();

RunOnUIThread.Execute(() =>
{
VerifyUIAName("Some UIA name");
});

void VerifyUIAName(string value)
{
Verify.AreEqual(value, FrameworkElementAutomationPeer.CreatePeerForElement(textBox).GetName());
}
}

private NumberBox SetupNumberBox()
{
NumberBox numberBox = null;
Expand Down
37 changes: 37 additions & 0 deletions dev/NumberBox/NumberBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ NumberBox::NumberBox()

SetDefaultStyleKey(this);
SetDefaultInputScope();

// We are not revoking this since the event and the listener reside on the same object and as such have the same lifecycle.
// That means that as soon as the NumberBox gets removed so will the event and the listener.
this->RegisterPropertyChangedCallback(winrt::AutomationProperties::NameProperty(), { this , &NumberBox::OnAutomationPropertiesNamePropertyChanged });
StephenLPeters marked this conversation as resolved.
Show resolved Hide resolved
}

void NumberBox::SetDefaultInputScope()
Expand Down Expand Up @@ -361,6 +365,32 @@ void NumberBox::OnIsEnabledChanged(const winrt::IInspectable&, const winrt::Depe
UpdateVisualStateForIsEnabledChange();
}

void NumberBox::OnAutomationPropertiesNamePropertyChanged(const winrt::DependencyObject&, const winrt::DependencyProperty&)
{
ReevaluateForwardedUIAName();
}

void NumberBox::ReevaluateForwardedUIAName()
{
if (const auto textBox = m_textBox.get())
{
const auto name = winrt::AutomationProperties::GetName(*this);
if (!name.empty())
{
// AutomationProperties.Name is a non empty string, we will use that value.
winrt::AutomationProperties::SetName(textBox, name);
}
else
{
if (const auto headerAsString = Header().try_as<winrt::IReference<winrt::hstring>>())
{
// Header is a string, we can use that as our UIA name.
winrt::AutomationProperties::SetName(textBox, headerAsString.Value());
}
}
}
}

void NumberBox::UpdateVisualStateForIsEnabledChange()
{
winrt::VisualStateManager::GoToState(*this, IsEnabled() ? L"Normal" : L"Disabled", false);
Expand Down Expand Up @@ -686,6 +716,11 @@ void NumberBox::UpdateHeaderPresenterState()
{
// Header is not a string, so let's show header presenter
shouldShowHeader = true;
// When our header isn't a string, we use the NumberBox's UIA name for the textbox's UIA name.
if (const auto textBox = m_textBox.get())
{
winrt::AutomationProperties::SetName(textBox, winrt::AutomationProperties::GetName(*this));
}
}
}
if(const auto headerTemplate = HeaderTemplate())
Expand All @@ -706,6 +741,8 @@ void NumberBox::UpdateHeaderPresenterState()
{
headerPresenter.Visibility(shouldShowHeader ? winrt::Visibility::Visible : winrt::Visibility::Collapsed);
}

ReevaluateForwardedUIAName();
}

void NumberBox::MoveCaretToTextEnd()
Expand Down
3 changes: 3 additions & 0 deletions dev/NumberBox/NumberBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ class NumberBox :
void OnNumberBoxScroll(winrt::IInspectable const& sender, winrt::PointerRoutedEventArgs const& args);
void OnCornerRadiusPropertyChanged(const winrt::DependencyObject&, const winrt::DependencyProperty&);
void OnIsEnabledChanged(const winrt::IInspectable&, const winrt::DependencyPropertyChangedEventArgs&);
void OnAutomationPropertiesNamePropertyChanged(const winrt::DependencyObject&, const winrt::DependencyProperty&);

void ReevaluateForwardedUIAName();

void ValidateInput();
void CoerceMinimum();
Expand Down