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/NuGet.config b/NuGet.config
index 60455ce31c6f..4d9a98e5b813 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -25,7 +25,7 @@
-
+
diff --git a/docs/design/FeatureSwitches.md b/docs/design/FeatureSwitches.md
index d38107f40bf2..1f02e03cabd1 100644
--- a/docs/design/FeatureSwitches.md
+++ b/docs/design/FeatureSwitches.md
@@ -11,6 +11,7 @@ The following switches are toggled for applications running on Mono for `TrimMod
| MauiQueryPropertyAttributeSupport | Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported | When disabled, the `[QueryProperty(...)]` attributes won't be used to set values to properties when navigating. |
| MauiImplicitCastOperatorsUsageViaReflectionSupport | Microsoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported | When disabled, MAUI won't look for implicit cast operators when converting values from one type to another. This feature is not trim-compatible. |
| _MauiBindingInterceptorsSupport | Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported | When disabled, MAUI won't intercept any calls to `SetBinding` methods and try to compile them. Enabled by default. |
+| MauiEnableXamlCBindingWithSourceCompilation | Microsoft.Maui.RuntimeFeature.XamlCBindingWithSourceCompilationEnabled | When enabled, MAUI will compile all bindings, including those where the `Source` property is used. |
## MauiEnableIVisualAssemblyScanning
@@ -61,3 +62,12 @@ Compiled binding in XAML:
```xml
```
+
+## MauiEnableXamlCBindingWithSourceCompilation
+
+XamlC skipped compilation of bindings with the `Source` property set to any value in previous releases. Some bindings might start producing build errors or start failing at runtime after this feature is enabled. After enabling this feature, make sure all bindings have the right `x:DataType` so they are compiled correctly. For bindings which should not be compiled, clear the data type like this:
+```
+{Binding MyProperty, Source={x:Reference MyTarget}, x:DataType={x:Null}}
+```
+
+This feature is disabled by default, unless `TrimMode=true` or `PublishAot=true`. For fully trimmed and NativeAOT apps, the feature is enabled.
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index df25e1a53a9a..86dbe8673ce2 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -8,9 +8,9 @@
https://github.com/dotnet/runtime
6f23d04dc2b2039e9eaf97bee2ac02a77ce56b21
-
+
https://github.com/dotnet/android
- c77c22c9e281e518fddfbd2f3ec41939ba631778
+ b795a653460417af9da75f94e4a487a2665faa7f
https://github.com/xamarin/xamarin-macios
@@ -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 15c5442fc39a..4fb0c857efe7 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -27,7 +27,7 @@
- 8.0.70
+ 8.0.82
9.0.100-rc.2.24426.11
$(MicrosoftNETSdkPackageVersion)
@@ -50,7 +50,7 @@
9.0.0-rc.2.24463.7
9.0.0-rc.2.24463.7
- 35.0.0-rc.2.87
+ 35.0.0-rc.2.134
17.5.9334-net9-rc2
14.5.9334-net9-rc2
@@ -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/maui-templates.yml b/eng/pipelines/common/maui-templates.yml
index 7dc7e513dfe6..2648a6e37fc9 100644
--- a/eng/pipelines/common/maui-templates.yml
+++ b/eng/pipelines/common/maui-templates.yml
@@ -148,6 +148,7 @@ jobs:
- template: provision.yml
parameters:
skipXcode: ${{ eq(RunPlatform.testName, 'RunOnAndroid') }}
+ skipAndroidImages: ${{ ne(RunPlatform.testName, 'RunOnAndroid') }}
checkoutDirectory: ${{ parameters.checkoutDirectory }}
- task: DownloadBuildArtifacts@0
diff --git a/eng/pipelines/common/provision.yml b/eng/pipelines/common/provision.yml
index 9911fff49a9a..9af973d9291b 100644
--- a/eng/pipelines/common/provision.yml
+++ b/eng/pipelines/common/provision.yml
@@ -57,20 +57,21 @@ steps:
# Setup JDK Paths (gradle needs it)
- bash: |
- echo "##vso[task.setvariable variable=JI_JAVA_HOME]$(JAVA_HOME_11_X64)"
- echo "##vso[task.setvariable variable=JAVA_HOME]$(JAVA_HOME_11_X64)"
+ echo "##vso[task.setvariable variable=JI_JAVA_HOME]$(JAVA_HOME_17_X64)"
+ echo "##vso[task.setvariable variable=JAVA_HOME]$(JAVA_HOME_17_X64)"
+ brew install --cask microsoft-openjdk@17
displayName: 'Setup JDK Paths'
condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin'))
# Configure VS Mac for Xcode
# This seems to be needed or else it will pick other xcode for example on xharness
- - bash: |
- set -x
- mkdir -p ~/Library/Preferences/Xamarin
- rm -f ~/Library/Preferences/Xamarin/Settings.plist
- /usr/libexec/PlistBuddy -c "add :AppleSdkRoot string $(dirname $(dirname $(xcode-select -p)))" ~/Library/Preferences/Xamarin/Settings.plist || true
- cat ~/Library/Preferences/Xamarin/Settings.plist || true
- displayName: 'Configure Visual Studio'
- condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin'))
+ # - bash: |
+ # set -x
+ # mkdir -p ~/Library/Preferences/Xamarin
+ # rm -f ~/Library/Preferences/Xamarin/Settings.plist
+ # /usr/libexec/PlistBuddy -c "add :AppleSdkRoot string $(dirname $(dirname $(xcode-select -p)))" ~/Library/Preferences/Xamarin/Settings.plist || true
+ # cat ~/Library/Preferences/Xamarin/Settings.plist || true
+ # displayName: 'Configure Visual Studio'
+ # condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin'))
# Prepare Windows
# Provision Additional Software
@@ -90,10 +91,10 @@ steps:
SKIP_ANDROID_API_IMAGES: 'true'
- pwsh: |
- if ($env:JAVA_HOME_11_X64) {
- $env:JAVA_HOME = $env:JAVA_HOME_11_X64
+ if ($env:JAVA_HOME_17_X64) {
+ $env:JAVA_HOME = $env:JAVA_HOME_17_X64
} else {
- $path = (Get-ChildItem $env:ProgramFiles\Microsoft\jdk-11.*\bin\java.exe) | Select-Object -First 1
+ $path = (Get-ChildItem $env:ProgramFiles\Microsoft\jdk-17.*\bin\java.exe) | Select-Object -First 1
if ($path -and (Test-Path $path)) {
$env:JAVA_HOME = $path.Directory.Parent.FullName
}
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/eng/pipelines/device-tests.yml b/eng/pipelines/device-tests.yml
index 473e91533789..a9e60ea4edb9 100644
--- a/eng/pipelines/device-tests.yml
+++ b/eng/pipelines/device-tests.yml
@@ -66,7 +66,7 @@ parameters:
name: $(androidTestsVmPool)
vmImage: $(androidTestsVmImage)
demands:
- - macOS.Name -equals Ventura
+ - macOS.Name -equals Sonoma
- macOS.Architecture -equals x64
- name: iosPool
diff --git a/eng/provisioning/provisioning.csx b/eng/provisioning/provisioning.csx
index b45e4c333668..96d382861008 100644
--- a/eng/provisioning/provisioning.csx
+++ b/eng/provisioning/provisioning.csx
@@ -1,7 +1,7 @@
if (IsMac)
{
ForceJavaCleanup();
- MicrosoftOpenJdk ("11.0.13.8.1");
+ MicrosoftOpenJdk ("17.0.12");
//this is needed for tools on macos like for nuget pack additional target and for classic xamarin projects
Item("https://download.mono-project.com/archive/6.12.0/macos-10-universal/MonoFramework-MDK-6.12.0.206.macos10.xamarin.universal.pkg");
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/Core/Compatibility.ControlGallery.Core.csproj b/src/Compatibility/ControlGallery/src/Core/Compatibility.ControlGallery.Core.csproj
index 103a3fd14197..4428b9ebcff7 100644
--- a/src/Compatibility/ControlGallery/src/Core/Compatibility.ControlGallery.Core.csproj
+++ b/src/Compatibility/ControlGallery/src/Core/Compatibility.ControlGallery.Core.csproj
@@ -9,6 +9,7 @@
True
+ true
TRACE;DEBUG;PERF;APP
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/samples/Controls.Sample/Maui.Controls.Sample.csproj b/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj
index 51663e1473e2..308ab6a0e930 100644
--- a/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj
+++ b/src/Controls/samples/Controls.Sample/Maui.Controls.Sample.csproj
@@ -23,6 +23,7 @@
1.0
1
<_FastDeploymentDiagnosticLogging>True
+ true
diff --git a/src/Controls/samples/Directory.Build.props b/src/Controls/samples/Directory.Build.props
index a1eec8a4e555..9ca30ddb197d 100644
--- a/src/Controls/samples/Directory.Build.props
+++ b/src/Controls/samples/Directory.Build.props
@@ -4,6 +4,7 @@
true
Maui
$(WarningsNotAsErrors);XC0022;XC0023
+ true
-
+
\ No newline at end of file
diff --git a/src/Controls/src/Build.Tasks/BuildException.cs b/src/Controls/src/Build.Tasks/BuildException.cs
index 49e11db2259a..e54f266069bd 100644
--- a/src/Controls/src/Build.Tasks/BuildException.cs
+++ b/src/Controls/src/Build.Tasks/BuildException.cs
@@ -58,6 +58,7 @@ class BuildExceptionCode
public static BuildExceptionCode BindingWithoutDataType = new BuildExceptionCode("XC", 0022, nameof(BindingWithoutDataType), "https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/compiled-bindings"); //warning
public static BuildExceptionCode BindingWithNullDataType = new BuildExceptionCode("XC", 0023, nameof(BindingWithNullDataType), "https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/compiled-bindings"); //warning
public static BuildExceptionCode BindingWithXDataTypeFromOuterScope = new BuildExceptionCode("XC", 0024, nameof(BindingWithXDataTypeFromOuterScope), "https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/compiled-bindings"); //warning
+ public static BuildExceptionCode BindingWithSourceCompilationSkipped = new BuildExceptionCode("XC", 0025, nameof(BindingWithSourceCompilationSkipped), "https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/compiled-bindings"); //warning
//Bindings, conversions
public static BuildExceptionCode Conversion = new BuildExceptionCode("XC", 0040, nameof(Conversion), "");
@@ -92,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 715ff81f631a..39907273d5d0 100644
--- a/src/Controls/src/Build.Tasks/ErrorMessages.resx
+++ b/src/Controls/src/Build.Tasks/ErrorMessages.resx
@@ -144,6 +144,9 @@
Binding might be compiled incorrectly since the x:DataType annotation comes from an outer scope. Make sure you annotate all DataTemplate XAML elements with the correct x:DataType. See https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/compiled-bindings for more information.
+
+ Binding was not compiled because it has an explicitly set Source property and compilation of bindings with Source is not enabled. Consider enabling this optimization by setting the <MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation> in your project file and make sure the correct x:DataType is specified for this binding. See https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/compiled-bindings for more information.
+
Binding: Property "{0}" not found on "{1}".
0 is property name, 1 is type name
@@ -265,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/ILContext.cs b/src/Controls/src/Build.Tasks/ILContext.cs
index 3a0f8cac3686..56630b15f561 100644
--- a/src/Controls/src/Build.Tasks/ILContext.cs
+++ b/src/Controls/src/Build.Tasks/ILContext.cs
@@ -20,6 +20,7 @@ public ILContext(ILProcessor il, MethodBody body, ModuleDefinition module, XamlC
ParentContextValues = parentContextValues;
Module = module;
Cache = cache;
+ CompileBindingsWithSource = false;
}
public XamlCache Cache { get; private set; }
@@ -46,5 +47,7 @@ public ILContext(ILProcessor il, MethodBody body, ModuleDefinition module, XamlC
public TaskLoggingHelper LoggingHelper { get; internal set; }
public bool ValidateOnly { get; set; }
+
+ public bool CompileBindingsWithSource { get; set; }
}
}
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 72ff2f32ae20..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
@@ -300,10 +299,21 @@ public static IEnumerable ProvideValue(VariableDefinitionReference
if (bindingExtensionType.HasValue)
{
- if (TryCompileBindingPath(node, context, vardefref.VariableDefinition, bindingExtensionType.Value, isStandaloneBinding: bpRef is null, out var instructions))
+ // for backwards compatibility, it is possible to disable compilation of bindings with the `Source` property via a feature switch
+ // this feature switch is enabled by default only for NativeAOT and full trimming mode
+ bool hasSource = node.Properties.ContainsKey(new XmlName("", "Source"));
+ bool skipBindingCompilation = hasSource && !context.CompileBindingsWithSource;
+ if (!skipBindingCompilation)
{
- foreach (var instruction in instructions)
- yield return instruction;
+ if (TryCompileBindingPath(node, context, vardefref.VariableDefinition, bindingExtensionType.Value, isStandaloneBinding: bpRef is null, out var instructions))
+ {
+ foreach (var instruction in instructions)
+ yield return instruction;
+ }
+ }
+ else
+ {
+ context.LoggingHelper.LogWarningOrError(BuildExceptionCode.BindingWithSourceCompilationSkipped, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, null);
}
}
@@ -327,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;
@@ -351,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;
@@ -1099,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);
@@ -1107,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,
@@ -1311,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)
@@ -1442,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;
@@ -1759,6 +1791,7 @@ static void SetDataTemplate(IElementNode parentNode, ElementNode node, ILContext
XamlFilePath = parentContext.XamlFilePath,
LoggingHelper = parentContext.LoggingHelper,
ValidateOnly = parentContext.ValidateOnly,
+ CompileBindingsWithSource = parentContext.CompileBindingsWithSource,
};
//Instanciate nested class
diff --git a/src/Controls/src/Build.Tasks/XamlCTask.cs b/src/Controls/src/Build.Tasks/XamlCTask.cs
index 49ca7b5fab4d..13a66bdb9b92 100644
--- a/src/Controls/src/Build.Tasks/XamlCTask.cs
+++ b/src/Controls/src/Build.Tasks/XamlCTask.cs
@@ -128,6 +128,7 @@ public class XamlCTask : XamlTask
public bool OptimizeIL { get; set; } = true;
public bool DefaultCompile { get; set; }
public bool ForceCompile { get; set; }
+ public bool CompileBindingsWithSource { get; set; }
public string TargetFramework { get; set; }
public int WarningLevel { get; set; } = 4; //unused so far
@@ -415,6 +416,7 @@ bool TryCoreCompile(MethodDefinition initComp, ILRootNode rootnode, string xamlF
XamlFilePath = xamlFilePath,
LoggingHelper = loggingHelper,
ValidateOnly = ValidateOnly,
+ CompileBindingsWithSource = CompileBindingsWithSource,
};
diff --git a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets
index b54df858abdd..1218ee356dba 100644
--- a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets
+++ b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets
@@ -175,6 +175,7 @@
DebugSymbols = "$(DebugSymbols)"
DebugType = "$(DebugType)"
DefaultCompile = "true"
+ CompileBindingsWithSource = "$(MauiEnableXamlCBindingWithSourceCompilation)"
ValidateOnly = "$(_MauiXamlCValidateOnly)"
TargetFramework = "$(TargetFramework)"
KeepXamlResources = "$(MauiKeepXamlResources)"
@@ -225,7 +226,7 @@
Text="The %24(TargetFrameworkVersion) for $(ProjectName) ($(TargetFrameworkVersion)) is less than the minimum required %24(TargetFrameworkVersion) for Microsoft.Maui ($(MinTargetFrameworkVersionForMaui)). You need to increase the %24(TargetFrameworkVersion) for $(ProjectName)." />
-
+
false
@@ -233,6 +234,7 @@
false
false
false
+ true
+
diff --git a/src/Controls/src/Core/Binding.cs b/src/Controls/src/Core/Binding.cs
index efd66e5fee7e..80810bc64f10 100644
--- a/src/Controls/src/Core/Binding.cs
+++ b/src/Controls/src/Core/Binding.cs
@@ -124,10 +124,16 @@ internal override void Apply(object context, BindableObject bindObj, BindablePro
var isApplied = IsApplied;
var bindingContext = src ?? Context ?? context;
- if (DataType != null && bindingContext != null && !DataType.IsAssignableFrom(bindingContext.GetType()))
+
+ // Do not check type mismatch if this is a binding with Source and compilation of bindings with Source is disabled
+ bool skipTypeMismatchCheck = Source is not null && !RuntimeFeature.IsXamlCBindingWithSourceCompilationEnabled;
+ if (!skipTypeMismatchCheck)
{
- BindingDiagnostics.SendBindingFailure(this, "Binding", "Mismatch between the specified x:DataType and the current binding context");
- bindingContext = null;
+ if (DataType != null && bindingContext != null && !DataType.IsAssignableFrom(bindingContext.GetType()))
+ {
+ BindingDiagnostics.SendBindingFailure(this, "Binding", $"Mismatch between the specified x:DataType ({DataType}) and the current binding context ({bindingContext.GetType()}).");
+ bindingContext = null;
+ }
}
base.Apply(bindingContext, bindObj, targetProperty, fromBindingContextChanged, specificity);
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/Core/TypedBinding.cs b/src/Controls/src/Core/TypedBinding.cs
index 1718cf48bfcc..cdbe7d6a9b5b 100644
--- a/src/Controls/src/Core/TypedBinding.cs
+++ b/src/Controls/src/Core/TypedBinding.cs
@@ -288,6 +288,11 @@ internal override void Unapply(bool fromBindingContextChanged = false)
internal void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget, SetterSpecificity specificity)
{
var isTSource = sourceObject is TSource;
+ if (!isTSource && sourceObject is not null)
+ {
+ BindingDiagnostics.SendBindingFailure(this, "Binding", $"Mismatch between the specified x:DataType ({typeof(TSource)}) and the current binding context ({sourceObject.GetType()})");
+ }
+
var mode = this.GetRealizedMode(property);
if ((mode == BindingMode.OneWay || mode == BindingMode.OneTime) && fromTarget)
return;
diff --git a/src/Controls/src/Xaml/MarkupExtensions/BindingExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/BindingExtension.cs
index bbbe745a889e..8800de4bf1ac 100644
--- a/src/Controls/src/Xaml/MarkupExtensions/BindingExtension.cs
+++ b/src/Controls/src/Xaml/MarkupExtensions/BindingExtension.cs
@@ -38,8 +38,8 @@ BindingBase IMarkupExtension.ProvideValue(IServiceProvider serviceP
return TypedBinding;
[UnconditionalSuppressMessage("TrimAnalysis", "IL2026",
- Justification = "This code is only reachable in XamlC compiled code when there is a missing x:DataType and the binding could not be compiled. " +
- "In that case, we produce a warning that the binding could not be compiled.")]
+ Justification = "If this method is invoked, we have already produced warnings in XamlC " +
+ "when the compilation of this binding failed or was skipped.")]
BindingBase CreateBinding()
{
Type bindingXDataType = null;
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/SourceGen.UnitTests/SourceGen.UnitTests.csproj b/src/Controls/tests/SourceGen.UnitTests/SourceGen.UnitTests.csproj
index dbda40919279..509646f069b3 100644
--- a/src/Controls/tests/SourceGen.UnitTests/SourceGen.UnitTests.csproj
+++ b/src/Controls/tests/SourceGen.UnitTests/SourceGen.UnitTests.csproj
@@ -10,6 +10,7 @@
false
enable
true
+ true
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/Controls.TestCases.HostApp.csproj b/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
index 7c500859f43a..8167f9f23171 100644
--- a/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
+++ b/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj
@@ -11,6 +11,7 @@
maccatalyst-x64
maccatalyst-arm64
true
+ true
diff --git a/src/Controls/tests/TestCases.HostApp/Directory.Build.props b/src/Controls/tests/TestCases.HostApp/Directory.Build.props
index a4b44f259892..f815ebc9791a 100644
--- a/src/Controls/tests/TestCases.HostApp/Directory.Build.props
+++ b/src/Controls/tests/TestCases.HostApp/Directory.Build.props
@@ -4,6 +4,7 @@
true
Maui
$(WarningsNotAsErrors);XC0022;XC0023
+ true
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/Controls.Xaml.UnitTests.csproj b/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj
index f0dccf50d07a..d58073ca50f3 100644
--- a/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj
+++ b/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj
@@ -6,9 +6,10 @@
Microsoft.Maui.Controls.Xaml.UnitTests
4
$(NoWarn);0672;0219;0414;CS0436;CS0618
- $(WarningsNotAsErrors);XC0618;XC0022;XC0023;XC0045
+ $(WarningsNotAsErrors);XC0618;XC0022;XC0023;XC0025;XC0045
false
true
+ true
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/Maui23711.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui23711.xaml
new file mode 100644
index 000000000000..d5e2af4c79b9
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui23711.xaml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui23711.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui23711.xaml.cs
new file mode 100644
index 000000000000..c296426aa64f
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui23711.xaml.cs
@@ -0,0 +1,66 @@
+using System.Linq;
+using Microsoft.Maui.ApplicationModel;
+using Microsoft.Maui.Controls.Core.UnitTests;
+using Microsoft.Maui.Devices;
+using Microsoft.Maui.Dispatching;
+
+using Microsoft.Maui.UnitTests;
+using NUnit.Framework;
+
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+[XamlCompilation(XamlCompilationOptions.Skip)]
+public partial class Maui23711 : ContentPage
+{
+ public Maui23711()
+ {
+ InitializeComponent();
+ }
+
+ public Maui23711(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 UsesReflectionBasedBindingsWhenCompilationOfBindingsWithSourceIsDisabled([Values(false, true)] bool compileBindingsWithSource)
+ {
+ MockCompiler.Compile(typeof(Maui23711), out MethodDefinition methodDefinition, compileBindingsWithSource: compileBindingsWithSource);
+ Assert.AreEqual(compileBindingsWithSource, ContainsTypedBindingInstantiation(methodDefinition));
+ }
+
+ static bool ContainsTypedBindingInstantiation(MethodDefinition methodDef)
+ => methodDef.Body.Instructions.Any(instruction =>
+ instruction.OpCode == OpCodes.Newobj
+ && instruction.Operand is MethodReference methodRef
+ && methodRef.DeclaringType.Name == "TypedBinding`2");
+ }
+}
+
+public class DeclaredModel
+{
+ public string Value { get; set; }
+}
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/Controls/tests/Xaml.UnitTests/MockCompiler.cs b/src/Controls/tests/Xaml.UnitTests/MockCompiler.cs
index cc5baaefd844..4a4a41bc6b06 100644
--- a/src/Controls/tests/Xaml.UnitTests/MockCompiler.cs
+++ b/src/Controls/tests/Xaml.UnitTests/MockCompiler.cs
@@ -9,12 +9,21 @@ namespace Microsoft.Maui.Controls.Xaml.UnitTests
{
public static class MockCompiler
{
- public static void Compile(Type type, string targetFramework = null, bool treatWarningsAsErrors = false)
+ public static void Compile(
+ Type type,
+ string targetFramework = null,
+ bool treatWarningsAsErrors = false,
+ bool compileBindingsWithSource = true)
{
- Compile(type, out _, targetFramework, treatWarningsAsErrors);
+ Compile(type, out _, targetFramework, treatWarningsAsErrors, compileBindingsWithSource);
}
- public static void Compile(Type type, out MethodDefinition methodDefinition, string targetFramework = null, bool treatWarningsAsErrors = false)
+ public static void Compile(
+ Type type,
+ out MethodDefinition methodDefinition,
+ string targetFramework = null,
+ bool treatWarningsAsErrors = false,
+ bool compileBindingsWithSource = true)
{
methodDefinition = null;
var assembly = type.Assembly.Location;
@@ -33,6 +42,7 @@ public static void Compile(Type type, out MethodDefinition methodDefinition, str
Type = type.FullName,
TargetFramework = targetFramework,
TreatWarningsAsErrors = treatWarningsAsErrors,
+ CompileBindingsWithSource = compileBindingsWithSource,
BuildEngine = new MSBuild.UnitTests.DummyBuildEngine()
};
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/src/RuntimeFeature.cs b/src/Core/src/RuntimeFeature.cs
index 4f16a8120593..bab4e6e9b0bb 100644
--- a/src/Core/src/RuntimeFeature.cs
+++ b/src/Core/src/RuntimeFeature.cs
@@ -19,6 +19,7 @@ internal static class RuntimeFeature
private const bool IsQueryPropertyAttributeSupportedByDefault = true;
private const bool IsImplicitCastOperatorsUsageViaReflectionSupportedByDefault = true;
private const bool AreBindingInterceptorsSupportedByDefault = true;
+ private const bool IsXamlCBindingWithSourceCompilationEnabledByDefault = false;
#pragma warning disable IL4000 // Return value does not match FeatureGuardAttribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute'.
#if NET9_0_OR_GREATER
@@ -57,10 +58,21 @@ internal static bool IsShellSearchResultsRendererDisplayMemberNameSupported
? isSupported
: IsImplicitCastOperatorsUsageViaReflectionSupportedByDefault;
+#if NET9_0_OR_GREATER
+ [FeatureSwitchDefinition("Microsoft.Maui.RuntimeFeature.Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported")]
+#endif
internal static bool AreBindingInterceptorsSupported =>
AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported", out bool areSupported)
? areSupported
: AreBindingInterceptorsSupportedByDefault;
+
+#if NET9_0_OR_GREATER
+ [FeatureSwitchDefinition("Microsoft.Maui.RuntimeFeature.IsXamlCBindingWithSourceCompilationEnabled")]
+#endif
+ internal static bool IsXamlCBindingWithSourceCompilationEnabled =>
+ AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.IsXamlCBindingWithSourceCompilationEnabled", out bool areSupported)
+ ? areSupported
+ : IsXamlCBindingWithSourceCompilationEnabledByDefault;
#pragma warning restore IL4000
}
}
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/Core/tests/DeviceTests/Core.DeviceTests.csproj b/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
index aac3fcc6ebf2..32cec6e277d7 100644
--- a/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
+++ b/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
@@ -33,6 +33,11 @@
+
+
+ true
+
+
diff --git a/src/Essentials/samples/Directory.Build.props b/src/Essentials/samples/Directory.Build.props
index a1eec8a4e555..41af9256608e 100644
--- a/src/Essentials/samples/Directory.Build.props
+++ b/src/Essentials/samples/Directory.Build.props
@@ -4,6 +4,7 @@
true
Maui
$(WarningsNotAsErrors);XC0022;XC0023
+ true
diff --git a/src/TestUtils/src/DeviceTests.Runners/TestUtils.DeviceTests.Runners.csproj b/src/TestUtils/src/DeviceTests.Runners/TestUtils.DeviceTests.Runners.csproj
index de7612daa041..55cae5c70af0 100644
--- a/src/TestUtils/src/DeviceTests.Runners/TestUtils.DeviceTests.Runners.csproj
+++ b/src/TestUtils/src/DeviceTests.Runners/TestUtils.DeviceTests.Runners.csproj
@@ -10,6 +10,7 @@
$(WarningsNotAsErrors);XC0022;XC0023
true
true
+ true
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();
+ }
}
}