Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
872bb94
[iOS] CarouselView with CarouselViewHandler2 make app crash when Loop…
kubaflo Jun 9, 2025
22ed68e
Bump to 1.7.250606001 of WindowsAppSDK (#29915)
PureWeen Jun 11, 2025
736b1e6
Fixes Setting BackgroundColor to null does not actually changes Backg…
Jon2G Jun 13, 2025
706161e
Fixed the picker title's color (#23075)
kubaflo Jun 13, 2025
e268456
[android] Fallback to default icons in SearchHandler (#25067)
aheubusch Jun 13, 2025
3285c24
[Testing] Feature Matrix UITest Cases for Button (#29803)
TamilarasanSF4853 Jun 13, 2025
db77d5f
[Testing] Feature matrix UITest Cases for BoxView Control (#29808)
HarishKumarSF4517 Jun 13, 2025
0406752
Enabled-MultiTouch-iOS (#29895)
prakashKannanSf3972 Jun 13, 2025
733ac3f
[houekeeping] update namespaces in hostapp and shared tests projects …
bhavanesh2001 Jun 13, 2025
f89740e
Optimize converters for GridLength, ColumnDefinition, and RowDefiniti…
symbiogenesis Jun 13, 2025
5897ad7
Add defensive IsAlive check to Android ViewExtensions.OnUnloaded (#29…
jfversluis Jun 13, 2025
024371d
Update SetterSpecificity.cs Remove Extra Line From Bad Merge (#29987)
PureWeen Jun 13, 2025
885eeac
ScrollView's Background on iOS (#25541)
kubaflo Jun 16, 2025
f053c13
[Windows] Fixed runtime update issue for SearchBar PlaceholderColor a…
Tamilarasan-Paranthaman Jun 16, 2025
590bd5f
[Testing] Enable HandlerDoesNotLeak for Button and ProgressBar (#29896)
bhavanesh2001 Jun 16, 2025
88b7642
[create-pull-request] automated change (#30019)
github-actions[bot] Jun 17, 2025
9899329
Revert "Fixes Setting BackgroundColor to null does not actually chang…
mattleibow Jun 17, 2025
87f5001
Weak subscription to CanExecuteChange events (#29837)
PureWeen Jun 17, 2025
0c3a393
[create-pull-request] automated change (#30043)
github-actions[bot] Jun 18, 2025
a9f228f
[iOS, Mac] Fix for downsized image retaining original dimensions in G…
SyedAbdulAzeemSF4852 Jun 18, 2025
637e2e4
[Android] Prevent Picker from Gaining Focus on Touch (#29068)
bhavanesh2001 Jun 19, 2025
2d7a1e4
Revert - Fixed the Label not sized correctly on Android (#30023)
Ahamed-Ali Jun 19, 2025
731302d
[create-pull-request] automated change (#30078)
github-actions[bot] Jun 21, 2025
7d0888a
Fix CV1 GridItemsLayout centering single item AND Fix Empty view not …
albyrock87 Jun 21, 2025
eb4e2e2
Update Controls.TestCases.HostApp.csproj (#30124)
HarishKumarSF4517 Jun 23, 2025
e0723d7
[Testing] Fixed Test case failure in PR 30115 - [2025/06/23] Candidat…
HarishKumarSF4517 Jun 26, 2025
0175e3c
[Testing] Add Validation Test For Issue28051 On Android (#30026)
prakashKannanSf3972 Jun 26, 2025
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
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
<MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion>9.0.5</MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion>
<MicrosoftNETWorkloadEmscriptenPackageVersion>$(MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion)</MicrosoftNETWorkloadEmscriptenPackageVersion>
<!-- wasdk -->
<MicrosoftWindowsAppSDKPackageVersion>1.7.250513003</MicrosoftWindowsAppSDKPackageVersion>
<MicrosoftWindowsAppSDKPackageVersion>1.7.250606001</MicrosoftWindowsAppSDKPackageVersion>
<MicrosoftWindowsSDKBuildToolsPackageVersion>10.0.22621.756</MicrosoftWindowsSDKBuildToolsPackageVersion>
<MicrosoftGraphicsWin2DPackageVersion>1.3.2</MicrosoftGraphicsWin2DPackageVersion>
<MicrosoftWindowsWebView2PackageVersion>1.0.3179.45</MicrosoftWindowsWebView2PackageVersion>
Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Core/Button/Button.cs
Original file line number Diff line number Diff line change
Expand Up @@ -616,5 +616,7 @@ private protected override string GetDebuggerDisplay()
var commandText = DebuggerDisplayHelpers.GetDebugText(nameof(Command), Command, false);
return $"{base.GetDebuggerDisplay()}, {textString}, {commandText}";
}

WeakCommandSubscription ICommandElement.CleanupTracker { get; set; }
}
}
46 changes: 18 additions & 28 deletions src/Controls/src/Core/Cells/TextCell.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,27 @@
#nullable disable
using System;
using System.Windows.Input;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Graphics;

namespace Microsoft.Maui.Controls
{
/// <include file="../../../docs/Microsoft.Maui.Controls/TextCell.xml" path="Type[@FullName='Microsoft.Maui.Controls.TextCell']/Docs/*" />
public class TextCell : Cell
public class TextCell : Cell, ICommandElement
{
/// <summary>Bindable property for <see cref="Command"/>.</summary>
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(TextCell), default(ICommand),
propertyChanging: (bindable, oldvalue, newvalue) =>
{
var textCell = (TextCell)bindable;
var oldcommand = (ICommand)oldvalue;
if (oldcommand != null)
oldcommand.CanExecuteChanged -= textCell.OnCommandCanExecuteChanged;
}, propertyChanged: (bindable, oldvalue, newvalue) =>
{
var textCell = (TextCell)bindable;
var newcommand = (ICommand)newvalue;
if (newcommand != null)
{
textCell.IsEnabled = newcommand.CanExecute(textCell.CommandParameter);
newcommand.CanExecuteChanged += textCell.OnCommandCanExecuteChanged;
}
});
public static readonly BindableProperty CommandProperty =
BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(TextCell),
propertyChanging: CommandElement.OnCommandChanging,
propertyChanged: CommandElement.OnCommandChanged);

/// <summary>Bindable property for <see cref="CommandParameter"/>.</summary>
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(TextCell), default(object),
propertyChanged: (bindable, oldvalue, newvalue) =>
{
var textCell = (TextCell)bindable;
if (textCell.Command != null)
{
textCell.IsEnabled = textCell.Command.CanExecute(newvalue);
}
});
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create(nameof(CommandParameter),
typeof(object),
typeof(TextCell),
null,
propertyChanged: CommandElement.OnCommandParameterChanged);

/// <summary>Bindable property for <see cref="Text"/>.</summary>
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(TextCell), default(string));
Expand Down Expand Up @@ -104,9 +89,14 @@ protected internal override void OnTapped()
Command?.Execute(CommandParameter);
}

void OnCommandCanExecuteChanged(object sender, EventArgs eventArgs)
void ICommandElement.CanExecuteChanged(object sender, EventArgs eventArgs)
{
if (Command is null)
return;

IsEnabled = Command.CanExecute(CommandParameter);
}

WeakCommandSubscription ICommandElement.CleanupTracker { get; set; }
}
}
5 changes: 5 additions & 0 deletions src/Controls/src/Core/ColumnDefinitionCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable disable
using System.Collections.Generic;
namespace Microsoft.Maui.Controls
{
/// <include file="../../docs/Microsoft.Maui.Controls/ColumnDefinitionCollection.xml" path="Type[@FullName='Microsoft.Maui.Controls.ColumnDefinitionCollection']/Docs/*" />
Expand All @@ -11,5 +12,9 @@ public ColumnDefinitionCollection() : base()
public ColumnDefinitionCollection(params ColumnDefinition[] definitions) : base(definitions)
{
}

internal ColumnDefinitionCollection(List<ColumnDefinition> definitions, bool copy) : base(definitions, copy)
{
}
}
}
61 changes: 48 additions & 13 deletions src/Controls/src/Core/ColumnDefinitionCollectionTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#nullable disable
using System;
using System.Buffers;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.Maui.Controls.Xaml;

namespace Microsoft.Maui.Controls
Expand All @@ -19,28 +21,61 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strValue = value?.ToString();
var strValue = value as string ?? value?.ToString()
?? throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(ColumnDefinitionCollection)));

if (strValue != null)
// fast path for no value or empty string
if (strValue.Length == 0)
return new ColumnDefinitionCollection();

#if NET6_0_OR_GREATER
var unsplit = (ReadOnlySpan<char>)strValue;
var count = unsplit.Count(',') + 1;
var definitions = new List<ColumnDefinition>(count);
foreach (var range in unsplit.Split(','))
{
var length = GridLengthTypeConverter.ParseStringToGridLength(unsplit[range]);
definitions.Add(new ColumnDefinition(length));
}
#else
var lengths = strValue.Split(',');
var count = lengths.Length;
var definitions = new List<ColumnDefinition>(count);
foreach (var lengthStr in lengths)
{
var lengths = strValue.Split(',');
var converter = new GridLengthTypeConverter();
var definitions = new ColumnDefinition[lengths.Length];
for (var i = 0; i < lengths.Length; i++)
definitions[i] = new ColumnDefinition { Width = (GridLength)converter.ConvertFromInvariantString(lengths[i]) };
return new ColumnDefinitionCollection(definitions);
var length = GridLengthTypeConverter.ParseStringToGridLength(lengthStr);
definitions.Add(new ColumnDefinition(length));
}
#endif

throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", strValue, typeof(ColumnDefinitionCollection)));
return new ColumnDefinitionCollection(definitions, copy: false);
}


public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is not ColumnDefinitionCollection cdc)
if (value is not ColumnDefinitionCollection definitions)
throw new NotSupportedException();
var converter = new GridLengthTypeConverter();
return string.Join(", ", cdc.Select(cd => converter.ConvertToInvariantString(cd.Width)));

var count = definitions.Count;

// fast path for empty or single definitions
if (count == 0)
return string.Empty;
if (count == 1)
return GridLengthTypeConverter.ConvertToString(definitions[0].Width);

// for multiple items
var pool = ArrayPool<string>.Shared;
var rentedArray = pool.Rent(definitions.Count);
for (var i = 0; i < definitions.Count; i++)
{
var definition = definitions[i];
rentedArray[i] = GridLengthTypeConverter.ConvertToString(definition.Width);
}
var result = string.Join(", ", rentedArray, 0, definitions.Count);
pool.Return(rentedArray);
return result;
}
}
}
19 changes: 14 additions & 5 deletions src/Controls/src/Core/CommandElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,24 @@ static class CommandElement
public static void OnCommandChanging(BindableObject bo, object o, object n)
{
var commandElement = (ICommandElement)bo;
if (o is ICommand oldCommand)
oldCommand.CanExecuteChanged -= commandElement.CanExecuteChanged;
commandElement.CleanupTracker?.Dispose();
commandElement.CleanupTracker = null;
}

