diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index c15a5babb4c5..b051da2f3d39 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -21,7 +21,7 @@
]
},
"microsoft.dotnet.xharness.cli": {
- "version": "10.0.0-prerelease.24467.4",
+ "version": "10.0.0-prerelease.24476.1",
"commands": [
"xharness"
]
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 510e3cbe95cb..745842be7c70 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -50,6 +50,7 @@ body:
- 9.0.0-preview.3.10457
- 9.0.0-preview.2.10293
- 9.0.0-preview.1.9973
+ - 8.0.91 SR9.1
- 8.0.90 SR9
- 8.0.82 SR8.2
- 8.0.80 SR8
@@ -126,6 +127,7 @@ body:
- 8.0.80 SR8
- 8.0.82 SR8.2
- 8.0.90 SR9
+ - 8.0.91 SR9.1
- 9.0.0-preview.1.9973
- 9.0.0-preview.2.10293
- 9.0.0-preview.3.10457
diff --git a/.github/workflows/similarIssues.yml b/.github/workflows/similarIssues.yml
deleted file mode 100644
index ecba7b66952e..000000000000
--- a/.github/workflows/similarIssues.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-name: GitGudSimilarIssues comments
-
-on:
- issues:
- types: [opened]
- issue_comment:
- types: [created]
-
-jobs:
- getSimilarIssues:
- runs-on: ubuntu-latest
- if: >-
- (github.event_name == 'issues' && github.event.action == 'opened') ||
- (github.event_name == 'issue_comment' && github.event.action == 'created' && startsWith(github.event.comment.body, '/similarissues'))
- outputs:
- message: ${{ steps.getBody.outputs.message }}
- steps:
- - id: getBody
- uses: craigloewen-msft/GitGudSimilarIssues@main
- with:
- issueTitle: ${{ github.event.issue.title }}
- issueBody: ${{ github.event.issue.body }}
- repo: ${{ github.repository }}
- similaritytolerance: "0.70"
- add-comment:
- needs: getSimilarIssues
- runs-on: ubuntu-latest
- permissions:
- issues: write
- if: needs.getSimilarIssues.outputs.message != ''
- steps:
- - name: Add comment
- run: gh issue comment "$NUMBER" --repo "$REPO" --body "$BODY"
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- NUMBER: ${{ github.event.issue.number }}
- REPO: ${{ github.repository }}
- BODY: ${{ needs.getSimilarIssues.outputs.message }}
diff --git a/Microsoft.Maui-dev.sln b/Microsoft.Maui-dev.sln
index e8dc43aced84..570c40a5f558 100644
--- a/Microsoft.Maui-dev.sln
+++ b/Microsoft.Maui-dev.sln
@@ -135,6 +135,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SharedSource", "SharedSource", "{4F2926C8-43AB-4328-A735-D9EAD699F81D}"
ProjectSection(SolutionItems) = preProject
src\BlazorWebView\src\SharedSource\AutoCloseOnReadCompleteStream.cs = src\BlazorWebView\src\SharedSource\AutoCloseOnReadCompleteStream.cs
+ src\BlazorWebView\src\SharedSource\HostAddressHelper.cs = src\BlazorWebView\src\SharedSource\HostAddressHelper.cs
src\BlazorWebView\src\SharedSource\QueryStringHelper.cs = src\BlazorWebView\src\SharedSource\QueryStringHelper.cs
src\BlazorWebView\src\SharedSource\UrlLoadingEventArgs.cs = src\BlazorWebView\src\SharedSource\UrlLoadingEventArgs.cs
src\BlazorWebView\src\SharedSource\UrlLoadingStrategy.cs = src\BlazorWebView\src\SharedSource\UrlLoadingStrategy.cs
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index f8150876bcd9..86dbe8673ce2 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -135,17 +135,17 @@
https://github.com/dotnet/runtime
46cfb747b4c22471242dee0d106f5c79cf9fd4c5
-
+
https://github.com/dotnet/xharness
- 3cfb1a3d86da666fb80ba0adb970525e88339d57
+ 7d5c32dbda0c6c8b9dc20cde4e1261b191896138
-
+
https://github.com/dotnet/xharness
- 3cfb1a3d86da666fb80ba0adb970525e88339d57
+ 7d5c32dbda0c6c8b9dc20cde4e1261b191896138
-
+
https://github.com/dotnet/xharness
- 3cfb1a3d86da666fb80ba0adb970525e88339d57
+ 7d5c32dbda0c6c8b9dc20cde4e1261b191896138
diff --git a/eng/Versions.props b/eng/Versions.props
index 8fc3873ec6f9..4fb0c857efe7 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -124,9 +124,9 @@
<_HarfBuzzSharpVersion>7.3.0.2
<_SkiaSharpNativeAssetsVersion>0.0.0-commit.7af1d0840a381c0ce7ef2877454a88dbb2949686.1086
7.0.114
- 10.0.0-prerelease.24467.4
- 10.0.0-prerelease.24467.4
- 10.0.0-prerelease.24467.4
+ 10.0.0-prerelease.24476.1
+ 10.0.0-prerelease.24476.1
+ 10.0.0-prerelease.24476.1
0.9.2
1.0.0.16
1.3.0
diff --git a/eng/pipelines/common/variables.yml b/eng/pipelines/common/variables.yml
index 5d7677141e71..581bf9355bdf 100644
--- a/eng/pipelines/common/variables.yml
+++ b/eng/pipelines/common/variables.yml
@@ -22,7 +22,7 @@ variables:
- name: isTargetMainBranch
value: $[eq(variables['System.PullRequest.TargetBranch'], 'refs/heads/main')]
- name: isLocPRBranch
- value: $[startsWith(variables['System.PullRequest.SourceBranch'], 'loc-hb')]
+ value: $[startsWith(variables['System.PullRequest.SourceBranch'], 'lego')]
- name: isPullRequest
value: $[eq(variables['Build.Reason'], 'PullRequest')]
- name: isLocHandoffBranch
diff --git a/global.json b/global.json
index 0ea1b4e4523d..ddb0d5a2b186 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"tools": {
- "dotnet": "9.0.100-preview.7.24407.12"
+ "dotnet": "9.0.100-rc.1.24452.12"
},
"msbuild-sdks": {
"MSBuild.Sdk.Extras": "3.0.44",
diff --git a/src/BlazorWebView/src/Maui/BlazorWebView.cs b/src/BlazorWebView/src/Maui/BlazorWebView.cs
index 330ead19c2d7..d7dbab342c08 100644
--- a/src/BlazorWebView/src/Maui/BlazorWebView.cs
+++ b/src/BlazorWebView/src/Maui/BlazorWebView.cs
@@ -11,44 +11,7 @@ namespace Microsoft.AspNetCore.Components.WebView.Maui
///
public partial class BlazorWebView : View, IBlazorWebView
{
- internal static string AppHostAddress { get; } = GetAppHostAddress();
-
- private const string AppHostAddressAlways0000Switch = "BlazorWebView.AppHostAddressAlways0000";
-
- private static bool IsAppHostAddressAlways0000Enabled =>
- AppContext.TryGetSwitch(AppHostAddressAlways0000Switch, out var enabled) && enabled;
-
- private static string GetAppHostAddress()
- {
- if (IsAppHostAddressAlways0000Enabled)
- {
- return "0.0.0.0";
- }
- else
- {
-#if IOS || MACCATALYST
- // On iOS/MacCatalyst 18 and higher the 0.0.0.0 address does not work, so we use localhost instead.
- // This preserves behavior on older versions of those systems, while defaulting to new behavior on
- // the new system.
-
- // Note that pre-release versions of iOS/MacCatalyst have the expected Major/Minor values,
- // but the Build, MajorRevision, MinorRevision, and Revision values are all -1, so we need
- // to pass in int.MinValue for those values.
-
- if (System.OperatingSystem.IsIOSVersionAtLeast(major: 18, minor: int.MinValue, build: int.MinValue) ||
- System.OperatingSystem.IsMacCatalystVersionAtLeast(major: 18, minor: int.MinValue, build: int.MinValue))
- {
- return "localhost";
- }
- else
- {
- return "0.0.0.0";
- }
-#else
- return "0.0.0.0";
-#endif
- }
- }
+ internal static string AppHostAddress { get; } = HostAddressHelper.GetAppHostAddress();
private readonly JSComponentConfigurationStore _jSComponents = new();
diff --git a/src/BlazorWebView/src/SharedSource/HostAddressHelper.cs b/src/BlazorWebView/src/SharedSource/HostAddressHelper.cs
new file mode 100644
index 000000000000..1ae50ab20a2e
--- /dev/null
+++ b/src/BlazorWebView/src/SharedSource/HostAddressHelper.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Components.WebView;
+
+internal static class HostAddressHelper
+{
+ private const string AppHostAddressAlways0000Switch = "BlazorWebView.AppHostAddressAlways0000";
+
+ private static bool IsAppHostAddressAlways0000Enabled =>
+ AppContext.TryGetSwitch(AppHostAddressAlways0000Switch, out var enabled) && enabled;
+
+ public static string GetAppHostAddress()
+ => IsAppHostAddressAlways0000Enabled
+ ? "0.0.0.0"
+ : "0.0.0.1";
+}
diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs
index 6f7c1dd01674..4ff3a97195e1 100644
--- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs
+++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs
@@ -49,10 +49,10 @@ internal class WebView2WebViewManager : WebViewManager
// Using an IP address means that WebView2 doesn't wait for any DNS resolution,
// making it substantially faster. Note that this isn't real HTTP traffic, since
// we intercept all the requests within this origin.
- internal static readonly string AppHostAddress = "0.0.0.0";
+ internal static readonly string AppHostAddress = HostAddressHelper.GetAppHostAddress();
///
- /// Gets the application's base URI. Defaults to https://0.0.0.0/
+ /// Gets the application's base URI. Defaults to https://0.0.0.1/.
///
protected static readonly string AppOrigin = $"https://{AppHostAddress}/";
diff --git a/src/Compatibility/ControlGallery/src/WinUI/Compatibility.ControlGallery.WinUI.csproj b/src/Compatibility/ControlGallery/src/WinUI/Compatibility.ControlGallery.WinUI.csproj
index e3c68f9c0f4f..e889e2cb6272 100644
--- a/src/Compatibility/ControlGallery/src/WinUI/Compatibility.ControlGallery.WinUI.csproj
+++ b/src/Compatibility/ControlGallery/src/WinUI/Compatibility.ControlGallery.WinUI.csproj
@@ -11,7 +11,7 @@
1701;1702;CS8305;8305;CA1416;0612;CS0672;CS0618
true
false
-
+ false
true
MSIX
win10-x64
diff --git a/src/Controls/src/Build.Tasks/BuildException.cs b/src/Controls/src/Build.Tasks/BuildException.cs
index a4a25c59edcc..e54f266069bd 100644
--- a/src/Controls/src/Build.Tasks/BuildException.cs
+++ b/src/Controls/src/Build.Tasks/BuildException.cs
@@ -93,7 +93,8 @@ class BuildExceptionCode
public static BuildExceptionCode ResourceDictDuplicateKey = new BuildExceptionCode("XC", 0125, nameof(ResourceDictDuplicateKey), "");
public static BuildExceptionCode ResourceDictMissingKey = new BuildExceptionCode("XC", 0126, nameof(ResourceDictMissingKey), "");
public static BuildExceptionCode XKeyNotLiteral = new BuildExceptionCode("XC", 0127, nameof(XKeyNotLiteral), "");
-
+ public static BuildExceptionCode StaticResourceSyntax = new BuildExceptionCode("XC", 0128, nameof(StaticResourceSyntax), "");
+
//CSC equivalents
public static BuildExceptionCode ObsoleteProperty = new BuildExceptionCode("XC", 0618, nameof(ObsoleteProperty), ""); //warning
diff --git a/src/Controls/src/Build.Tasks/CompiledMarkupExtensions/StaticResourceExtension.cs b/src/Controls/src/Build.Tasks/CompiledMarkupExtensions/StaticResourceExtension.cs
new file mode 100644
index 000000000000..a78664dd4997
--- /dev/null
+++ b/src/Controls/src/Build.Tasks/CompiledMarkupExtensions/StaticResourceExtension.cs
@@ -0,0 +1,181 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml;
+using Microsoft.Maui.Controls.Xaml;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using static Mono.Cecil.Cil.Instruction;
+using static Mono.Cecil.Cil.OpCodes;
+
+namespace Microsoft.Maui.Controls.Build.Tasks
+{
+ //yes, this is a ICompiledMarkupExtension, but declared as ICompiledValueProvider so it's evaluated later (in SetPropertyValue, not CreateObject)
+ class StaticResourceExtension : ICompiledValueProvider
+ {
+ public IEnumerable ProvideValue(VariableDefinitionReference vardefref, ModuleDefinition module, BaseNode node, ILContext context)
+ {
+ var name = new XmlName("", "Key");
+ var eNode = node as ElementNode;
+
+ if (!eNode.Properties.TryGetValue(name, out INode keyNode) && eNode.CollectionItems.Any())
+ keyNode = eNode.CollectionItems[0];
+
+ if (!(keyNode is ValueNode keyValueNode))
+ throw new BuildException(BuildExceptionCode.StaticResourceSyntax, eNode as IXmlLineInfo, null, null);
+
+ var n = eNode;
+ while (n != null)
+ {
+ if (n.Properties.TryGetValue(new XmlName(XamlParser.MauiUri, "Resources"), out var resourcesNode))
+ {
+ //single resource in
+ if (resourcesNode is IElementNode irn
+ && irn.Properties.TryGetValue(XmlName.xKey, out INode xKeyNode)
+ && context.Variables.ContainsKey(irn)
+ && xKeyNode is ValueNode xKeyValueNode
+ && xKeyValueNode.Value as string == keyValueNode.Value as string)
+ {
+ if (context.Variables[resourcesNode as IElementNode].VariableType.FullName == "System.String") {
+ foreach (var instruction in TryConvert(irn.CollectionItems[0] as ValueNode, eNode, vardefref, module, context))
+ yield return instruction;
+ yield break;
+ }
+
+ vardefref.VariableDefinition = context.Variables[irn];
+ yield break;
+ }
+ //multiple resources in
+ else if (resourcesNode is ListNode lr) {
+ foreach (var rn in lr.CollectionItems) {
+ if (rn is IElementNode irn2
+ && irn2.Properties.TryGetValue(XmlName.xKey, out INode xKeyNode2)
+ && context.Variables.ContainsKey(irn2)
+ && xKeyNode2 is ValueNode xKeyValueNode2
+ && xKeyValueNode2.Value as string == keyValueNode.Value as string)
+ {
+ if (irn2.CollectionItems.Count == 1 && irn2.CollectionItems[0] is ValueNode vn2 && vn2.Value is string) {
+ foreach (var instruction in TryConvert(vn2, eNode, vardefref, module, context))
+ yield return instruction;
+ yield break;
+ }
+
+ vardefref.VariableDefinition = context.Variables[irn2];
+ yield break;
+ }
+ }
+ }
+ //explicit ResourceDictionary in Resources
+ else if (resourcesNode is IElementNode resourceDictionary
+ && resourceDictionary.XmlType.Name == "ResourceDictionary") {
+ foreach (var rn in resourceDictionary.CollectionItems) {
+ if (rn is IElementNode irn3
+ && irn3.Properties.TryGetValue(XmlName.xKey, out INode xKeyNode3)
+ && irn3.XmlType.Name != "OnPlatform"
+ && context.Variables.ContainsKey(irn3)
+ && xKeyNode3 is ValueNode xKeyValueNode3
+ && xKeyValueNode3.Value as string == keyValueNode.Value as string)
+ {
+ if (irn3.CollectionItems.Count == 1 && irn3.CollectionItems[0] is ValueNode vn3 && vn3.Value is string) {
+ foreach (var instruction in TryConvert(vn3, eNode, vardefref, module, context))
+ yield return instruction;
+ yield break;
+ }
+
+ vardefref.VariableDefinition = context.Variables[irn3];
+ yield break;
+ }
+ }
+ }
+ }
+
+ n = n.Parent as ElementNode;
+ }
+
+
+ //Fallback
+ foreach (var instruction in FallBack(keyValueNode.Value as string, eNode, module, context).ToList())
+ yield return instruction;
+
+ var vardef = new VariableDefinition(module.TypeSystem.Object);
+ yield return Create(Stloc, vardef);
+ vardefref.VariableDefinition = vardef;
+ }
+
+ public static IEnumerable TryConvert(ValueNode stringResourceNode, IElementNode node, VariableDefinitionReference vardefref, ModuleDefinition module, ILContext context)
+ {
+ XmlName propertyName = XmlName.Empty;
+ SetPropertiesVisitor.TryGetPropertyName(node, node.Parent, out propertyName);
+ var localName = propertyName.LocalName;
+ var parentType = module.ImportReference((node.Parent as IElementNode).XmlType.GetTypeReference(context.Cache, module, (IXmlLineInfo)node));
+
+ var bpRef = SetPropertiesVisitor.GetBindablePropertyReference(parentType, propertyName.NamespaceURI, ref localName, out _, context, (IXmlLineInfo)node);
+ //BindableProperty
+ if (bpRef != null)
+ {
+ var targetTypeRef = module.ImportReference(bpRef.GetBindablePropertyType(context.Cache, node as IXmlLineInfo, module));
+ foreach (var instruction in stringResourceNode.PushConvertedValue(context, bpRef, requiredServices => stringResourceNode.PushServiceProvider(context, requiredServices, bpRef: bpRef), true, false))
+ yield return instruction;
+ var vardef = new VariableDefinition(targetTypeRef);
+ yield return Create(Stloc, vardef);
+ vardefref.VariableDefinition = vardef;
+ yield break;
+ }
+
+ var propertyRef = parentType.GetProperty(context.Cache, pd => pd.Name == localName, out var declaringTypeReference);
+ if (propertyRef != null)
+ {
+ foreach (var instruction in stringResourceNode.PushConvertedValue(context, propertyRef.PropertyType, new ICustomAttributeProvider[] { propertyRef, propertyRef.PropertyType.ResolveCached(context.Cache) }, requiredServices => stringResourceNode.PushServiceProvider(context, requiredServices, propertyRef: propertyRef), true, false))
+ yield return instruction;
+ var vardef = new VariableDefinition(propertyRef.PropertyType);
+ yield return Create(Stloc, vardef);
+ vardefref.VariableDefinition = vardef;
+ yield break;
+ }
+
+
+ }
+
+ public static IEnumerable FallBack(string key, IElementNode node, ModuleDefinition module, ILContext context)
+ {
+ var staticResourceExtensionType = module.ImportReference(context.Cache,
+ ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "StaticResourceExtension"));
+ yield return Create(Newobj, module.ImportCtorReference(context.Cache,
+ staticResourceExtensionType,
+ paramCount: 0));
+
+ SetPropertiesVisitor.TryGetPropertyName(node, node.Parent, out var propertyName);
+ var localName = propertyName.LocalName;
+
+ //Set the Key
+ var keyProperty = staticResourceExtensionType.GetProperty(context.Cache, pd => pd.Name == "Key", out _);
+ yield return Create(Dup);
+ yield return Create(Ldstr, key);
+ yield return Create(Callvirt, module.ImportReference(keyProperty.SetMethod));
+
+ FieldReference bpRef = null;
+ PropertyDefinition propertyRef = null;
+ TypeReference declaringTypeReference = null;
+ if (node.Parent is IElementNode parentNode && propertyName != XmlName.Empty)
+ {
+ var parentType = module.ImportReference(parentNode.XmlType.GetTypeReference(context.Cache, module, (IXmlLineInfo)node));
+ bpRef = SetPropertiesVisitor.GetBindablePropertyReference(parentType,
+ propertyName.NamespaceURI,
+ ref localName,
+ out _,
+ context,
+ (IXmlLineInfo)node);
+ propertyRef = parentType.GetProperty(context.Cache, pd => pd.Name == localName, out declaringTypeReference);
+
+ }
+
+ var requiredServices = staticResourceExtensionType.GetRequiredServices(context.Cache, module);
+ foreach (var instruction in node.PushServiceProvider(context, requiredServices, bpRef, propertyRef, declaringTypeReference))
+ yield return instruction;
+
+ yield return Create(Callvirt, module.ImportMethodReference(context.Cache,
+ ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "StaticResourceExtension"),
+ methodName: "ProvideValue",
+ parameterTypes: new[] { ("System.ComponentModel", "System", "IServiceProvider") }));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/src/Build.Tasks/ErrorMessages.resx b/src/Controls/src/Build.Tasks/ErrorMessages.resx
index 14f3c32018c0..39907273d5d0 100644
--- a/src/Controls/src/Build.Tasks/ErrorMessages.resx
+++ b/src/Controls/src/Build.Tasks/ErrorMessages.resx
@@ -268,5 +268,7 @@
x:Key expects a string literal.
-
+
+ A key is required in {StaticResource}.
+
\ No newline at end of file
diff --git a/src/Controls/src/Build.Tasks/NodeILExtensions.cs b/src/Controls/src/Build.Tasks/NodeILExtensions.cs
index d0f87e92a89c..2e68a52c2e17 100644
--- a/src/Controls/src/Build.Tasks/NodeILExtensions.cs
+++ b/src/Controls/src/Build.Tasks/NodeILExtensions.cs
@@ -197,9 +197,7 @@ public static IEnumerable PushConvertedValue(this ValueNode node, I
if (isExtendedConverter)
{
- var requireServiceAttribute = typeConverter.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "RequireServiceAttribute"));
- var requiredServiceType = (requireServiceAttribute?.ConstructorArguments[0].Value as CustomAttributeArgument[])?.Select(ca => ca.Value as TypeReference).ToArray();
-
+ var requiredServiceType = typeConverter.GetRequiredServices(context.Cache, module);
foreach (var instruction in pushServiceProvider(requiredServiceType))
yield return instruction;
}
@@ -329,6 +327,12 @@ public static IEnumerable PushConvertedValue(this ValueNode node, I
yield return Create(Box, module.ImportReference(originalTypeRef));
}
+ public static TypeReference[] GetRequiredServices(this TypeReference type, XamlCache cache, ModuleDefinition module)
+ {
+ var requireServiceAttribute = type.GetCustomAttribute(cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "RequireServiceAttribute"));
+ return (requireServiceAttribute?.ConstructorArguments[0].Value as CustomAttributeArgument[])?.Select(ca => ca.Value as TypeReference).ToArray();
+ }
+
static Instruction PushParsedEnum(XamlCache cache, TypeReference enumRef, string value, IXmlLineInfo lineInfo)
{
var enumDef = enumRef.ResolveCached(cache);
diff --git a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
index f66d66a5f847..b823f24dc347 100644
--- a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
+++ b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
@@ -285,10 +285,9 @@ public static IEnumerable ProvideValue(VariableDefinitionReference
out markupExtension, out genericArguments))
{
var acceptEmptyServiceProvider = vardefref.VariableDefinition.VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "AcceptEmptyServiceProviderAttribute")) != null;
- var requireServiceAttribute = vardefref.VariableDefinition.VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "RequireServiceAttribute"));
- var requiredServiceType = (requireServiceAttribute?.ConstructorArguments[0].Value as CustomAttributeArgument[])?.Select(ca => ca.Value as TypeReference).ToArray();
+ var requiredServiceType = vardefref.VariableDefinition.VariableType.GetRequiredServices(context.Cache, module);
- if (!acceptEmptyServiceProvider && requireServiceAttribute == null)
+ if (!acceptEmptyServiceProvider && requiredServiceType is null)
context.LoggingHelper.LogWarningOrError(BuildExceptionCode.UnattributedMarkupType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, vardefref.VariableDefinition.VariableType);
(string, string, string)? bindingExtensionType = vardefref.VariableDefinition.VariableType.FullName switch
@@ -338,13 +337,32 @@ public static IEnumerable ProvideValue(VariableDefinitionReference
else if (context.Variables[node].VariableType.ImplementsInterface(context.Cache, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IMarkupExtension"))))
{
var acceptEmptyServiceProvider = context.Variables[node].VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "AcceptEmptyServiceProviderAttribute")) != null;
- var requireServiceAttribute = vardefref.VariableDefinition.VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "RequireServiceAttribute"));
- var requiredServiceType = (requireServiceAttribute?.ConstructorArguments[0].Value as CustomAttributeArgument[])?.Select(ca => ca.Value as TypeReference).ToArray();
+ var requiredServiceType = vardefref.VariableDefinition.VariableType.GetRequiredServices(context.Cache, module);
- if (!acceptEmptyServiceProvider && requireServiceAttribute == null)
+ if (!acceptEmptyServiceProvider && requiredServiceType is null)
context.LoggingHelper.LogWarningOrError(BuildExceptionCode.UnattributedMarkupType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, vardefref.VariableDefinition.VariableType);
var markupExtensionType = ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IMarkupExtension");
+ //some markup extensions weren't compiled earlier (on purpose), so we need to compile them now
+ var compiledValueProviderName = context.Variables[node].VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "ProvideCompiledAttribute"))?.ConstructorArguments?[0].Value as string;
+ Type compiledValueProviderType;
+ ICompiledValueProvider valueProvider;
+
+ if ( compiledValueProviderName != null
+ && (compiledValueProviderType = Type.GetType(compiledValueProviderName)) != null
+ && (valueProvider = Activator.CreateInstance(compiledValueProviderType) as ICompiledValueProvider) != null)
+ {
+ var cProvideValue = typeof(ICompiledValueProvider).GetMethods().FirstOrDefault(md => md.Name == "ProvideValue");
+ var instructions = (IEnumerable)cProvideValue.Invoke(valueProvider, [
+ vardefref,
+ context.Body.Method.Module,
+ node as BaseNode,
+ context]);
+ foreach (var i in instructions)
+ yield return i;
+ yield break;
+ }
+
vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object);
foreach (var instruction in context.Variables[node].LoadAs(context.Cache, module.GetTypeDefinition(context.Cache, markupExtensionType), module))
yield return instruction;
@@ -362,10 +380,9 @@ public static IEnumerable ProvideValue(VariableDefinitionReference
else if (context.Variables[node].VariableType.ImplementsInterface(context.Cache, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "IValueProvider"))))
{
var acceptEmptyServiceProvider = context.Variables[node].VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "AcceptEmptyServiceProviderAttribute")) != null;
- var requireServiceAttribute = vardefref.VariableDefinition.VariableType.GetCustomAttribute(context.Cache, module, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Xaml", "RequireServiceAttribute"));
- var requiredServiceType = (requireServiceAttribute?.ConstructorArguments[0].Value as CustomAttributeArgument[])?.Select(ca => ca.Value as TypeReference).ToArray();
+ var requiredServiceType = vardefref.VariableDefinition.VariableType.GetRequiredServices(context.Cache, module);
- if (!acceptEmptyServiceProvider && requireServiceAttribute == null)
+ if (!acceptEmptyServiceProvider && requiredServiceType is null)
context.LoggingHelper.LogWarningOrError(BuildExceptionCode.UnattributedMarkupType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, vardefref.VariableDefinition.VariableType);
var valueProviderType = context.Variables[node].VariableType;
@@ -1110,7 +1127,7 @@ public static IEnumerable GetPropertyValue(VariableDefinition paren
if (CanGetValue(parent, bpRef, attached, lineInfo, context, out _))
return GetValue(parent, bpRef, lineInfo, context, out propertyType);
- //If it's a property, set it
+ //If it's a property, get it
if (CanGet(parent, localName, context, out _))
return Get(parent, localName, lineInfo, context, out propertyType);
@@ -1118,12 +1135,14 @@ public static IEnumerable GetPropertyValue(VariableDefinition paren
}
static FieldReference GetBindablePropertyReference(VariableDefinition parent, string namespaceURI, ref string localName, out bool attached, ILContext context, IXmlLineInfo iXmlLineInfo)
+ => GetBindablePropertyReference(parent.VariableType, namespaceURI, ref localName, out attached, context, iXmlLineInfo);
+
+ public static FieldReference GetBindablePropertyReference(TypeReference bpOwnerType, string namespaceURI, ref string localName, out bool attached, ILContext context, IXmlLineInfo iXmlLineInfo)
{
var module = context.Body.Method.Module;
TypeReference declaringTypeReference;
//If it's an attached BP, update elementType and propertyName
- var bpOwnerType = parent.VariableType;
attached = GetNameAndTypeRef(ref bpOwnerType, namespaceURI, ref localName, context, iXmlLineInfo);
var name = $"{localName}Property";
FieldDefinition bpDef = bpOwnerType.GetField(context.Cache,
@@ -1322,10 +1341,12 @@ static bool CanSetValue(FieldReference bpRef, bool attached, INode node, IXmlLin
if (!context.Variables.TryGetValue(elementNode, out VariableDefinition varValue))
return false;
+
+
var bpTypeRef = bpRef.GetBindablePropertyType(context.Cache, iXmlLineInfo, module);
// If it's an attached BP, there's no second chance to handle IMarkupExtensions, so we try here.
// Worst case scenario ? InvalidCastException at runtime
- if (attached && varValue.VariableType.FullName == "System.Object")
+ if (varValue.VariableType.FullName == "System.Object")
return true;
var implicitOperator = varValue.VariableType.GetImplicitOperatorTo(context.Cache, bpTypeRef, module);
if (implicitOperator != null)
@@ -1453,7 +1474,7 @@ static bool CanSet(VariableDefinition parent, string localName, INode node, ILCo
return false;
var valueNode = node as ValueNode;
- if (valueNode != null && valueNode.CanConvertValue(context, propertyType, new ICustomAttributeProvider[] { property, propertyType.ResolveCached(context.Cache) }))
+ if (valueNode != null && valueNode.CanConvertValue(context, propertyType, new ICustomAttributeProvider[] { property, propertyType.ResolveCached(context.Cache) }))
return true;
var elementNode = node as IElementNode;
diff --git a/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
index 566fe6a282cb..4b72db7ba14a 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
@@ -504,7 +504,7 @@ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
UpdateUseLargeTitles();
}
- else if (e.PropertyName == NavigationPage.BackButtonTitleProperty.PropertyName)
+ else if (e.PropertyName == NavigationPage.BackButtonTitleProperty.PropertyName || e.PropertyName == NavigationPage.TitleProperty.PropertyName)
{
var pack = (ParentingViewController)TopViewController;
pack?.UpdateTitleArea(pack.Child);
@@ -1510,16 +1510,17 @@ internal void UpdateTitleArea(Page page)
if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsMacCatalystVersionAtLeast(11)) && !isBackButtonTextSet)
backButtonText = "";
+ _navigation.TryGetTarget(out NavigationRenderer n);
+
// First page and we have a flyout detail to contend with
UpdateLeftBarButtonItem();
- UpdateBackButtonTitle(page.Title, backButtonText);
+ UpdateBackButtonTitle(page.Title ?? n?.NavPage.Title, backButtonText);
//var hadTitleView = NavigationItem.TitleView != null;
ClearTitleViewContainer();
if (needContainer)
{
- NavigationRenderer n;
- if (!_navigation.TryGetTarget(out n))
+ if (n is null)
return;
Container titleViewContainer = new Container(titleView, n.NavigationBar);
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellSectionRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellSectionRenderer.cs
index 32c8a6b7a12b..24ceab4a95cf 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellSectionRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellSectionRenderer.cs
@@ -149,6 +149,30 @@ public override AView OnCreateView(LayoutInflater inflater, ViewGroup container,
return _rootView = root;
}
+ void OnShellContentPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == ShellContent.TitleProperty.PropertyName && sender is ShellContent shellContent)
+ {
+ UpdateTabTitle(shellContent);
+ }
+ }
+
+ void UpdateTabTitle(ShellContent shellContent)
+ {
+ if (_tablayout == null || SectionController.GetItems().Count == 0)
+ return;
+
+ int index = SectionController.GetItems().IndexOf(shellContent);
+ if (index >= 0)
+ {
+ var tab = _tablayout.GetTabAt(index);
+ if (tab != null)
+ {
+ tab.SetText(new string(shellContent.Title));
+ }
+ }
+ }
+
void OnTabLayoutChange(object sender, AView.LayoutChangeEventArgs e)
{
if (_disposed)
@@ -327,6 +351,10 @@ void HookEvents()
SectionController.ItemsCollectionChanged += OnItemsCollectionChanged;
((IShellController)_shellContext.Shell).AddAppearanceObserver(this, ShellSection);
ShellSection.PropertyChanged += OnShellItemPropertyChanged;
+ foreach (var item in SectionController.GetItems())
+ {
+ item.PropertyChanged += OnShellContentPropertyChanged;
+ }
}
void UnhookEvents()
@@ -334,6 +362,10 @@ void UnhookEvents()
SectionController.ItemsCollectionChanged -= OnItemsCollectionChanged;
((IShellController)_shellContext?.Shell)?.RemoveAppearanceObserver(this);
ShellSection.PropertyChanged -= OnShellItemPropertyChanged;
+ foreach (var item in SectionController.GetItems())
+ {
+ item.PropertyChanged -= OnShellContentPropertyChanged;
+ }
}
protected virtual void OnPageSelected(int position)
diff --git a/src/Controls/src/Core/Element/Element.cs b/src/Controls/src/Core/Element/Element.cs
index 849967d69d14..ecf15fc254f0 100644
--- a/src/Controls/src/Core/Element/Element.cs
+++ b/src/Controls/src/Core/Element/Element.cs
@@ -782,7 +782,7 @@ internal void OnResourcesChanged(IEnumerable> value
internal override void OnSetDynamicResource(BindableProperty property, string key, SetterSpecificity specificity)
{
base.OnSetDynamicResource(property, key, specificity);
- if (!DynamicResources.TryGetValue(property, out var existing) || existing.Item2 < specificity)
+ if (!DynamicResources.TryGetValue(property, out var existing) || existing.Item2 <= specificity)
DynamicResources[property] = (key, specificity);
if (this.TryGetResource(key, out var value))
OnResourceChanged(property, value, specificity);
diff --git a/src/Controls/src/Core/Frame/Frame.cs b/src/Controls/src/Core/Frame/Frame.cs
index ca79dcedefd4..8c6a2a28ceff 100644
--- a/src/Controls/src/Core/Frame/Frame.cs
+++ b/src/Controls/src/Core/Frame/Frame.cs
@@ -112,7 +112,8 @@ IShadow IView.Shadow
Size ICrossPlatformLayout.CrossPlatformArrange(Graphics.Rect bounds)
{
#if !WINDOWS
- bounds = bounds.Inset(((IBorderElement)this).BorderWidth); // Windows' implementation would cause an incorrect double-counting of the inset
+ if (BorderColor is not null)
+ bounds = bounds.Inset(((IBorderElement)this).BorderWidth); // Windows' implementation would cause an incorrect double-counting of the inset
#endif
this.ArrangeContent(bounds);
return bounds.Size;
@@ -122,7 +123,8 @@ Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double he
{
var inset = Padding;
#if !WINDOWS
- inset += ((IBorderElement)this).BorderWidth; // Windows' implementation would cause an incorrect double-counting of the inset
+ if (BorderColor is not null)
+ inset += ((IBorderElement)this).BorderWidth; // Windows' implementation would cause an incorrect double-counting of the inset
#endif
return this.MeasureContent(inset, widthConstraint, heightConstraint);
}
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/GroupableItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/GroupableItemsViewController.cs
index 3e618c05deb3..f1064d9abe34 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/GroupableItemsViewController.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/GroupableItemsViewController.cs
@@ -173,17 +173,9 @@ internal CGSize GetReferenceSizeForheaderOrFooter(UICollectionView collectionVie
return CGSize.Empty;
}
- if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11)))
- {
- // iOS 10 crashes if we try to dequeue a cell for measurement
- // so we'll use an alternate method
- return MeasureSupplementaryView(elementKind, section);
- }
-
- var cell = GetViewForSupplementaryElement(collectionView, elementKind,
- NSIndexPath.FromItemSection(0, section)) as ItemsViewCell;
-
- return cell.Measure();
+ // Dequeuing a supplementary view for measurement caused multiple instances of the header/footer to appear in the view.
+ // We now always use MeasureSupplementaryView, an alternate approach for calculating the size without dequeuing the view.
+ return MeasureSupplementaryView(elementKind, section);
}
internal void SetScrollAnimationEndedCallback(Action callback)
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs
index feba35a2451f..050133eb7569 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs
@@ -204,7 +204,7 @@ bool NotLoadedYet()
void Add(NotifyCollectionChangedEventArgs args)
{
- if (ReloadRequired())
+ if (NotLoadedYet())
{
Reload();
return;
diff --git a/src/Controls/src/Core/Handlers/Shell/ShellContentHandler.Windows.cs b/src/Controls/src/Core/Handlers/Shell/ShellContentHandler.Windows.cs
index 776e69c38373..3ae9eb434f34 100644
--- a/src/Controls/src/Core/Handlers/Shell/ShellContentHandler.Windows.cs
+++ b/src/Controls/src/Core/Handlers/Shell/ShellContentHandler.Windows.cs
@@ -7,7 +7,7 @@ namespace Microsoft.Maui.Controls.Handlers
public partial class ShellContentHandler : ElementHandler
{
public static PropertyMapper Mapper =
- new PropertyMapper(ElementMapper);
+ new PropertyMapper(ElementMapper) { [nameof(ShellContent.Title)] = MapTitle };
public static CommandMapper CommandMapper =
new CommandMapper(ElementCommandMapper);
@@ -16,6 +16,14 @@ public ShellContentHandler() : base(Mapper, CommandMapper)
{
}
+ internal static void MapTitle(ShellContentHandler handler, ShellContent item)
+ {
+ var shellSection = item.Parent as ShellSection;
+ var shellItem = shellSection?.Parent as ShellItem;
+ var shellItemHandler = shellItem?.Handler as ShellItemHandler;
+ shellItemHandler?.UpdateTitle();
+ }
+
protected override FrameworkElement CreatePlatformElement()
{
return (VirtualView as IShellContentController).GetOrCreateContent().ToPlatform(MauiContext);
diff --git a/src/Controls/src/Core/NavigationPage/NavigationPageToolbar.cs b/src/Controls/src/Core/NavigationPage/NavigationPageToolbar.cs
index 9e58809acca2..f51ff1e35388 100644
--- a/src/Controls/src/Core/NavigationPage/NavigationPageToolbar.cs
+++ b/src/Controls/src/Core/NavigationPage/NavigationPageToolbar.cs
@@ -273,7 +273,7 @@ string GetTitle()
return string.Empty;
}
- return _currentNavigationPage?.CurrentPage?.Title;
+ return _currentNavigationPage?.CurrentPage?.Title ?? _currentNavigationPage?.Title;
}
VisualElement GetTitleView()
diff --git a/src/Controls/src/Core/SetterSpecificityList.cs b/src/Controls/src/Core/SetterSpecificityList.cs
index 11f33229370a..9dd26389f500 100644
--- a/src/Controls/src/Core/SetterSpecificityList.cs
+++ b/src/Controls/src/Core/SetterSpecificityList.cs
@@ -15,6 +15,7 @@ internal class SetterSpecificityList where T : class
SetterSpecificity[] _keys;
T[] _values;
+
int _count;
public int Count => _count;
@@ -231,4 +232,4 @@ void SetCapacity(int currentCapacity, int capacity)
_values = newValues;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Controls/src/Core/Shell/BaseShellItem.cs b/src/Controls/src/Core/Shell/BaseShellItem.cs
index f7237a2cd298..2759d64b5e8c 100644
--- a/src/Controls/src/Core/Shell/BaseShellItem.cs
+++ b/src/Controls/src/Core/Shell/BaseShellItem.cs
@@ -50,7 +50,7 @@ public class BaseShellItem : NavigableElement, IPropertyPropagationController, I
/// Bindable property for .
public static readonly BindableProperty TitleProperty =
- BindableProperty.Create(nameof(Title), typeof(string), typeof(BaseShellItem), null, BindingMode.OneTime, propertyChanged: OnTitlePropertyChanged);
+ BindableProperty.Create(nameof(Title), typeof(string), typeof(BaseShellItem), null, BindingMode.TwoWay, propertyChanged: OnTitlePropertyChanged);
/// Bindable property for .
public static readonly BindableProperty IsVisibleProperty =
diff --git a/src/Controls/src/Core/Toolbar/Toolbar.Android.cs b/src/Controls/src/Core/Toolbar/Toolbar.Android.cs
index 3e33d47fab4e..fb632db95961 100644
--- a/src/Controls/src/Core/Toolbar/Toolbar.Android.cs
+++ b/src/Controls/src/Core/Toolbar/Toolbar.Android.cs
@@ -138,6 +138,7 @@ public static void MapIsVisible(ToolbarHandler arg1, Toolbar arg2) =>
public static void MapBarTextColor(IToolbarHandler arg1, Toolbar arg2)
{
arg1.PlatformView.UpdateBarTextColor(arg2);
+ arg2.UpdateMenu();
}
public static void MapBarBackground(IToolbarHandler arg1, Toolbar arg2)
diff --git a/src/Controls/src/Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/StaticResourceExtension.cs
index 6af7ec29d1d4..99cd45f53938 100644
--- a/src/Controls/src/Xaml/MarkupExtensions/StaticResourceExtension.cs
+++ b/src/Controls/src/Xaml/MarkupExtensions/StaticResourceExtension.cs
@@ -7,6 +7,7 @@ namespace Microsoft.Maui.Controls.Xaml
{
[ContentProperty(nameof(Key))]
[RequireService([typeof(IXmlLineInfoProvider), typeof(IProvideParentValues)])]
+ [ProvideCompiled("Microsoft.Maui.Controls.Build.Tasks.StaticResourceExtension")]
public sealed class StaticResourceExtension : IMarkupExtension
{
public string Key { get; set; }
diff --git a/src/Controls/src/Xaml/XamlServiceProvider.cs b/src/Controls/src/Xaml/XamlServiceProvider.cs
index f69c958fe0fb..c2b86f47db39 100644
--- a/src/Controls/src/Xaml/XamlServiceProvider.cs
+++ b/src/Controls/src/Xaml/XamlServiceProvider.cs
@@ -372,4 +372,4 @@ static bool IsBindingBaseProperty(IElementNode node, HydrationContext context)
public string BindingDataType { get; }
public HydrationContext Context { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ChangeShellContentTitle.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ChangeShellContentTitle.png
new file mode 100644
index 000000000000..ab55034b484c
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ChangeShellContentTitle.png differ
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CollectionViewAddGroupWhenViewIsEmpty.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CollectionViewAddGroupWhenViewIsEmpty.png
new file mode 100644
index 000000000000..5d661ee0c610
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CollectionViewAddGroupWhenViewIsEmpty.png differ
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CollectionViewDuplicateViewsWhenAddItemToGroup.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CollectionViewDuplicateViewsWhenAddItemToGroup.png
new file mode 100644
index 000000000000..cb1e0f385aa5
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CollectionViewDuplicateViewsWhenAddItemToGroup.png differ
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/NavigationPageTitle.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/NavigationPageTitle.png
new file mode 100644
index 000000000000..3699d81a9306
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/NavigationPageTitle.png differ
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarItemsShouldBeVisible.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarItemsShouldBeVisible.png
new file mode 100644
index 000000000000..614e03c2473e
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarItemsShouldBeVisible.png differ
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ValidateFrameOffsets.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ValidateFrameOffsets.png
new file mode 100644
index 000000000000..c041d90cdb19
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ValidateFrameOffsets.png differ
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue17969.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue17969.xaml
new file mode 100644
index 000000000000..aab6b5922577
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue17969.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue17969.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue17969.xaml.cs
new file mode 100644
index 000000000000..3fad15402b5e
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue17969.xaml.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Microsoft.Maui.Controls;
+
+namespace Maui.Controls.Sample.Issues
+{
+
+ [Issue(IssueTracker.Github, 17969, "CollectionView duplicates group headers/footers when adding a new item to a group or crashes when adding a new group with empty view", PlatformAffected.iOS)]
+ public partial class Issue17969 : ContentPage
+ {
+ GroupedCollectionViewModel viewModel;
+ public Issue17969()
+ {
+ InitializeComponent();
+ viewModel = new GroupedCollectionViewModel();
+ BindingContext = viewModel;
+ }
+
+ private void OnAddClicked(object sender, EventArgs e)
+ {
+ viewModel.AddAnimal();
+ }
+
+ private void OnResetClicked(object sender, EventArgs e)
+ {
+ viewModel.Reset();
+ }
+
+ }
+
+ public class TestItem
+ {
+ public string Name { get; set; }
+ }
+ public class Animal
+ {
+ public string Name { get; set; }
+ }
+
+ public class AnimalGroup : ObservableCollection
+ {
+ public string Name { get; private set; }
+
+ public AnimalGroup(string name, List animals) : base(animals)
+ {
+ Name = name;
+ }
+ }
+
+ public class GroupedCollectionViewModel
+ {
+ public GroupedCollectionViewModel()
+ {
+ Init();
+
+ }
+
+ private void Init()
+ {
+ AnimalBaseList.Clear();
+ AnimalBaseList.Add(new AnimalGroup("Bears", new List
+ {
+ new Animal
+ {
+ Name = "American Black Bear",
+ },
+ new Animal
+ {
+ Name = "Asian Black Bear",
+ },
+ new Animal
+ {
+ Name = "Brown Bear",
+ },
+ new Animal
+ {
+ Name = "Grizzly-Polar Bear Hybrid",
+ },
+ new Animal
+ {
+ Name = "Sloth Bear",
+ },
+ new Animal
+ {
+ Name = "Sun Bear",
+ },
+ new Animal
+ {
+ Name = "Polar Bear",
+ },
+ new Animal
+ {
+ Name = "Spectacled Bear",
+ },
+ new Animal
+ {
+ Name = "Short-faced Bear",
+ },
+ new Animal
+ {
+ Name = "California Grizzly Bear",
+ }
+ }));
+
+ AnimalBaseList.Add(new AnimalGroup("Cats", new List
+ {
+ new Animal
+ {
+ Name = "Abyssinian",
+ },
+ new Animal
+ {
+ Name = "Arabian Mau",
+ },
+ new Animal
+ {
+ Name = "Bengal",
+ },
+ new Animal
+ {
+ Name = "Burmese",
+ },
+ new Animal
+ {
+ Name = "Cyprus",
+ },
+ new Animal
+ {
+ Name = "German Rex",
+ },
+ new Animal
+ {
+ Name = "Highlander",
+ },
+ new Animal
+ {
+ Name = "Peterbald",
+ },
+ new Animal
+ {
+ Name = "Scottish Fold",
+ },
+ new Animal
+ {
+ Name = "Sphynx",
+ }
+ }));
+
+ Animals.Add(new AnimalGroup(AnimalBaseList[0].Name, new List()));
+
+ AddAnimal();
+
+ Animals.Add(new AnimalGroup(AnimalBaseList[1].Name, AnimalBaseList[1].ToList()));
+
+ }
+
+
+ public bool AddAnimal()
+ {
+ // Add animal from first group
+ var sourceGroup = AnimalBaseList.First();
+ var targetGroup = Animals.First();
+
+ if (sourceGroup.Count == 0)
+ return false;
+
+ var animal = sourceGroup.First();
+ targetGroup.Add(animal);
+ sourceGroup.Remove(animal);
+
+ return true;
+ }
+
+ public void Reset()
+ {
+ Animals.Clear();
+ Init();
+
+ }
+
+ private List AnimalBaseList { get; set; } = new List();
+
+ public ObservableCollection Animals { get; private set; } = new ObservableCollection();
+ }
+
+}
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue19859.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue19859.xaml
new file mode 100644
index 000000000000..44b1ffcefc3d
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue19859.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue19859.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue19859.xaml.cs
new file mode 100644
index 000000000000..eed77f4d3d1c
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue19859.xaml.cs
@@ -0,0 +1,37 @@
+namespace Maui.Controls.Sample.Issues
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ [Issue(IssueTracker.Github, 19859, "NavigationPage: BarBackgroundColor, BarTextColor and Title not updating", PlatformAffected.Android)]
+ public partial class Issue19859 : FlyoutPage
+ {
+ public Issue19859()
+ {
+ InitializeComponent();
+ }
+
+ private static int _count = 0;
+
+ private void OnClicked(object sender, EventArgs e)
+ {
+ var oldBarBackgroundColor = NavigationPage.BarBackgroundColor;
+ NavigationPage.BarBackgroundColor = oldBarBackgroundColor.Equals(Colors.Yellow)
+ ? Colors.Red
+ : Colors.Yellow;
+ var newBarBackgroundColor = NavigationPage.BarBackgroundColor;
+
+ var oldBarTextColor = NavigationPage.BarTextColor;
+ NavigationPage.BarTextColor = oldBarTextColor.Equals(Colors.Yellow)
+ ? Colors.Red
+ : Colors.Yellow;
+ var newBarTextColor = NavigationPage.BarTextColor;
+
+ var oldTitle = NavigationPage.Title;
+ NavigationPage.Title = oldTitle == "Title 1"
+ ? "Title 2"
+ : "Title 1";
+ var newTitle = NavigationPage.Title;
+
+ button.Text = $"{_count++}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue23333.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue23333.xaml
new file mode 100644
index 000000000000..d0618abb302c
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue23333.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue23333.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue23333.xaml.cs
new file mode 100644
index 000000000000..00b017b58e0a
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue23333.xaml.cs
@@ -0,0 +1,15 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ [Issue(IssueTracker.Github, 23333, "Frame offsets inner content view by 1pt", PlatformAffected.Android | PlatformAffected.iOS | PlatformAffected.macOS)]
+ public partial class Issue23333 : ContentPage
+ {
+ public Issue23333()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue24878.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue24878.xaml
new file mode 100644
index 000000000000..a97b3a88b6c2
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue24878.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue24878.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue24878.xaml.cs
new file mode 100644
index 000000000000..fd6bebeb088f
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue24878.xaml.cs
@@ -0,0 +1,14 @@
+using Microsoft.Maui.Controls.Internals;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [Issue(IssueTracker.Github, 24878, "AppThemeBinding does not work on ToolbarItems", PlatformAffected.Android)]
+ public partial class Issue24878 : Shell
+ {
+ public Issue24878()
+ {
+ Application.Current.UserAppTheme = AppTheme.Dark;
+ InitializeComponent();
+ }
+ }
+}
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue7453.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue7453.xaml
new file mode 100644
index 000000000000..487e80aa0e6f
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue7453.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue7453.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue7453.xaml.cs
new file mode 100644
index 000000000000..ac8479c4559c
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue7453.xaml.cs
@@ -0,0 +1,16 @@
+namespace Maui.Controls.Sample.Issues;
+
+[XamlCompilation(XamlCompilationOptions.Compile)]
+[Issue(IssueTracker.Github, 7453, "ShellContent Title doesn't observe changes to bound properties", PlatformAffected.UWP | PlatformAffected.Android)]
+public partial class Issue7453 : Shell
+{
+ public Issue7453()
+ {
+ InitializeComponent();
+ }
+
+ private void OnButtonClicked(object sender, EventArgs e)
+ {
+ this.tab.Title = "Updated title";
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17969.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17969.cs
new file mode 100644
index 000000000000..73f4399277bd
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue17969.cs
@@ -0,0 +1,39 @@
+#if !MACCATALYST
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues
+{
+ public class Issue17969 : _IssuesUITest
+ {
+
+ public Issue17969(TestDevice device)
+ : base(device)
+ { }
+
+ public override string Issue => "CollectionView duplicates group headers/footers when adding a new item to a group or crashes when adding a new group with empty view";
+
+ [Test]
+ [Category(UITestCategories.CollectionView)]
+ [FailsOnWindows]
+ public void CollectionViewDuplicateViewsWhenAddItemToGroup()
+ {
+ App.WaitForElement("collectionView");
+ App.Tap("addItem");
+ VerifyScreenshot();
+
+ }
+
+ [Test]
+ [Category(UITestCategories.CollectionView)]
+ [FailsOnWindows]
+ public void CollectionViewAddGroupWhenViewIsEmpty()
+ {
+ App.WaitForElement("collectionView");
+ App.Tap("addGroup");
+ VerifyScreenshot();
+ }
+ }
+}
+#endif
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19859.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19859.cs
new file mode 100644
index 000000000000..36af89eda320
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19859.cs
@@ -0,0 +1,26 @@
+#if !MACCATALYST
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues
+{
+ public class Issue19859 : _IssuesUITest
+ {
+ public Issue19859(TestDevice device) : base(device)
+ {
+ }
+
+ public override string Issue => "NavigationPage: BarBackgroundColor, BarTextColor and Title not updating";
+
+ [Test]
+ [Category(UITestCategories.Navigation)]
+ public void NavigationPageTitle()
+ {
+ App.WaitForElement("Button");
+ App.Tap("Button");
+ VerifyScreenshot();
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue23333.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue23333.cs
new file mode 100644
index 000000000000..1763e511fc9d
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue23333.cs
@@ -0,0 +1,26 @@
+#if !MACCATALYST
+using NUnit.Framework;
+using NUnit.Framework.Legacy;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues
+{
+ public class Issue23333 : _IssuesUITest
+ {
+ public override string Issue => "Frame offsets inner content view by 1pt";
+
+ public Issue23333(TestDevice device) : base(device)
+ {
+ }
+
+ [Test]
+ [Category(UITestCategories.Frame)]
+ public void ValidateFrameOffsets()
+ {
+ App.WaitForElement("FrameWithImage");
+ VerifyScreenshot();
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24878.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24878.cs
new file mode 100644
index 000000000000..e0f2e1057680
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24878.cs
@@ -0,0 +1,23 @@
+#if ANDROID || WINDOWS
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues;
+
+public class Issue24878 : _IssuesUITest
+{
+ public Issue24878(TestDevice device) : base(device) { }
+
+ public override string Issue => "AppThemeBinding does not work on ToolbarItems";
+
+ [Test]
+ [Category(UITestCategories.ToolbarItem)]
+ public void ToolbarItemsShouldBeVisible()
+ {
+ App.WaitForElement("label");
+
+ VerifyScreenshot();
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue7453.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue7453.cs
new file mode 100644
index 000000000000..b8672f3e2f37
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue7453.cs
@@ -0,0 +1,32 @@
+#if !MACCATALYST
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Maui.Controls;
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues
+{
+ public class Issue7453 : _IssuesUITest
+ {
+ public Issue7453(TestDevice device) : base(device)
+ {
+ }
+
+ public override string Issue => "ShellContent Title doesn't observe changes to bound properties";
+
+ [Test]
+ [Category(UITestCategories.Shell)]
+ public void ChangeShellContentTitle()
+ {
+ App.WaitForElement("ChangeShellContentTitle");
+ App.Click("ChangeShellContentTitle");
+ VerifyScreenshot();
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ChangeShellContentTitle.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ChangeShellContentTitle.png
new file mode 100644
index 000000000000..5757b20acd46
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ChangeShellContentTitle.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/NavigationPageTitle.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/NavigationPageTitle.png
new file mode 100644
index 000000000000..33401f4f2d6d
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/NavigationPageTitle.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarItemsShouldBeVisible.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarItemsShouldBeVisible.png
new file mode 100644
index 000000000000..bce1c2a7f3f7
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarItemsShouldBeVisible.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ValidateFrameOffsets.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ValidateFrameOffsets.png
new file mode 100644
index 000000000000..c732803922cf
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ValidateFrameOffsets.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ChangeShellContentTitle.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ChangeShellContentTitle.png
new file mode 100644
index 000000000000..244abbe2cd34
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ChangeShellContentTitle.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CollectionViewAddGroupWhenViewIsEmpty.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CollectionViewAddGroupWhenViewIsEmpty.png
new file mode 100644
index 000000000000..90ce2112d0a6
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CollectionViewAddGroupWhenViewIsEmpty.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CollectionViewDuplicateViewsWhenAddItemToGroup.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CollectionViewDuplicateViewsWhenAddItemToGroup.png
new file mode 100644
index 000000000000..8a2f1fd28230
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CollectionViewDuplicateViewsWhenAddItemToGroup.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue17366Test.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue17366Test.png
index c8404881fe39..a14ae7cee138 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue17366Test.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue17366Test.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/NavigationPageTitle.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/NavigationPageTitle.png
new file mode 100644
index 000000000000..00e6bc730e7f
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/NavigationPageTitle.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ValidateFrameOffsets.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ValidateFrameOffsets.png
new file mode 100644
index 000000000000..737bd08721e0
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ValidateFrameOffsets.png differ
diff --git a/src/Controls/tests/Xaml.UnitTests/AppResources/Style24849.xaml b/src/Controls/tests/Xaml.UnitTests/AppResources/Style24849.xaml
new file mode 100644
index 000000000000..afab77d71927
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/AppResources/Style24849.xaml
@@ -0,0 +1,26 @@
+
+
+ White
+ #3c3c3b
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/Xaml.UnitTests/AppResources/Style24849.xaml.cs b/src/Controls/tests/Xaml.UnitTests/AppResources/Style24849.xaml.cs
new file mode 100644
index 000000000000..e2be8a4d8adc
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/AppResources/Style24849.xaml.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Core.UnitTests;
+using NUnit.Framework;
+
+namespace Microsoft.Maui.Controls.Xaml.UnitTests
+{
+ public partial class Style24849 : ResourceDictionary
+ {
+ public Style24849()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui20244.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui20244.xaml
new file mode 100644
index 000000000000..284a8613664f
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui20244.xaml
@@ -0,0 +1,20 @@
+
+
+
+ *, *, *, *, *, auto
+
+
+
+
+ #FF0000
+ *, *, auto
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui20244.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui20244.xaml.cs
new file mode 100644
index 000000000000..243c52ba1108
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui20244.xaml.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using Microsoft.Maui.ApplicationModel;
+using Microsoft.Maui.Controls.Core.UnitTests;
+using Microsoft.Maui.Controls.Shapes;
+using Microsoft.Maui.Devices;
+using Microsoft.Maui.Dispatching;
+
+using Microsoft.Maui.Graphics;
+using Microsoft.Maui.UnitTests;
+using NUnit.Framework;
+
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public partial class Maui20244 :ContentPage
+{
+ public Maui20244()
+ {
+ InitializeComponent();
+ }
+
+ public Maui20244(bool useCompiledXaml)
+ {
+ //this stub will be replaced at compile time
+ }
+
+ [TestFixture]
+ class Test
+ {
+ [SetUp]
+ public void Setup()
+ {
+ Application.SetCurrentApplication(new MockApplication());
+ DispatcherProvider.SetCurrent(new DispatcherProviderStub());
+ }
+
+ [TearDown] public void TearDown() => AppInfo.SetCurrent(null);
+
+ [Test]
+ public void RowDefStaticResource([Values(false, true)] bool useCompiledXaml)
+ {
+ if (useCompiledXaml)
+ MockCompiler.Compile(typeof(Maui20244));
+
+ var page = new Maui20244(useCompiledXaml);
+ var grid = page.grid;
+
+ Assert.That(grid.RowDefinitions.Count, Is.EqualTo(6));
+ Assert.That(grid.RowDefinitions[0].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star)));
+ Assert.That(grid.RowDefinitions[1].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star)));
+ Assert.That(grid.RowDefinitions[2].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star)));
+ Assert.That(grid.RowDefinitions[3].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star)));
+ Assert.That(grid.RowDefinitions[4].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star)));
+ Assert.That(grid.RowDefinitions[5].Height, Is.EqualTo(new GridLength(1, GridUnitType.Auto)));
+
+ Assert.That(grid.ColumnDefinitions.Count, Is.EqualTo(3));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui24849.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui24849.xaml
new file mode 100644
index 000000000000..d8e47a6bf560
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui24849.xaml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui24849.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui24849.xaml.cs
new file mode 100644
index 000000000000..3617240d5897
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui24849.xaml.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using Microsoft.Maui.ApplicationModel;
+using Microsoft.Maui.Controls.Core.UnitTests;
+using Microsoft.Maui.Controls.Shapes;
+using Microsoft.Maui.Devices;
+using Microsoft.Maui.Dispatching;
+
+using Microsoft.Maui.Graphics;
+using Microsoft.Maui.UnitTests;
+using NUnit.Framework;
+
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public partial class Maui24849 : ContentPage
+{
+ public Maui24849()
+ {
+ InitializeComponent();
+ }
+
+ public Maui24849(bool useCompiledXaml)
+ {
+ //this stub will be replaced at compile time
+ }
+
+ [TestFixture]
+ class Test
+ {
+ MockDeviceInfo mockDeviceInfo;
+
+ [SetUp]
+ public void Setup()
+ {
+ Application.SetCurrentApplication(new MockApplication());
+ DeviceInfo.SetCurrent(mockDeviceInfo = new MockDeviceInfo());
+ DispatcherProvider.SetCurrent(new DispatcherProviderStub());
+ }
+
+
+ [TearDown] public void TearDown()
+ {
+ AppInfo.SetCurrent(null);
+ DeviceInfo.SetCurrent(null);
+ }
+
+ [Test]
+ public void VSGReturnsToNormal([Values(false, true)] bool useCompiledXaml)
+ {
+ var app = new MockApplication();
+ app.Resources.Add(new Style24849());
+ var page = new Maui24849(useCompiledXaml);
+
+ app.MainPage = page;
+
+ Assert.That(page.button.IsEnabled, Is.False);
+ Assert.That(page.button.TextColor, Is.EqualTo(Color.FromHex("#3c3c3b")));
+
+ page.button.IsEnabled = true;
+ Assert.That(page.button.IsEnabled, Is.True);
+ Assert.That(page.button.TextColor, Is.EqualTo(Colors.White));
+
+ page.button.IsEnabled = false;
+ Assert.That(page.button.IsEnabled, Is.False);
+ Assert.That(page.button.TextColor, Is.EqualTo(Color.FromHex("#3c3c3b")));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Core/src/Handlers/View/ViewHandler.Windows.cs b/src/Core/src/Handlers/View/ViewHandler.Windows.cs
index f7b36aa79dae..159757c930d1 100644
--- a/src/Core/src/Handlers/View/ViewHandler.Windows.cs
+++ b/src/Core/src/Handlers/View/ViewHandler.Windows.cs
@@ -1,31 +1,35 @@
#nullable enable
using System;
-using System.Collections.Generic;
-using System.ComponentModel;
+using System.Runtime.CompilerServices;
using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Input;
using PlatformView = Microsoft.UI.Xaml.FrameworkElement;
namespace Microsoft.Maui.Handlers
{
public partial class ViewHandler
{
+ readonly static ConditionalWeakTable FocusManagerMapping = new();
+
+ static ViewHandler()
+ {
+ FocusManager.GotFocus += FocusManager_GotFocus;
+ FocusManager.LostFocus += FocusManager_LostFocus;
+ }
+
partial void ConnectingHandler(PlatformView? platformView)
{
- if (platformView != null)
+ if (platformView is not null)
{
- platformView.GotFocus += OnPlatformViewGotFocus;
- platformView.LostFocus += OnPlatformViewLostFocus;
+ FocusManagerMapping.Add(platformView, this);
}
}
partial void DisconnectingHandler(PlatformView platformView)
{
+ FocusManagerMapping.Remove(platformView);
UpdateIsFocused(false);
-
- platformView.GotFocus -= OnPlatformViewGotFocus;
- platformView.LostFocus -= OnPlatformViewLostFocus;
}
static partial void MappingFrame(IViewHandler handler, IView view)
@@ -88,7 +92,9 @@ public static void MapAnchorY(IViewHandler handler, IView view)
public static void MapToolbar(IViewHandler handler, IView view)
{
if (view is IToolbarElement tb)
+ {
MapToolbar(handler, tb);
+ }
}
internal static void MapToolbar(IElementHandler handler, IToolbarElement toolbarElement)
@@ -133,25 +139,35 @@ internal static void MapContextFlyout(IElementHandler handler, IContextFlyoutEle
}
}
- void OnPlatformViewGotFocus(object sender, RoutedEventArgs args)
+ static void FocusManager_GotFocus(object? sender, FocusManagerGotFocusEventArgs e)
{
- UpdateIsFocused(true);
+ if (e.NewFocusedElement is PlatformView platformView && FocusManagerMapping.TryGetValue(platformView, out ViewHandler? handler))
+ {
+ handler.UpdateIsFocused(true);
+ }
}
- void OnPlatformViewLostFocus(object sender, RoutedEventArgs args)
+ static void FocusManager_LostFocus(object? sender, FocusManagerLostFocusEventArgs e)
{
- UpdateIsFocused(false);
+ if (e.OldFocusedElement is PlatformView platformView && FocusManagerMapping.TryGetValue(platformView, out ViewHandler? handler))
+ {
+ handler.UpdateIsFocused(false);
+ }
}
void UpdateIsFocused(bool isFocused)
{
- if (VirtualView == null)
+ if (VirtualView is not { } virtualView)
+ {
return;
+ }
- bool updateIsFocused = (isFocused && !VirtualView.IsFocused) || (!isFocused && VirtualView.IsFocused);
+ bool updateIsFocused = (isFocused && !virtualView.IsFocused) || (!isFocused && virtualView.IsFocused);
if (updateIsFocused)
- VirtualView.IsFocused = isFocused;
+ {
+ virtualView.IsFocused = isFocused;
+ }
}
}
}
\ No newline at end of file
diff --git a/src/Core/src/Platform/Windows/ViewExtensions.cs b/src/Core/src/Platform/Windows/ViewExtensions.cs
index db87407376f7..0237a55a5c27 100644
--- a/src/Core/src/Platform/Windows/ViewExtensions.cs
+++ b/src/Core/src/Platform/Windows/ViewExtensions.cs
@@ -36,7 +36,9 @@ public static void Focus(this FrameworkElement platformView, FocusRequest reques
public static void Unfocus(this FrameworkElement platformView, IView view)
{
if (platformView is Control control)
+ {
UnfocusControl(control);
+ }
}
public static void UpdateVisibility(this FrameworkElement platformView, IView view)
@@ -379,8 +381,10 @@ internal static Graphics.Rect GetBoundingBox(this FrameworkElement? platformView
internal static void UnfocusControl(Control control)
{
- if (control == null || !control.IsEnabled)
+ if (!control.IsEnabled)
+ {
return;
+ }
var isTabStop = control.IsTabStop;
control.IsTabStop = false;
diff --git a/src/Core/tests/DeviceTests.Shared/HandlerTests/Focus/FocusHandlerTests.cs b/src/Core/tests/DeviceTests.Shared/HandlerTests/Focus/FocusHandlerTests.cs
index 9baba69af963..8e20e9784cdc 100644
--- a/src/Core/tests/DeviceTests.Shared/HandlerTests/Focus/FocusHandlerTests.cs
+++ b/src/Core/tests/DeviceTests.Shared/HandlerTests/Focus/FocusHandlerTests.cs
@@ -97,7 +97,7 @@ await AttachAndRun(layout, async (contentViewHandler) =>
Assert.True(inputControl1.IsFocused);
Assert.False(inputControl2.IsFocused);
- // UNfocus the first control (revert the focus)
+ // Unfocus the first control (revert the focus)
inputControl1.Handler.Invoke(nameof(IView.Unfocus));
// assert
diff --git a/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs b/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs
index 045ca092eda5..a7c83e7f9be8 100644
--- a/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs
+++ b/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs
@@ -13,6 +13,7 @@
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Graphics.DirectX;
using Windows.Storage.Streams;
+using Microsoft.UI.Xaml.Input;
using Xunit;
using Xunit.Sdk;
using WColor = Windows.UI.Color;
@@ -49,7 +50,8 @@ public static Task SendKeyboardReturnType(this FrameworkElement view, ReturnType
public static async Task WaitForFocused(this FrameworkElement view, int timeout = 1000)
{
TaskCompletionSource focusSource = new TaskCompletionSource();
- view.GotFocus += OnFocused;
+
+ FocusManager.GotFocus += OnFocused;
try
{
@@ -57,20 +59,24 @@ public static async Task WaitForFocused(this FrameworkElement view, int timeout
}
finally
{
- view.GotFocus -= OnFocused;
+ FocusManager.GotFocus -= OnFocused;
}
- void OnFocused(object? sender, RoutedEventArgs e)
+ void OnFocused(object? sender, FocusManagerGotFocusEventArgs e)
{
- view.GotFocus -= OnFocused;
- focusSource.SetResult();
+ if (e.NewFocusedElement == view)
+ {
+ FocusManager.GotFocus -= OnFocused;
+ focusSource.SetResult();
+ }
}
}
public static async Task WaitForUnFocused(this FrameworkElement view, int timeout = 1000)
{
TaskCompletionSource focusSource = new TaskCompletionSource();
- view.LostFocus += OnUnFocused;
+
+ FocusManager.LostFocus += OnUnFocused;
try
{
@@ -78,13 +84,16 @@ public static async Task WaitForUnFocused(this FrameworkElement view, int timeou
}
finally
{
- view.LostFocus -= OnUnFocused;
+ FocusManager.LostFocus -= OnUnFocused;
}
- void OnUnFocused(object? sender, RoutedEventArgs e)
+ void OnUnFocused(object? sender, FocusManagerLostFocusEventArgs e)
{
- view.LostFocus -= OnUnFocused;
- focusSource.SetResult();
+ if (e.OldFocusedElement == view)
+ {
+ FocusManager.LostFocus -= OnUnFocused;
+ focusSource.SetResult();
+ }
}
}