Skip to content
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
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();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

macOS won't generate screen shots so I would just ignore this test on macOS

Do we need the 500 ms delays?

VerifyScreenshot already does a delay and retry

@MartyIX MartyIX Jul 4, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that the delay before VerifyScreenshot() is necessary I just wanted to be sure.

The previous delay, I'm not sure. It's very hard for me to tell. I just find it safer with the delay. I guess you are concerned with the CI test time, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that I have a limited set of tries, I play it safe. :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I attempted to change it in 67b3766.

Unfortunately, for some reason I'm seeing locally

Severity	Code	Description	Project	File	Line	Suppression State
Error (active)	NETSDK1047	Assets file 'D:\maui\artifacts\obj\Controls.TestCases.HostApp\project.assets.json' doesn't have a target for 'net8.0-ios/ios-arm64'. Ensure that restore has run and that you have included 'net8.0-ios' in the TargetFrameworks for your project. You may also need to include 'ios-arm64' in your project's RuntimeIdentifiers.	Controls.TestCases.HostApp (net8.0-ios)	C:\Program Files\dotnet\sdk\8.0.302\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets	266	

So I can't run the test locally.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
#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 @@ public static void SetIsDeleteButtonEnabled(DependencyObject obj, bool value) =>

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;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like this is a workaround as well. So if the approach of this PR is ok, then it would be a replacement of a workaround for a workaround.

}

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);
}
}
}