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
7 changes: 6 additions & 1 deletion src/Controls/src/Core/SwipeView/SwipeItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(SwipeItem), null);

/// <summary>Bindable property for <see cref="IsVisible"/>.</summary>
public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true);
public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwipeItem), true, propertyChanged: OnIsVisibleChanged);

/// <summary>
/// Gets or sets the background color of the swipe item. This is a bindable property.
Expand All @@ -36,11 +36,16 @@

public event EventHandler<EventArgs> Invoked;

static void OnIsVisibleChanged(BindableObject bindable, object oldValue, object newValue)
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
{
var swipeItem = (SwipeItem)bindable;
swipeItem.Handler?.UpdateValue(nameof(ISwipeItemMenuItem.Visibility));
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
}
Paint ISwipeItemMenuItem.Background => new SolidPaint(BackgroundColor);

Visibility ISwipeItemMenuItem.Visibility => this.IsVisible ? Visibility.Visible : Visibility.Collapsed;

void Maui.ISwipeItem.OnInvoked()

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Debug))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Debug))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Debug))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Debug))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Debug))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Pack .NET MAUI Pack macOS)

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Pack .NET MAUI Pack macOS)

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Pack .NET MAUI Pack macOS)

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Pack .NET MAUI Pack macOS)

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types

Check failure on line 48 in src/Controls/src/Core/SwipeView/SwipeItem.cs

View check run for this annotation

Azure Pipelines / maui-pr (Pack .NET MAUI Pack macOS)

src/Controls/src/Core/SwipeView/SwipeItem.cs#L48

src/Controls/src/Core/SwipeView/SwipeItem.cs(48,15): error CS0111: (NETCORE_ENGINEERING_TELEMETRY=Build) Type 'SwipeItem' already defines a member called 'OnIsVisibleChanged' with the same parameter types
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
Expand Down
100 changes: 100 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue35216.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 35216, "SwipeItem IsVisible should properly refresh native swipe items when binding value changes dynamically", PlatformAffected.UWP)]
public class Issue35216 : ContentPage
{
readonly Issue35216ViewModel _viewModel = new() { IsDeleteVisible = false };
SwipeView _swipeView;

public Issue35216()
{
BindingContext = _viewModel;

SwipeItem deleteSwipeItem = new SwipeItem
{
Text = "Delete",
BackgroundColor = Colors.Green,
AutomationId = "DeleteSwipeItem"
};
deleteSwipeItem.SetBinding(SwipeItem.IsVisibleProperty, new Binding(nameof(Issue35216ViewModel.IsDeleteVisible)));

SwipeItem archiveSwipeItem = new SwipeItem
{
Text = "Archive",
BackgroundColor = Colors.Blue,
AutomationId = "ArchiveSwipeItem"
};

_swipeView = new SwipeView
{
HeightRequest = 60,
LeftItems = new SwipeItems { deleteSwipeItem, archiveSwipeItem },
Content = new Grid
{
BackgroundColor = Colors.LightGray,
Children =
{
new Label
{
AutomationId = "SwipeContent",
Text = "Swipe right to reveal items",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}
}
}
};

Button toggleButton = new Button
{
Text = "Toggle Delete Visibility",
AutomationId = "ToggleVisibilityButton"
};
toggleButton.Clicked += (s, e) => _viewModel.IsDeleteVisible = !_viewModel.IsDeleteVisible;

Button resetButton = new Button
{
Text = "Reset",
AutomationId = "ResetButton"
};
resetButton.Clicked += (s, e) => _viewModel.IsDeleteVisible = false;

Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 20,
Children =
{
_swipeView,
toggleButton,
resetButton,
}
};
}
}

