Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,31 @@ await InvokeOnMainThreadAsync(async () =>
});
}

[Fact]
[Description("Button pressed state should be cleared when pointer exits bounds")]
public async Task ButtonPressedStateClearedOnPointerExit()
{
var button = new Microsoft.Maui.Controls.Button
{
Text = "Test Button"
};

var handler = await CreateHandlerAsync<ButtonHandler>(button);
await InvokeOnMainThreadAsync(() =>
{
// Manually trigger pressed state
button.SendPressed();

// Check that button is in pressed state
Assert.True(button.IsPressed);

// Manually trigger released state (simulating what happens on pointer exit)
button.SendReleased();

// Check that button is no longer in pressed state
Assert.False(button.IsPressed);
});
}

}
}
72 changes: 72 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue25.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?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"
x:Class="Maui.Controls.Sample.Issues.Issue25"
Title="Issue 25 - Button Pressed State">
<ScrollView>
<StackLayout Padding="20" Spacing="20">
<Label Text="Test for Issue #25: Button and Border Controls Remain in Pressed State on Windows Touch Screens"
FontSize="16"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"/>

<Label Text="Instructions:"
FontAttributes="Bold"
FontSize="14"/>

<Label Text="1. Touch and hold a button below
2. While holding, drag your finger outside the button area
3. Release your finger outside the button
4. The button should NOT remain in pressed state"
FontSize="12"/>

<Label Text="Test Buttons:"
FontAttributes="Bold"
FontSize="14"
Margin="0,20,0,10"/>

<Button x:Name="TestButton1"
Text="Test Button 1"
AutomationId="TestButton1"
BackgroundColor="LightBlue"
Pressed="OnButtonPressed"
Released="OnButtonReleased"
Clicked="OnButtonClicked"/>

<Button x:Name="TestButton2"
Text="Test Button 2"
AutomationId="TestButton2"
BackgroundColor="LightGreen"
Pressed="OnButtonPressed"
Released="OnButtonReleased"
Clicked="OnButtonClicked"/>

<Border x:Name="TestBorder1"
BackgroundColor="LightCoral"
Stroke="DarkRed"
StrokeThickness="2"
HeightRequest="50"
Margin="0,10,0,0">
<Border.GestureRecognizers>
<TapGestureRecognizer Tapped="OnBorderTapped" />
</Border.GestureRecognizers>
<Label Text="Test Border (Tap Me)"
HorizontalOptions="Center"
VerticalOptions="Center"
AutomationId="TestBorderLabel"/>
</Border>

<Label Text="Status:"
FontAttributes="Bold"
FontSize="14"
Margin="0,20,0,0"/>

<Label x:Name="StatusLabel"
Text="Ready for testing..."
AutomationId="StatusLabel"
FontSize="12"
TextColor="Blue"/>

</StackLayout>
</ScrollView>
</ContentPage>
50 changes: 50 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue25.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.CustomAttributes;
using Microsoft.Maui.Controls.Xaml;

namespace Maui.Controls.Sample.Issues
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 25, "Button and Border Controls Remain in Pressed State on Windows Touch Screens",
PlatformAffected.UWP)]
public partial class Issue25 : ContentPage
{
public Issue25()
{
InitializeComponent();
}

private void OnButtonPressed(object sender, System.EventArgs e)
{
if (sender is Button button)
{
StatusLabel.Text = $"{button.Text} pressed at {System.DateTime.Now:HH:mm:ss}";
StatusLabel.TextColor = Colors.Red;
}
}

private void OnButtonReleased(object sender, System.EventArgs e)
{
if (sender is Button button)
{
StatusLabel.Text = $"{button.Text} released at {System.DateTime.Now:HH:mm:ss}";
StatusLabel.TextColor = Colors.Green;
}
}

private void OnButtonClicked(object sender, System.EventArgs e)
{
if (sender is Button button)
{
StatusLabel.Text = $"{button.Text} clicked at {System.DateTime.Now:HH:mm:ss}";
StatusLabel.TextColor = Colors.Blue;
}
}

private void OnBorderTapped(object sender, System.EventArgs e)
{
StatusLabel.Text = $"Border tapped at {System.DateTime.Now:HH:mm:ss}";
StatusLabel.TextColor = Colors.Purple;
}
}
}
30 changes: 30 additions & 0 deletions src/Core/src/Handlers/Button/ButtonHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public partial class ButtonHandler : ViewHandler<IButton, Button>
{
PointerEventHandler? _pointerPressedHandler;
PointerEventHandler? _pointerReleasedHandler;
PointerEventHandler? _pointerExitedHandler;
PointerEventHandler? _pointerCanceledHandler;
bool _isPressed;

protected override Button CreatePlatformView() => new MauiButton();
Expand All @@ -17,11 +19,15 @@ protected override void ConnectHandler(Button platformView)
{
_pointerPressedHandler = new PointerEventHandler(OnPointerPressed);
_pointerReleasedHandler = new PointerEventHandler(OnPointerReleased);
_pointerExitedHandler = new PointerEventHandler(OnPointerExited);
_pointerCanceledHandler = new PointerEventHandler(OnPointerCanceled);

platformView.Click += OnClick;
platformView.Unloaded += OnUnloaded;
platformView.AddHandler(UIElement.PointerPressedEvent, _pointerPressedHandler, true);
platformView.AddHandler(UIElement.PointerReleasedEvent, _pointerReleasedHandler, true);
platformView.AddHandler(UIElement.PointerExitedEvent, _pointerExitedHandler, true);
platformView.AddHandler(UIElement.PointerCanceledEvent, _pointerCanceledHandler, true);

base.ConnectHandler(platformView);
}
Expand All @@ -33,9 +39,13 @@ protected override void DisconnectHandler(Button platformView)
platformView.Unloaded -= OnUnloaded;
platformView.RemoveHandler(UIElement.PointerPressedEvent, _pointerPressedHandler);
platformView.RemoveHandler(UIElement.PointerReleasedEvent, _pointerReleasedHandler);
platformView.RemoveHandler(UIElement.PointerExitedEvent, _pointerExitedHandler);
platformView.RemoveHandler(UIElement.PointerCanceledEvent, _pointerCanceledHandler);

_pointerPressedHandler = null;
_pointerReleasedHandler = null;
_pointerExitedHandler = null;
_pointerCanceledHandler = null;

base.DisconnectHandler(platformView);
}
Expand Down Expand Up @@ -111,6 +121,26 @@ void OnPointerReleased(object sender, PointerRoutedEventArgs e)
VirtualView?.Released();
}

void OnPointerExited(object sender, PointerRoutedEventArgs e)
{
// Clear pressed state when pointer exits the button bounds
if (_isPressed)
{
_isPressed = false;
VirtualView?.Released();
}
}

void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
// Clear pressed state when pointer is canceled
if (_isPressed)
{
_isPressed = false;
VirtualView?.Released();
}
}

void OnUnloaded(object sender, RoutedEventArgs e)
{
// WinUI will not raise the PointerReleased event if the pointer is pressed and then unloaded
Expand Down