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
5 changes: 4 additions & 1 deletion src/Controls/src/Core/Handlers/Items/ItemsViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ public ItemsViewHandler(PropertyMapper mapper = null) : base(mapper ?? ItemsView
[Controls.ItemsView.EmptyViewTemplateProperty.PropertyName] = MapEmptyViewTemplate,
[Controls.ItemsView.FlowDirectionProperty.PropertyName] = MapFlowDirection,
[Controls.ItemsView.IsVisibleProperty.PropertyName] = MapIsVisible,
[Controls.ItemsView.ItemsUpdatingScrollModeProperty.PropertyName] = MapItemsUpdatingScrollMode
[Controls.ItemsView.ItemsUpdatingScrollModeProperty.PropertyName] = MapItemsUpdatingScrollMode,
#if IOS
[nameof(IView.IsEnabled)] = MapIsEnabled,
#endif
};
}
}
5 changes: 5 additions & 0 deletions src/Controls/src/Core/Handlers/Items/ItemsViewHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ protected virtual void UpdateLayout()
Controller?.UpdateLayout(_layout);
}

internal static void MapIsEnabled(ItemsViewHandler<TItemsView> handler, ItemsView itemsView)
{
(handler.Controller as SelectableItemsViewController<ReorderableItemsView>)?.UpdateSelectionMode();
}