public static void OnCommandChanged(BindableObject bo, object o, object n)
{
var commandElement = (ICommandElement)bo;
if (n is ICommand newCommand)
newCommand.CanExecuteChanged += commandElement.CanExecuteChanged;

if (n is null)
{
commandElement.CleanupTracker?.Dispose();
commandElement.CleanupTracker = null;
}
else
{
commandElement.CleanupTracker = new WeakCommandSubscription(bo, (ICommand)n, commandElement.CanExecuteChanged);
}

commandElement.CanExecuteChanged(bo, EventArgs.Empty);
}

Expand All @@ -36,4 +45,4 @@ public static bool GetCanExecute(ICommandElement commandElement)
return commandElement.Command.CanExecute(commandElement.CommandParameter);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
using Android.Widget;
using AndroidX.AppCompat.Widget;
using AndroidX.CardView.Widget;
using AndroidX.Core.Content;
using Java.Lang;
using Microsoft.Maui.Controls.Platform.Compatibility;
using AColor = Android.Graphics.Color;
using AImageButton = Android.Widget.ImageButton;
using ASupportDrawable = AndroidX.AppCompat.Graphics.Drawable;
Expand Down Expand Up @@ -317,12 +317,22 @@ AImageButton CreateImageButton(Context context, BindableObject bindable, Bindabl
result.SetScaleType(ImageView.ScaleType.FitCenter);

if (bindable.GetValue(property) is ImageSource image)
{
AutomationPropertiesProvider.SetContentDescription(result, image, null, null);

((ImageSource)bindable.GetValue(property)).LoadImage(MauiContext, (r) =>
image.LoadImage(MauiContext, (r) =>
{
result.SetImageDrawable(r?.Value);
});
}
else if (defaultImage > 0 && ContextCompat.GetDrawable(Context, defaultImage) is Drawable defaultDrawable)
{
result.SetImageDrawable(r?.Value);
});
result.SetImageDrawable(defaultDrawable);
}
else
{
result.SetImageDrawable(null);
}