public class Issue35216ViewModel : INotifyPropertyChanged
{
bool _isDeleteVisible;

public bool IsDeleteVisible
{
get => _isDeleteVisible;
set
{
if (_isDeleteVisible != value)
{
_isDeleteVisible = value;
OnPropertyChanged();
}
}
}

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged([CallerMemberName] string name = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#if WINDOWS // Existing PR for iOS & Android: https://github.com/dotnet/maui/pull/35217
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue35216 : _IssuesUITest
{
public override string Issue => "SwipeItem IsVisible should properly refresh native swipe items when binding value changes dynamically";

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

[Test]
[Order(1)]
[Category(UITestCategories.SwipeView)]
public void Issue35216SwipeItemInitiallyHiddenBecomesVisibleAfterBindingChanges()
{
Exception? exception = null;
var rect = App.WaitForElement("SwipeContent").GetRect();
var centerX = rect.X + rect.Width / 2;
var centerY = rect.Y + rect.Height / 2;

App.DragCoordinates(centerX, centerY, centerX + 200, centerY);

VerifyScreenshotOrSetException(ref exception, "Issue35216SwipeOpen_InitiallyHidden",
retryTimeout: TimeSpan.FromSeconds(2), tolerance: 1.0);

App.Tap("ToggleVisibilityButton");

App.DragCoordinates(centerX, centerY, centerX + 200, centerY);
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
VerifyScreenshotOrSetException(ref exception, "Issue35216SwipeOpen_BecomeVisible",
retryTimeout: TimeSpan.FromSeconds(2), tolerance: 1.0);

App.Tap("ResetButton");

if (exception is not null)
{
throw exception;
}
}

[Test]
[Order(2)]
[Category(UITestCategories.SwipeView)]
public void Issue35216SwipeItemBecomesHiddenAfterBindingChanges()
{
Exception? exception = null;
var rect = App.WaitForElement("SwipeContent").GetRect();
var centerX = rect.X + rect.Width / 2;
var centerY = rect.Y + rect.Height / 2;

App.Tap("ToggleVisibilityButton");
App.DragCoordinates(centerX, centerY, centerX + 200, centerY);

VerifyScreenshotOrSetException(ref exception, "Issue35216SwipeOpen_DeleteVisible",
retryTimeout: TimeSpan.FromSeconds(2), tolerance: 1.0);

App.Tap("ToggleVisibilityButton");

App.DragCoordinates(centerX, centerY, centerX + 200, centerY);
VerifyScreenshotOrSetException(ref exception, "Issue35216SwipeOpen_DeleteHidden",
retryTimeout: TimeSpan.FromSeconds(2), tolerance: 1.0);

App.Tap("ResetButton");

if (exception is not null)
{
throw exception;
}
}
}
#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.
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.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,48 @@ public static void MapText(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem
public static void MapBackground(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view) =>
handler.PlatformView.UpdateBackground(view.Background);

public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view) { }
public static void MapVisibility(ISwipeItemMenuItemHandler handler, ISwipeItemMenuItem view)
{
// WinUI SwipeItem does not support a Visibility property, so we need to
// rebuild the parent SwipeView's swipe items to reflect the visibility change.
var swipeView = GetParentSwipeView(view);
if (swipeView?.Handler is ISwipeViewHandler swipeViewHandler)
{
if (swipeView.LeftItems?.Contains(view) == true)
{
swipeViewHandler.UpdateValue(nameof(ISwipeView.LeftItems));
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
}
else if (swipeView.RightItems?.Contains(view) == true)
{
swipeViewHandler.UpdateValue(nameof(ISwipeView.RightItems));
}
else if (swipeView.TopItems?.Contains(view) == true)
{
swipeViewHandler.UpdateValue(nameof(ISwipeView.TopItems));
}
else if (swipeView.BottomItems?.Contains(view) == true)
{
swipeViewHandler.UpdateValue(nameof(ISwipeView.BottomItems));
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
}
}
}

// Walk up the virtual view parent chain to find the owning ISwipeView.
// This is more robust than assuming a fixed depth (Parent?.Parent) which
// can silently fail if parenting is in transition.
static ISwipeView? GetParentSwipeView(IElement? element)
{
var parent = element?.Parent;
while (parent is not null)
{
if (parent is ISwipeView swipeView)
{
return swipeView;
}
parent = parent.Parent;
}
return null;
}

protected override void ConnectHandler(WSwipeItem platformView)
{
Expand Down
60 changes: 55 additions & 5 deletions src/Core/src/Handlers/SwipeView/SwipeViewHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ namespace Microsoft.Maui.Handlers
{
public partial class SwipeViewHandler : ViewHandler<ISwipeView, WSwipeControl>
{
// Guard flag to prevent re-entrancy when CreateSwipeItems calls item.ToHandler(),
// which triggers MapVisibility, which calls UpdateValue back into MapLeftItems/MapRightItems.
bool _isRebuildingSwipeItems;
protected override WSwipeControl CreatePlatformView() => new();

public override void SetVirtualView(IView view)
Expand Down Expand Up @@ -83,11 +86,21 @@ public static void MapLeftItems(ISwipeViewHandler handler, ISwipeView view)
if (!handler.PlatformView.IsLoaded)
return;

if (handler is SwipeViewHandler { _isRebuildingSwipeItems: true })
{
return;
}

UpdateSwipeItems(SwipeDirection.Left, handler, view, (items) => handler.PlatformView.LeftItems = items, view.LeftItems, handler.PlatformView.LeftItems);
}

public static void MapTopItems(ISwipeViewHandler handler, ISwipeView view)
{
if (handler is SwipeViewHandler { _isRebuildingSwipeItems: true })
{
return;
}

UpdateSwipeItems(SwipeDirection.Up, handler, view, (items) => handler.PlatformView.TopItems = items, view.TopItems, handler.PlatformView.TopItems);
}

Expand All @@ -96,11 +109,21 @@ public static void MapRightItems(ISwipeViewHandler handler, ISwipeView view)
if (!handler.PlatformView.IsLoaded)
return;

if (handler is SwipeViewHandler { _isRebuildingSwipeItems: true })
{
return;
}

UpdateSwipeItems(SwipeDirection.Right, handler, view, (items) => handler.PlatformView.RightItems = items, view.RightItems, handler.PlatformView.RightItems);
}

public static void MapBottomItems(ISwipeViewHandler handler, ISwipeView view)
{
if (handler is SwipeViewHandler { _isRebuildingSwipeItems: true })
{
return;
}

UpdateSwipeItems(SwipeDirection.Down, handler, view, (items) => handler.PlatformView.BottomItems = items, view.BottomItems, handler.PlatformView.BottomItems);
}

Expand Down Expand Up @@ -178,13 +201,40 @@ static WSwipeItems CreateSwipeItems(SwipeDirection swipeDirection, ISwipeViewHan

swipeItems.Mode = items.Mode.ToPlatform();

foreach (var item in items)
// Set the re-entrancy guard before calling ToHandler() on each item.
// ToHandler() triggers initial property mapping, which calls MapVisibility,
// which calls UpdateValue back into MapLeftItems/MapRightItems — causing N
// redundant rebuilds. The guard prevents those nested calls from re-entering.
if (handler is SwipeViewHandler concreteHandler)
{
if (CanAddSwipeItems(swipeItems) && item is ISwipeItemMenuItem &&
item.ToHandler(handler.MauiContext!).PlatformView is WSwipeItem swipeItem)
concreteHandler._isRebuildingSwipeItems = true;
}

try
{
foreach (var item in items)
{
swipeItem.BehaviorOnInvoked = items.SwipeBehaviorOnInvoked.ToPlatform();
swipeItems.Add(swipeItem);
if (item is ISwipeItemMenuItem menuItem)
{
// Always create the handler regardless of visibility so that subsequent
// visibility changes can propagate via the handler's UpdateValue mechanism.
var itemElementHandler = item.ToHandler(handler.MauiContext!);

if (CanAddSwipeItems(swipeItems) &&
menuItem.Visibility != Visibility.Collapsed &&
itemElementHandler.PlatformView is WSwipeItem swipeItem)
{
swipeItem.BehaviorOnInvoked = items.SwipeBehaviorOnInvoked.ToPlatform();
swipeItems.Add(swipeItem);
}
}
}
}
finally
{
if (handler is SwipeViewHandler concreteHandler2)
{
concreteHandler2._isRebuildingSwipeItems = false;
}
}

Expand Down
Loading