protected virtual void ScrollToRequested(object sender, ScrollToRequestEventArgs args)
{
using (var indexPath = DetermineIndex(args))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ protected override UICollectionViewDelegateFlowLayout CreateDelegator()
// _Only_ called if the user initiates the selection change; will not be called for programmatic selection
public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath)
{
if (ItemsView?.ItemsSource is null)
if (ItemsView?.ItemsSource is null || !ItemsView.IsExplicitlyEnabled)
{
return;
}
Expand All @@ -35,7 +35,7 @@ public override void ItemSelected(UICollectionView collectionView, NSIndexPath i
// _Only_ called if the user initiates the selection change; will not be called for programmatic selection
public override void ItemDeselected(UICollectionView collectionView, NSIndexPath indexPath)
{
if (ItemsView?.ItemsSource is null)
if (ItemsView?.ItemsSource is null || !ItemsView.IsExplicitlyEnabled)
{
return;
}
Expand Down Expand Up @@ -185,6 +185,7 @@ internal void UpdatePlatformSelection()
internal void UpdateSelectionMode()
{
var mode = ItemsView.SelectionMode;
var isEnabled = ItemsView.IsExplicitlyEnabled;

switch (mode)
{
Expand All @@ -194,13 +195,13 @@ internal void UpdateSelectionMode()
ClearsSelectionOnViewWillAppear = true;
break;
case SelectionMode.Single:
CollectionView.AllowsSelection = true;
CollectionView.AllowsSelection = isEnabled;
CollectionView.AllowsMultipleSelection = false;
ClearsSelectionOnViewWillAppear = false;
break;
case SelectionMode.Multiple:
CollectionView.AllowsSelection = true;
CollectionView.AllowsMultipleSelection = true;
CollectionView.AllowsSelection = isEnabled;
CollectionView.AllowsMultipleSelection = isEnabled;
ClearsSelectionOnViewWillAppear = false;
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public ItemsViewHandler2(PropertyMapper mapper = null) : base(mapper ?? ItemsVie
[Controls.ItemsView.EmptyViewTemplateProperty.PropertyName] = MapEmptyViewTemplate,
[Controls.ItemsView.FlowDirectionProperty.PropertyName] = MapFlowDirection,
[Controls.ItemsView.IsVisibleProperty.PropertyName] = MapIsVisible,
[Controls.ItemsView.ItemsUpdatingScrollModeProperty.PropertyName] = MapItemsUpdatingScrollMode
[Controls.ItemsView.ItemsUpdatingScrollModeProperty.PropertyName] = MapItemsUpdatingScrollMode,
[nameof(IView.IsEnabled)] = MapIsEnabled
};

UICollectionViewLayout _layout;
Expand Down Expand Up @@ -121,6 +122,11 @@ public static void MapIsVisible(ItemsViewHandler2<TItemsView> handler, ItemsView
handler.Controller?.UpdateVisibility();
}

internal static void MapIsEnabled(ItemsViewHandler2<TItemsView> handler, ItemsView itemsView)
{
(handler.Controller as SelectableItemsViewController2<ReorderableItemsView>)?.UpdateSelectionMode();
}

public static void MapItemsUpdatingScrollMode(ItemsViewHandler2<TItemsView> handler, ItemsView itemsView)
{
if (handler.ItemsView is StructuredItemsView structuredItemsView && structuredItemsView.ItemsLayout is ItemsLayout itemsLayout)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ protected override UICollectionViewDelegateFlowLayout CreateDelegator()
// _Only_ called if the user initiates the selection change; will not be called for programmatic selection
public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath)
{
if (ItemsView?.ItemsSource is null)
if (ItemsView?.ItemsSource is null || !ItemsView.IsExplicitlyEnabled)
{
return;
}
Expand All @@ -36,7 +36,7 @@ public override void ItemSelected(UICollectionView collectionView, NSIndexPath i
// _Only_ called if the user initiates the selection change; will not be called for programmatic selection
public override void ItemDeselected(UICollectionView collectionView, NSIndexPath indexPath)
{
if (ItemsView?.ItemsSource is null)
if (ItemsView?.ItemsSource is null || !ItemsView.IsExplicitlyEnabled)
{
return;
}
Expand Down Expand Up @@ -186,6 +186,7 @@ internal void UpdatePlatformSelection()
internal void UpdateSelectionMode()
{
var mode = ItemsView.SelectionMode;
var isEnabled = ItemsView.IsExplicitlyEnabled;

switch (mode)
{
Expand All @@ -195,13 +196,13 @@ internal void UpdateSelectionMode()
ClearsSelectionOnViewWillAppear = true;
break;
case SelectionMode.Single:
CollectionView.AllowsSelection = true;
CollectionView.AllowsSelection = isEnabled;
CollectionView.AllowsMultipleSelection = false;
ClearsSelectionOnViewWillAppear = false;
break;
case SelectionMode.Multiple:
CollectionView.AllowsSelection = true;
CollectionView.AllowsMultipleSelection = true;
CollectionView.AllowsSelection = isEnabled;
CollectionView.AllowsMultipleSelection = isEnabled;
ClearsSelectionOnViewWillAppear = false;
break;
}
Expand Down
27 changes: 27 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue20274.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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.Issue20274"
xmlns:local="clr-namespace:Maui.Controls.Sample.Issues">

<Grid RowDefinitions="auto,auto, *">
<Button Text="Change State" AutomationId="Button" Clicked="Button_Clicked"/>
<Label Grid.Row="1" AutomationId="Label" HeightRequest="50" VerticalTextAlignment="Center" x:Name="label"/>
<CollectionView
Grid.Row="2" IsEnabled="False"
SelectionMode="Single" x:Name="collectionView" SelectionChanged="collectionView_SelectionChanged"
ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Margin="10"
Padding="10"
AutomationId="ItemsLabel"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Start"
Text="{Binding Name}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>

</ContentPage>
61 changes: 61 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue20274.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 20274, "IsEnabled=False on CollectionView not working", PlatformAffected.All)]
public partial class Issue20274 : ContentPage
{
public ObservableCollection<CollectionViewItem> Items { get; set; }

public Issue20274()
Comment on lines +6 to +11

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

The test files are using issue number 20274 in both the file names and code, but the PR description indicates this PR fixes issue #27770. The issue numbers should match. Consider renaming the test files to Issue27770.cs, Issue27770.xaml, and Issue27770.xaml.cs, and updating the [Issue] attribute to reference the correct issue number 27770.

Suggested change
[Issue(IssueTracker.Github, 20274, "IsEnabled=False on CollectionView not working", PlatformAffected.All)]
public partial class Issue20274 : ContentPage
{
public ObservableCollection<CollectionViewItem> Items { get; set; }
public Issue20274()
[Issue(IssueTracker.Github, 27770, "IsEnabled=False on CollectionView not working", PlatformAffected.All)]
public partial class Issue27770 : ContentPage
{
public ObservableCollection<CollectionViewItem> Items { get; set; }
public Issue27770()

Copilot uses AI. Check for mistakes.
{
Items = new ObservableCollection<CollectionViewItem>
{
new CollectionViewItem { Name = "Item 1" },
new CollectionViewItem { Name = "Item 2" },
new CollectionViewItem { Name = "Item 3" }
};
InitializeComponent();
BindingContext = this;
label.Text = Items[0].Name;
}

void collectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection?.FirstOrDefault() is CollectionViewItem selectedItem)
{
selectedItem.Name = $"{selectedItem.Name} - Selected";
this.label.Text = selectedItem.Name;
Comment on lines +28 to +29

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

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

The test logic appears to be incorrect. The test starts with the CollectionView disabled (IsEnabled="False"), taps an item (which should be ignored), then verifies the label shows "Item 1" (the initial state). Then it enables the CollectionView by clicking the button, taps the item again, and expects "Item 1 - Selected". However, there's a bug: on line 28 of the code-behind, when an item is selected, the item's name is mutated to append " - Selected", which means on subsequent selections, it would become "Item 1 - Selected - Selected" and so on. This test only works once. Consider resetting the item name when the CollectionView is re-enabled or using a different mechanism to track selection state.

Suggested change
selectedItem.Name = $"{selectedItem.Name} - Selected";
this.label.Text = selectedItem.Name;
this.label.Text = $"{selectedItem.Name} - Selected";

Copilot uses AI. Check for mistakes.
}
}

void Button_Clicked(object sender, EventArgs e)
{
collectionView.IsEnabled = !collectionView.IsEnabled;
}
}

public class CollectionViewItem : INotifyPropertyChanged
{
string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue20274 : _IssuesUITest
{
public override string Issue => "IsEnabled=False on CollectionView not working";

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


[Test]
[Category(UITestCategories.CollectionView)]
public void VerifyCollectionViewIsEnableState()
{
App.WaitForElement("Button");
App.Tap("ItemsLabel");
Assert.That(App.FindElement("Label").GetText(), Is.EqualTo("Item 1"));
App.Tap("Button");
App.Tap("ItemsLabel");
Assert.That(App.FindElement("Label").GetText(), Is.EqualTo("Item 1 - Selected"));
}
}
}
Loading