var lp = new LinearLayout.LayoutParams((int)Context.ToPixels(22), LP.MatchParent)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Core/DefinitionCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class DefinitionCollection<T> : IList<T>, ICollection<T> where T : IDefin

internal DefinitionCollection(params T[] items) => _internalList = new List<T>(items);

internal DefinitionCollection(List<T> items, bool copy) => _internalList = copy ? new List<T>(items) : items;

public void Add(T item)
{
_internalList.Add(item);
Expand Down
96 changes: 96 additions & 0 deletions src/Controls/src/Core/DepdendentHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#nullable enable

#if NETSTANDARD2_0 || NETSTANDARD2_1
using System;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace System.Runtime;

/// <summary>
/// A wrapper around ConditionalWeakTable that replicates DependentHandle behavior.
/// Creates a dependency between a primary object and a dependent object where
/// the dependent object becomes eligible for collection when the primary is collected.
/// </summary>
internal class DependentHandle : IDisposable
{
private readonly ConditionalWeakTable<object, object> _table;
private readonly WeakReference<object> _primaryRef;
private bool _disposed;

/// <summary>
/// Initializes a new instance of DependentHandle with a primary and dependent object.
/// </summary>
/// <param name="primary">The primary object that controls the lifetime of the dependent object.</param>
/// <param name="dependent">The dependent object that will be collected when primary is collected.</param>
public DependentHandle(object primary, object? dependent)
{
_table = new ConditionalWeakTable<object, object>();
_primaryRef = new WeakReference<object>(primary);

// Store the dependent object in the table, keyed by the primary object
if (dependent is not null)
{
_table.Add(primary, dependent);
}
}

/// <summary>
/// Gets the primary object if it's still alive, otherwise returns null.
/// </summary>
public object? Target
{
get
{
if (_disposed)
return null;

return _primaryRef.TryGetTarget(out var target) ? target : null;
}
}

/// <summary>
/// Gets the dependent object if the primary object is still alive, otherwise returns null.
/// </summary>
public object? Dependent
{
get
{
if (_disposed)
return null;

if (_primaryRef.TryGetTarget(out var primary) &&
_table.TryGetValue(primary, out var dependent))
{
return dependent;
}

return null;
}
}

/// <summary>
/// Checks if both primary and dependent objects are still alive.
/// </summary>
public bool IsAllocated => Target is not null && Dependent is not null;

/// <summary>
/// Disposes the DependentHandleCWT, clearing all references.
/// </summary>
public void Dispose()
{
if (_disposed)
return;

_disposed = true;

// Clear the table - this will allow dependent objects to be collected
// even if the primary object is still alive
if (_primaryRef.TryGetTarget(out var primary))
{
_table.Remove(primary);
}
}
}
#endif
Loading
Loading