Skip to content

Commit

Permalink
[Windows] Respect `ClearButtonVisibility = ClearButtonVisibility.Neve…
Browse files Browse the repository at this point in the history
…r` for `<Entry>`s (#23158)

### Description of Change

The style interception code works sometimes but not always. 

Relevant XAML template in WinUI 3 is
[here](https://github.com/microsoft/microsoft-ui-xaml/blob/98a60c8f30c84f297a175dd2884d54ecd1c8a4a9/controls/dev/CommonStyles/TextBox_themeresources.xaml#L311-L341).

### Issues Fixed

Fixes #23112
Related to #13714

PR that introduced the modified code: #3444
  • Loading branch information
mattleibow committed Jul 10, 2024
2 parents f323832 + 67b3766 commit 79f4c50
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 50 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue23158.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?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"
xmlns:local="clr-namespace:Maui.Controls.Sample.Issues"
x:Class="Maui.Controls.Sample.Issues.Issue23158"
x:DataType="local:Issue23158">

<VerticalStackLayout Padding="30,0">
<Button AutomationId="AddEntry" Text="Add entry dynamically" Margin="0,0,0,30" Clicked="AddEntryButton_Clicked"/>

<!-- First entry. -->
<Label Text="Entry.ClearButtonVisibility=Never"/>
<Entry x:Name="Entry1" ClearButtonVisibility="Never" Text="A"/>

<!-- Second entry. -->
<Label Text="Entry.ClearButtonVisibility=WhileEditing"/>
<Entry x:Name="Entry2" ClearButtonVisibility="WhileEditing" Text="B"/>

<!-- Third entry. See #23112. -->
<Label Text="Dynamically Entry.ClearButtonVisibility=Never + Focus"/>
<VerticalStackLayout x:Name="Entry3Container"/> <!-- Container for a dynamically added entry. -->

<Label AutomationId="TestInstructions" Margin="0,30,0,0" FontAttributes="Bold"
Text="Instructions: Click the top-most button and the third entry should not contain any clear button!"/>
</VerticalStackLayout>
</ContentPage>
26 changes: 26 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue23158.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Maui.Controls.Sample.Issues;

[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 23158, "Respect Entry.ClearButtonVisibility on Windows", PlatformAffected.UWP)]
public partial class Issue23158 : ContentPage
{
public Issue23158()
{
InitializeComponent();
}

private void AddEntryButton_Clicked(object sender, EventArgs e)
{
Entry entry = new Entry()
{
Text = "Some Text",
AutomationId = "Entry3"
};

Entry3Container.Children.Add(entry);

// Intentionally, after the entry is added to its layout container.
entry.ClearButtonVisibility = ClearButtonVisibility.Never;
entry.Focus();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue23158 : _IssuesUITest
{
public override string Issue => "Respect Entry.ClearButtonVisibility on Windows";

public Issue23158(TestDevice device) : base(device)
{
}

#if !MACCATALYST
[Test]
[Category(UITestCategories.Entry)]
public void ValidateEntryClearButtonVisibilityBehavior()
{
App.WaitForElement("TestInstructions");

// Click the button to add dynamically Entry3.
App.Click("AddEntry");

// Click the new entry to see if there is the clear button or not. No such button should be present.
App.Tap("Entry3");

VerifyScreenshot();
}
#endif
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 19 additions & 50 deletions src/Core/src/Platform/Windows/MauiTextBox.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#nullable enable
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;

namespace Microsoft.Maui.Platform
{
Expand All @@ -9,8 +9,6 @@ public static class MauiTextBox
const string ContentElementName = "ContentElement";
const string PlaceholderTextContentPresenterName = "PlaceholderTextContentPresenter";
const string DeleteButtonElementName = "DeleteButton";
const string ButtonStatesName = "ButtonStates";
const string ButtonVisibleStateName = "ButtonVisible";

public static void InvalidateAttachedProperties(DependencyObject obj)
{
Expand Down Expand Up @@ -39,15 +37,17 @@ static void OnVerticalTextAlignmentPropertyChanged(DependencyObject d, Dependenc

var scrollViewer = element?.GetDescendantByName<ScrollViewer>(ContentElementName);
if (scrollViewer is not null)
{
scrollViewer.VerticalAlignment = verticalAlignment;
}

var placeholder = element?.GetDescendantByName<TextBlock>(PlaceholderTextContentPresenterName);
if (placeholder is not null)
{
placeholder.VerticalAlignment = verticalAlignment;
}
}

// IsDeleteButtonEnabled

public static bool GetIsDeleteButtonEnabled(DependencyObject obj) =>
(bool)obj.GetValue(IsDeleteButtonEnabledProperty);

Expand All @@ -60,56 +60,25 @@ static void OnVerticalTextAlignmentPropertyChanged(DependencyObject d, Dependenc

static void OnIsDeleteButtonEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs? e = null)
{
// TODO: cache the buttonStates and buttonVisibleState values on the textBox

var element = d as FrameworkElement;

VisualStateGroup? buttonStates = null;
VisualState? buttonVisibleState = null;
var deleteButton = element?.GetDescendantByName<Button>(DeleteButtonElementName);
if (deleteButton?.Parent is Grid rootGrid)
(buttonStates, buttonVisibleState) = InterceptDeleteButtonVisualStates(rootGrid);

var states = buttonStates?.States;
if (element is not null && states is not null && buttonVisibleState is not null)
if (d is not FrameworkElement element)
{
var isEnabled = GetIsDeleteButtonEnabled(element);
var contains = states.Contains(buttonVisibleState);
if (isEnabled && !contains)
states.Add(buttonVisibleState);
else if (!isEnabled && contains)
states.Remove(buttonVisibleState);
return;
}
}

static (VisualStateGroup? Group, VisualState? State) InterceptDeleteButtonVisualStates(FrameworkElement? element)
{
// not the content we expected
if (element is null)
return (null, null);

// find "ButtonStates"
var visualStateGroups = VisualStateManager.GetVisualStateGroups(element);
VisualStateGroup? buttonStates = null;
foreach (var group in visualStateGroups)
{
if (group.Name == ButtonStatesName)
buttonStates = group;
}

// no button states
if (buttonStates is null)
return (null, null);
Button? deleteButton = element.GetDescendantByName<Button>(DeleteButtonElementName);

// find and return the "ButtonVisible" state
foreach (var state in buttonStates.States)
if (deleteButton is not null)
{
if (state.Name == ButtonVisibleStateName)
return (buttonStates, state);
if (GetIsDeleteButtonEnabled(element))
{
deleteButton.RenderTransform = null;
}
else
{
// This is a workaround to move the button to be effectively invisible. It is not perfect.
deleteButton.RenderTransform = new TranslateTransform() { X = -int.MaxValue, Y = -int.MaxValue };
}
}

// no button visible state
return (null, null);
}
}
}

0 comments on commit 79f4c50

Please sign in to comment.