Skip to content

[Windows] Fix for Picker CharacterSpacing Not Being Applied to Title and Dropdown Items#30612

Merged
kubaflo merged 8 commits into
dotnet:inflight/currentfrom
SyedAbdulAzeemSF4852:fix-30464
May 30, 2026
Merged

[Windows] Fix for Picker CharacterSpacing Not Being Applied to Title and Dropdown Items#30612
kubaflo merged 8 commits into
dotnet:inflight/currentfrom
SyedAbdulAzeemSF4852:fix-30464

Conversation

@SyedAbdulAzeemSF4852
Copy link
Copy Markdown
Contributor

@SyedAbdulAzeemSF4852 SyedAbdulAzeemSF4852 commented Jul 14, 2025

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Issue Details

  • On Windows, setting the CharacterSpacing property on a Picker control has no visual effect — the spacing is not applied to either the title text or the individual items in the dropdown.

Root Cause

  • On Windows, the Picker control is rendered using a native ComboBox, which does not automatically apply the CharacterSpacing property to the title or the items in the dropdown list.
  • Additionally, applying CharacterSpacing directly to the ComboBox control does not affect the internal TextBlock used to render the selected item or the item templates.

Description of Change

  • Added a new CharacterSpacingConverter class to convert CharacterSpacing values to the correct format for UWP rendering.
  • Enhanced the UpdateCharacterSpacing method to apply character spacing to the selected item in the ComboBox, including handling scenarios where the control is not yet loaded.
  • Updated the ComboBoxHeader template to use the new CharacterSpacingConverter for binding character spacing values, and registered the converter as a static resource for use in XAML.

Issues Fixed

Fixes #30464

Validated the behaviour in the following platforms

  • Windows
  • Android
  • iOS
  • Mac

Output

Before After
Before.mp4
After.mp4

@dotnet-policy-service dotnet-policy-service Bot added community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration labels Jul 14, 2025
@SyedAbdulAzeemSF4852 SyedAbdulAzeemSF4852 marked this pull request as ready for review July 16, 2025 08:32
Copilot AI review requested due to automatic review settings July 16, 2025 08:32
@SyedAbdulAzeemSF4852 SyedAbdulAzeemSF4852 requested a review from a team as a code owner July 16, 2025 08:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR ensures that the CharacterSpacing property on the Picker control is correctly applied on Windows by introducing a converter, updating the ComboBox template to use it, and extending the renderer to apply spacing to the selected item. It also adds a UI test case to validate the fix end-to-end.

  • Added CharacterSpacingConverter for UWP-compatible em conversion.
  • Updated Windows ComboBox style to bind CharacterSpacing via the new converter.
  • Enhanced UpdateCharacterSpacing in PickerExtensions to apply spacing to the selected item both on load and after.
  • Added UI test and host page for issue #30464 to validate behavior.

Reviewed Changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Resources.xaml Registered CharacterSpacingConverter as a XAML resource.
MauiComboBoxStyle.xaml Updated ComboBoxHeader DataTemplate to use the converter.
PickerExtensions.cs Extended UpdateCharacterSpacing to apply to selected item.
CharacterSpacingConverter.cs Introduced converter implementing UWP em conversion.
src/Controls/tests/TestCases.Shared.Tests/... Added UI test validating Picker character spacing behavior.
src/Controls/tests/TestCases.HostApp/... Added host page demonstrating and triggering the spacing fix.
Comments suppressed due to low confidence (1)

src/Core/src/Platform/Windows/CharacterSpacingConverter.cs:5

  • The UI.Xaml.Data.IValueConverter interface reference is incorrect without a using UI = Windows.UI.Xaml; alias or full Windows.UI.Xaml.Data.IValueConverter qualification. Update the namespace or add the appropriate using directive.
internal sealed class CharacterSpacingConverter : UI.Xaml.Data.IValueConverter

<TextBlock
Text="{Binding Title}"
CharacterSpacing="{Binding Path=CharacterSpacing, ElementName=HeaderContentPresenter}"
CharacterSpacing="{Binding CharacterSpacing, Converter={StaticResource CharacterSpacingConverter}}"
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

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

The binding for CharacterSpacing in the ComboBoxHeader template lacks ElementName=HeaderContentPresenter, so it will try to read CharacterSpacing from the data context rather than the control. Add ElementName=HeaderContentPresenter to the binding source.

Suggested change
CharacterSpacing="{Binding CharacterSpacing, Converter={StaticResource CharacterSpacingConverter}}"
CharacterSpacing="{Binding CharacterSpacing, ElementName=HeaderContentPresenter, Converter={StaticResource CharacterSpacingConverter}}"

Copilot uses AI. Check for mistakes.
@jsuarezruiz
Copy link
Copy Markdown
Contributor

/azp run MAUI-UITests-public

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Copy Markdown
Contributor

@jsuarezruiz jsuarezruiz left a comment

Choose a reason for hiding this comment

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

@SyedAbdulAzeemSF4852 Could you rebase and fix the conflict?

@SyedAbdulAzeemSF4852
Copy link
Copy Markdown
Contributor Author

@SyedAbdulAzeemSF4852 Could you rebase and fix the conflict?

@jsuarezruiz , Rebased the branch and resolved merge conflicts.

@MauiBot MauiBot added s/agent-review-incomplete s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-review-incomplete labels Mar 21, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

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

Could you please review the AI's summary?

@MauiBot MauiBot added s/agent-review-incomplete s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-review-incomplete labels Mar 29, 2026
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented May 24, 2026

/review -b feature/refactor-copilot-yml

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #1 automatically generated candidates and selected try-fix-1 as the strongest fix.

Why: try-fix-1 won because it passed the Windows regression test while covering title, closed selected item, and opened dropdown item rendering. The raw PR fix is incomplete for dropdown items, and pr-plus-reviewer is a valid reviewer-applied direction but less clean and not the independently validated STEP 6a candidate.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-1`)
diff --git a/src/Core/src/Platform/Windows/CharacterSpacingConverter.cs b/src/Core/src/Platform/Windows/CharacterSpacingConverter.cs
new file mode 100644
--- /dev/null
+++ b/src/Core/src/Platform/Windows/CharacterSpacingConverter.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Microsoft.Maui.Platform;
+
+internal sealed partial class CharacterSpacingConverter : UI.Xaml.Data.IValueConverter
+{
+public object Convert(object value, Type targetType, object parameter, string language)
+{
+if (value is double characterSpacing)
+{
+return characterSpacing.ToEm();
+}
+
+return 0;
+}
+
+public object ConvertBack(object value, Type targetType, object parameter, string language)
+{
+throw new NotSupportedException();
+}
+}
diff --git a/src/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xaml b/src/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xaml
--- a/src/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xaml
+++ b/src/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xaml
@@ -6,7 +6,7 @@
     <DataTemplate x:Key="ComboBoxHeader">
         <TextBlock
             Text="{Binding Title}"
-            CharacterSpacing="{Binding Path=CharacterSpacing, ElementName=HeaderContentPresenter}"
+            CharacterSpacing="{Binding CharacterSpacing, Converter={StaticResource CharacterSpacingConverter}}"
             Foreground="{Binding TitleColor, Converter={StaticResource ColorConverter}, ConverterParameter=DefaultTextForegroundThemeBrush}" />
     </DataTemplate>
diff --git a/src/Core/src/Platform/Windows/Styles/Resources.xaml b/src/Core/src/Platform/Windows/Styles/Resources.xaml
--- a/src/Core/src/Platform/Windows/Styles/Resources.xaml
+++ b/src/Core/src/Platform/Windows/Styles/Resources.xaml
@@ -14,6 +14,7 @@
   <x:Boolean x:Key="MicrosoftMauiCoreIncluded">true</x:Boolean>
 
   <windows:ColorConverter x:Key="ColorConverter" />
+  <windows:CharacterSpacingConverter x:Key="CharacterSpacingConverter"/>
 
   <x:Double x:Key="FlyoutMinWidth">350</x:Double>
diff --git a/src/Core/src/Handlers/Picker/PickerHandler.Windows.cs b/src/Core/src/Handlers/Picker/PickerHandler.Windows.cs
--- a/src/Core/src/Handlers/Picker/PickerHandler.Windows.cs
+++ b/src/Core/src/Handlers/Picker/PickerHandler.Windows.cs
@@ -134,10 +134,28 @@ namespace Microsoft.Maui.Handlers
 
 comboBox.MinWidth = comboBox.ActualWidth;
 
+ApplyCharacterSpacingToItems(comboBox);
+
 if (VirtualView is null)
 return;
 
 VirtualView.IsOpen = true;
 }
+
+static void ApplyCharacterSpacingToItems(ComboBox comboBox)
+{
+var characterSpacing = comboBox.CharacterSpacing;
+
+for (int i = 0; i < comboBox.Items.Count; i++)
+{
+if (comboBox.ContainerFromIndex(i) is ComboBoxItem container)
+{
+container.CharacterSpacing = characterSpacing;
+}
+}
+}
 
 void OnMauiComboBoxDropDownClosed(object? sender, object e)
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue30464.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue30464.cs
new file mode 100644
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue30464.cs
@@ -0,0 +1,49 @@
+namespace Controls.TestCases.HostApp.Issues;
+
+[Issue(IssueTracker.Github, 30464, "The CharacterSpacing property on the Picker control is not applied to the title or the items", PlatformAffected.UWP)]
+public class Issue30464 : ContentPage
+{
+public Issue30464()
+{
+Picker pickerTitleCharacterSpacing = new Picker
+{
+Title = "Select an item",
+AutomationId = "Issue30464TitlePicker"
+};
+pickerTitleCharacterSpacing.Items.Add("Item 1");
+
+Picker pickerItemCharacterSpacing = new Picker
+{
+ItemsSource = new List<string> { "Item 1", "Item 2" },
+SelectedIndex = 1,
+AutomationId = "Issue30464ItemPicker"
+};
+
+Button applyCharacterSpacingBtn = new Button
+{
+Text = "Apply character spacing",
+AutomationId = "Issue30464Btn"
+};
+
+applyCharacterSpacingBtn.Clicked += (sender, e) =>
+{
+pickerTitleCharacterSpacing.CharacterSpacing = 14;
+pickerItemCharacterSpacing.CharacterSpacing = 14;
+};
+
+Label descriptionLabel = new Label
+{
+AutomationId = "Issue30464DescriptionLabel",
+Text = "The test case passes only if character spacing is correctly applied to both the Picker title and items; otherwise, it fails.",
+};
+
+VerticalStackLayout stackLayout = new VerticalStackLayout
+{
+Spacing = 20,
+Padding = 20,
+Children = { pickerTitleCharacterSpacing, pickerItemCharacterSpacing, applyCharacterSpacingBtn, descriptionLabel }
+};
+
+Content = stackLayout;
+}
+}
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30464.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30464.cs
new file mode 100644
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30464.cs
@@ -0,0 +1,24 @@
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues;
+
+public class Issue30464 : _IssuesUITest
+{
+public Issue30464(TestDevice device) : base(device)
+{
+}
+
+public override string Issue => "The CharacterSpacing property on the Picker control is not applied to the title or the items";
+
+[Test]
+[Category(UITestCategories.Picker)]
+public void ValidatePickerTitleAndItemCharacterSpacing()
+{
+App.WaitForElement("Issue30464DescriptionLabel");
+App.Tap("Issue30464Btn");
+App.Tap("Issue30464ItemPicker");
+VerifyScreenshot();
+}
+}

@MauiBot MauiBot added s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels May 24, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

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

Could you please check the ai's suggestions?

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 26, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 30612

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 30612"

@SyedAbdulAzeemSF4852
Copy link
Copy Markdown
Contributor Author

Could you please check the ai's suggestions?

@kubaflo , Reviewed MauiBot’s suggestions and addressed all the valid concerns.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented May 26, 2026

/review -b feature/refactor-copilot-yml -p windows

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #3 automatically generated candidates and selected try-fix-3 as the strongest fix.

Why: try-fix-3 wins because the raw PR failed the regression gate, while this candidate is compile-validated and directly fixes the reviewer-identified virtualization gap with the smallest implementation risk. It preserves the default ComboBox item template and avoids adding a Picker-specific ComboBox subclass.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-3`)
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ValidatePickerTitleAndItemCharacterSpacing.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ValidatePickerTitleAndItemCharacterSpacing.png
new file mode 100644
index 0000000000..b95f40dd6c
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ValidatePickerTitleAndItemCharacterSpacing.png differ
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue30464.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue30464.cs
new file mode 100644
index 0000000000..66d84a07ab
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue30464.cs
@@ -0,0 +1,71 @@
+namespace Controls.TestCases.HostApp.Issues;
+
+[Issue(IssueTracker.Github, 30464, "The CharacterSpacing property on the Picker control is not applied to the title or the items", PlatformAffected.UWP)]
+public class Issue30464 : ContentPage
+{
+	public Issue30464()
+	{
+		Picker pickerTitleCharacterSpacing = new Picker
+		{
+			Title = "Select an item",
+		};
+		pickerTitleCharacterSpacing.Items.Add("Item 1");
+
+		Picker pickerItemCharacterSpacing = new Picker
+		{
+			ItemsSource = new List<string> { "Item 1", "Item 2" },
+			SelectedIndex = 1
+		};
+
+		Button applyCharacterSpacingBtn = new Button
+		{
+			Text = "Apply character spacing",
+			AutomationId = "Issue30464Btn"
+		};
+
+		applyCharacterSpacingBtn.Clicked += (sender, e) =>
+		{
+			pickerTitleCharacterSpacing.CharacterSpacing = 14;
+			pickerItemCharacterSpacing.CharacterSpacing = 14;
+		};
+
+		Label descriptionLabel = new Label
+		{
+			AutomationId = "Issue30464DescriptionLabel",
+			Text = "The test case passes only if character spacing is correctly applied to both the Picker title and items, and is maintained after selecting a different item; otherwise, it fails.",
+		};
+
+		Picker pickerSelectionChange = new Picker
+		{
+			ItemsSource = new List<string> { "Item 1", "Item 2" },
+			SelectedIndex = 0,
+			CharacterSpacing = 14,
+			AutomationId = "Issue30464SelectionChangePicker"
+		};
+
+		Button changeSelectionBtn = new Button
+		{
+			Text = "Change selection",
+			AutomationId = "Issue30464ChangeSelectionBtn"
+		};
+		changeSelectionBtn.Clicked += (sender, e) =>
+		{
+			pickerSelectionChange.SelectedIndex = 1;
+		};
+
+		Content = new VerticalStackLayout
+		{
+			Spacing = 20,
+			Padding = 20,
+			Children =
+			{
+				pickerTitleCharacterSpacing,
+				pickerItemCharacterSpacing,
+				applyCharacterSpacingBtn,
+				pickerSelectionChange,
+				changeSelectionBtn,
+				descriptionLabel,
+			}
+		};
+	}
+}
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30464.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30464.cs
new file mode 100644
index 0000000000..a55ac34235
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30464.cs
@@ -0,0 +1,26 @@
+#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST // PR Link - https://github.com/dotnet/maui/pull/34974
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues;
+
+public class Issue30464 : _IssuesUITest
+{
+	public Issue30464(TestDevice device) : base(device)
+	{
+	}
+
+	public override string Issue => "The CharacterSpacing property on the Picker control is not applied to the title or the items";
+
+	[Test]
+	[Category(UITestCategories.Picker)]
+	public void ValidatePickerTitleAndItemCharacterSpacing()
+	{
+		App.WaitForElement("Issue30464DescriptionLabel");
+		App.Tap("Issue30464Btn");
+		App.Tap("Issue30464ChangeSelectionBtn");
+		VerifyScreenshot();
+	}
+}
+#endif
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ValidatePickerTitleAndItemCharacterSpacing.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ValidatePickerTitleAndItemCharacterSpacing.png
new file mode 100644
index 0000000000..775150bea0
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ValidatePickerTitleAndItemCharacterSpacing.png differ
diff --git a/src/Core/src/Handlers/Picker/PickerHandler.Windows.cs b/src/Core/src/Handlers/Picker/PickerHandler.Windows.cs
index ede369bf75..6fa8803d04 100644
--- a/src/Core/src/Handlers/Picker/PickerHandler.Windows.cs
+++ b/src/Core/src/Handlers/Picker/PickerHandler.Windows.cs
@@ -1,84 +1,85 @@
 #nullable enable
 using System;
 using System.Collections.Generic;
 using System.Collections.Specialized;
+using Microsoft.Maui.Platform;
 using Microsoft.UI.Xaml.Controls;
 using WSelectionChangedEventArgs = Microsoft.UI.Xaml.Controls.SelectionChangedEventArgs;
 
 namespace Microsoft.Maui.Handlers
 {
 	public partial class PickerHandler : ViewHandler<IPicker, ComboBox>
 	{
 		protected override ComboBox CreatePlatformView()
 		{
 			var platformPicker = new ComboBox();
 			return platformPicker;
 		}
 
 		protected override void ConnectHandler(ComboBox platformView)
 		{
 			platformView.SelectionChanged += OnControlSelectionChanged;
 			platformView.DropDownOpened += OnMauiComboBoxDropDownOpened;
 			platformView.DropDownClosed += OnMauiComboBoxDropDownClosed;
 		}
 
 		protected override void DisconnectHandler(ComboBox platformView)
 		{
 			platformView.SelectionChanged -= OnControlSelectionChanged;
 			platformView.DropDownOpened -= OnMauiComboBoxDropDownOpened;
 			platformView.DropDownClosed -= OnMauiComboBoxDropDownClosed;
 		}
 
 		// Updating ItemSource Resets the SelectedIndex.
 		// Which propagates that change to the VirtualView
 		// We don't want the virtual views selected index to change
 		// when updating the ItemSource.
 		// The ItemSource should probably be reworked to just be an OC that's
 		// kept in sync
 		internal bool UpdatingItemSource { get; set; }
 
 		internal void SetUpdatingItemSource(bool updatingItemSource)
 		{
 			UpdatingItemSource = updatingItemSource;
 
 			if (!updatingItemSource && VirtualView is not null && (VirtualView.SelectedIndex < VirtualView.GetCount()))
 				UpdateValue(nameof(IPicker.SelectedIndex));
 		}
 
 		static void Reload(IPickerHandler handler)
 		{
 			if (handler is PickerHandler ph1)
 				ph1.SetUpdatingItemSource(true);
 
 			handler.PlatformView.ItemsSource = new ItemDelegateList<string>(handler.VirtualView);
 
 			if (handler is PickerHandler ph2)
 				ph2.SetUpdatingItemSource(false);
 		}
 
 		[Obsolete("Use Microsoft.Maui.Handlers.PickerHandler.MapItems instead")]
 		public static void MapReload(IPickerHandler handler, IPicker picker, object? args) => Reload(handler);
 
 		internal static void MapItems(IPickerHandler handler, IPicker picker) => Reload(handler);
 
 		public static void MapTitle(IPickerHandler handler, IPicker picker)
 		{
 			handler.PlatformView?.UpdateTitle(picker);
 			handler.UpdateValue(nameof(IView.Semantics));
 		}
 
 		public static void MapTitleColor(IPickerHandler handler, IPicker picker)
 		{
 			handler.PlatformView?.UpdateTitle(picker);
 		}
 
 		public static void MapBackground(IPickerHandler handler, IPicker picker)
 		{
 			handler.PlatformView?.UpdateBackground(picker);
 		}
 
 		public static void MapSelectedIndex(IPickerHandler handler, IPicker picker)
 		{
 			handler.PlatformView?.UpdateSelectedIndex(picker);
 		}
 
diff --git a/src/Core/src/Platform/Windows/CharacterSpacingConverter.cs b/src/Core/src/Platform/Windows/CharacterSpacingConverter.cs
new file mode 100644
index 0000000000..1b384a7d44
--- /dev/null
+++ b/src/Core/src/Platform/Windows/CharacterSpacingConverter.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Microsoft.Maui.Platform;
+
+internal sealed partial class CharacterSpacingConverter : UI.Xaml.Data.IValueConverter
+{
+	public object Convert(object value, Type targetType, object parameter, string language)
+	{
+		if (value is double characterSpacing)
+		{
+			return characterSpacing.ToEm();
+		}
+
+		return 0;
+	}
+
+	public object ConvertBack(object value, Type targetType, object parameter, string language)
+	{
+		throw new NotSupportedException();
+	}
+}
\ No newline at end of file
diff --git a/src/Core/src/Platform/Windows/PickerExtensions.cs b/src/Core/src/Platform/Windows/PickerExtensions.cs
index 30f33ec854..e95c81c44a 100644
--- a/src/Core/src/Platform/Windows/PickerExtensions.cs
+++ b/src/Core/src/Platform/Windows/PickerExtensions.cs
@@ -1,110 +1,127 @@
 #nullable enable
 using Microsoft.Maui.Graphics;
 using Microsoft.UI.Xaml;
 using Microsoft.UI.Xaml.Controls;
 
 namespace Microsoft.Maui.Platform
 {
 	public static class PickerExtensions
 	{
 		public static void UpdateTitle(this ComboBox nativeComboBox, IPicker picker)
 		{
 			nativeComboBox.Header = string.IsNullOrEmpty(picker.Title) ? null : picker;
 
 			nativeComboBox.HeaderTemplate = string.IsNullOrEmpty(picker.Title) ? null :
 				(UI.Xaml.DataTemplate)UI.Xaml.Application.Current.Resources["ComboBoxHeader"];
 
 		}
 
 		public static void UpdateBackground(this ComboBox nativeComboBox, IPicker picker)
 		{
 			var platformBrush = picker.Background?.ToPlatform();
 
 			if (platformBrush == null)
 				nativeComboBox.Resources.RemoveKeys(BackgroundColorResourceKeys);
 			else
 				nativeComboBox.Resources.SetValueForAllKey(BackgroundColorResourceKeys, platformBrush);
 
 			nativeComboBox.RefreshThemeResources();
 		}
 
 		static readonly string[] BackgroundColorResourceKeys =
 		{
 			"ComboBoxBackground",
 			"ComboBoxBackgroundPointerOver",
 			"ComboBoxBackgroundPressed",
 			"ComboBoxBackgroundDisabled",
 			"ComboBoxBackgroundUnfocused",
 		};
 
 
 		public static void UpdateTextColor(this ComboBox nativeComboBox, IPicker picker)
 		{
 			var platformBrush = picker.TextColor?.ToPlatform();
 
 			if (platformBrush == null)
 			{
 				nativeComboBox.Resources.RemoveKeys(TextColorResourceKeys);
 				nativeComboBox.ClearValue(ComboBox.ForegroundProperty);
 			}
 			else
 			{
 				nativeComboBox.Resources.SetValueForAllKey(TextColorResourceKeys, platformBrush);
 				nativeComboBox.Foreground = platformBrush;
 			}
 
 			nativeComboBox.RefreshThemeResources();
 		}
 
 		// ResourceKeys controlling the foreground color of the ComboBox.
 		// https://docs.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.combobox?view=windows-app-sdk-1.1
 		static readonly string[] TextColorResourceKeys =
 		{
 			"ComboBoxForeground",
 			"ComboBoxForegroundPointerOver",
 			"ComboBoxForegroundDisabled",
 			"ComboBoxForegroundFocused",
 			"ComboBoxForegroundFocusedPressed",
 		};
 
 		public static void UpdateSelectedIndex(this ComboBox nativeComboBox, IPicker picker)
 		{
 			nativeComboBox.SelectedIndex = picker.SelectedIndex;
 		}
 
 		public static void UpdateCharacterSpacing(this ComboBox nativeComboBox, IPicker picker)
 		{
-			nativeComboBox.CharacterSpacing = picker.CharacterSpacing.ToEm();
+			var characterSpacing = picker.CharacterSpacing.ToEm();
+			nativeComboBox.CharacterSpacing = characterSpacing;
+			nativeComboBox.ItemContainerStyle = CreateComboBoxItemStyle(characterSpacing);
+			nativeComboBox.Resources[typeof(TextBlock)] = CreateTextBlockStyle(characterSpacing);
+		}
+
+		static Style CreateComboBoxItemStyle(int characterSpacing)
+		{
+			var style = new Style(typeof(ComboBoxItem));
+			style.Setters.Add(new Setter(Control.CharacterSpacingProperty, characterSpacing));
+			return style;
+		}
+
+		static Style CreateTextBlockStyle(int characterSpacing)
+		{
+			var style = new Style(typeof(TextBlock));
+			style.Setters.Add(new Setter(TextBlock.CharacterSpacingProperty, characterSpacing));
+			return style;
 		}
 
 		public static void UpdateFont(this ComboBox nativeComboBox, IPicker picker, IFontManager fontManager) =>
 			nativeComboBox.UpdateFont(picker.Font, fontManager);
 
 		public static void UpdateHorizontalTextAlignment(this ComboBox nativeComboBox, IPicker picker)
 		{
 			nativeComboBox.HorizontalContentAlignment = picker.HorizontalTextAlignment.ToPlatformHorizontalAlignment();
 		}
 
 		public static void UpdateVerticalTextAlignment(this ComboBox nativeComboBox, IPicker picker)
 		{
 			nativeComboBox.VerticalContentAlignment = picker.VerticalTextAlignment.ToPlatformVerticalAlignment();
 		}
 
 		internal static void UpdateIsOpen(this ComboBox nativeComboBox, IPicker picker)
 		{
 			if (!nativeComboBox.IsLoaded)
 			{
 				RoutedEventHandler? onLoaded = null;
 				onLoaded = (s, e) =>
 				{
 					nativeComboBox.Loaded -= onLoaded;
 					UpdateIsOpen(nativeComboBox, picker);
 				};
 				nativeComboBox.Loaded += onLoaded;
 				return;
 			}
 
 			nativeComboBox.IsDropDownOpen = picker.IsOpen;
 		}
 	}
 }
\ No newline at end of file
diff --git a/src/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xaml b/src/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xaml
index f52b6279a9..4fbbe790cd 100644
--- a/src/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xaml
+++ b/src/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xaml
@@ -1,13 +1,13 @@
 <ResourceDictionary
 	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 	xmlns:uwp="using:Microsoft.Maui.Platform">
 
     <DataTemplate x:Key="ComboBoxHeader">
         <TextBlock
             Text="{Binding Title}" 
-            CharacterSpacing="{Binding Path=CharacterSpacing, ElementName=HeaderContentPresenter}"
+            CharacterSpacing="{Binding CharacterSpacing, Converter={StaticResource CharacterSpacingConverter}}"
             Foreground="{Binding TitleColor, Converter={StaticResource ColorConverter}, ConverterParameter=DefaultTextForegroundThemeBrush}" />
     </DataTemplate>
 
 </ResourceDictionary> 
\ No newline at end of file
diff --git a/src/Core/src/Platform/Windows/Styles/Resources.xaml b/src/Core/src/Platform/Windows/Styles/Resources.xaml
index 037beebde7..32d80894bd 100644
--- a/src/Core/src/Platform/Windows/Styles/Resources.xaml
+++ b/src/Core/src/Platform/Windows/Styles/Resources.xaml
@@ -1,71 +1,72 @@
 <ResourceDictionary
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:windows="using:Microsoft.Maui.Platform"
     x:Class="Microsoft.Maui.Platform.Resources">
 
   <ResourceDictionary.MergedDictionaries>
     <ResourceDictionary Source="MauiComboBoxStyle.xaml" />
     <ResourceDictionary Source="MauiSliderStyle.xaml" />
     <ResourceDictionary Source="MauiStepperStyle.xaml" />
     <ResourceDictionary Source="WindowRootViewStyle.xaml" />
   </ResourceDictionary.MergedDictionaries>
 
   <x:Boolean x:Key="MicrosoftMauiCoreIncluded">true</x:Boolean>
 
   <windows:ColorConverter x:Key="ColorConverter" />
+  <windows:CharacterSpacingConverter x:Key="CharacterSpacingConverter"/>
 
   <x:Double x:Key="FlyoutMinWidth">350</x:Double>
 
   <Style x:Key="MauiFlyoutPresenterStyle" TargetType="FlyoutPresenter">
     <Setter Property="MaxHeight" Value="{x:Null}" />
     <Setter Property="MaxWidth" Value="{x:Null}" />
     <Setter Property="MinWidth" Value="{StaticResource FlyoutMinWidth}"/>
     <Setter Property="HorizontalAlignment" Value="Center"/>
     <Setter Property="VerticalAlignment" Value="Center"/>
     <Setter Property="Template">
       <Setter.Value>
         <ControlTemplate TargetType="FlyoutPresenter">
           <Border Margin="20"
                   MinWidth="{TemplateBinding MinWidth}"
                   MaxWidth="{TemplateBinding MaxWidth}"
                   MaxHeight="{TemplateBinding MaxHeight}"
                   Padding="{ThemeResource FlyoutBorderThemePadding}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   CornerRadius="{StaticResource OverlayCornerRadius}"
                   Background="{TemplateBinding Background}"
                   BorderBrush="{TemplateBinding BorderBrush}"
                   BorderThickness="{TemplateBinding BorderThickness}">
             <ScrollViewer x:Name="FlyoutScrollViewer"
                           AutomationProperties.AccessibilityView="Raw"
                           HorizontalScrollBarVisibility="Disabled"
                           HorizontalScrollMode="Disabled"
                           VerticalScrollBarVisibility="Auto"
                           VerticalScrollMode="Auto"
                           ZoomMode="Disabled">
               <ContentPresenter Margin="{TemplateBinding Padding}"
                                 HorizontalAlignment="Stretch"
                                 VerticalAlignment="Top"
                                 Content="{TemplateBinding Content}"
                                 ContentTemplate="{TemplateBinding ContentTemplate}"
                                 ContentTransitions="{TemplateBinding ContentTransitions}" />
             </ScrollViewer>
           </Border>
         </ControlTemplate>
       </Setter.Value>
     </Setter>
   </Style>
 
   <DataTemplate x:Key="MauiActionSheetItemTemplate">
     <TextBlock Text="{Binding}"
                TextWrapping="Wrap"
                Padding="12,8"
                HorizontalAlignment="Left"
                VerticalAlignment="Center" />
   </DataTemplate>
 
     <Style TargetType="TextBlock">
         <Setter Property="LineStackingStrategy" Value="BlockLineHeight"/>
     </Style>
 </ResourceDictionary>

Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

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

Could you please check the ai's suggestions?

@sheiksyedm
Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented May 29, 2026

/review -b feature/refactor-copilot-yml

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.


// Reapply CharacterSpacing to the selected item's TextBlock so it persists
// across selection changes (e.g. programmatic SelectedIndex updates).
if (VirtualView != null && VirtualView.CharacterSpacing > 0)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[moderate] Logic and Correctness - This reapply path only runs when CharacterSpacing > 0, but CharacterSpacing has no non-negative validation and the other Windows text controls apply CharacterSpacing.ToEm() directly. A Picker using negative character spacing can update ComboBox.CharacterSpacing, then lose that value after selection/dropdown lifecycle because the selected TextBlock is not re-stamped here or in the matching dropdown-closed path at line 177. Apply the current converted value unconditionally so zero/negative values are preserved consistently.

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented May 29, 2026

🤖 AI Summary

👋 @SyedAbdulAzeemSF4852 — new AI review results are available. Please review the latest session below.

📊 Review Session06ee8c9 · Update Windows snapshot baseline · 2026-05-29 16:26 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: WINDOWS · Base: main · Merge base: b0ea772f

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue30464 Issue30464 ✅ FAIL — 571s ✅ PASS — 471s
🔴 Without fix — 🖥️ Issue30464: FAIL ✅ · 571s
  Determining projects to restore...
  Restored D:\a\1\s\src\Core\maps\src\Maps.csproj (in 41.98 sec).
  Restored D:\a\1\s\src\Controls\src\Xaml\Controls.Xaml.csproj (in 104 ms).
  Restored D:\a\1\s\src\Controls\tests\TestCases.HostApp\Controls.TestCases.HostApp.csproj (in 42.41 sec).
  Restored D:\a\1\s\src\Graphics\src\Graphics.Win2D\Graphics.Win2D.csproj (in 36 ms).
  Restored D:\a\1\s\src\Essentials\src\Essentials.csproj (in 18 ms).
  Restored D:\a\1\s\src\Core\src\Core.csproj (in 65 ms).
  Restored D:\a\1\s\src\Controls\src\Core\Controls.Core.csproj (in 25 ms).
  Restored D:\a\1\s\src\Controls\Maps\src\Controls.Maps.csproj (in 26 ms).
  Restored D:\a\1\s\src\Controls\Foldable\src\Controls.Foldable.csproj (in 20 ms).
  Restored D:\a\1\s\src\BlazorWebView\src\Maui\Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 38 ms).
  Restored D:\a\1\s\src\Graphics\src\Graphics\Graphics.csproj (in 1.69 sec).
  3 of 14 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Graphics -> D:\a\1\s\artifacts\bin\Graphics\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Graphics.Win2D -> D:\a\1\s\artifacts\bin\Graphics.Win2D\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.Win2D.WinUI.Desktop.dll
  Essentials -> D:\a\1\s\artifacts\bin\Essentials\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Core -> D:\a\1\s\artifacts\bin\Core\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Maps -> D:\a\1\s\artifacts\bin\Maps\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Maps.dll
  Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Debug\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Controls.Foldable -> D:\a\1\s\artifacts\bin\Controls.Foldable\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Foldable.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> D:\a\1\s\artifacts\bin\Microsoft.AspNetCore.Components.WebView.Maui\Debug\net10.0-windows10.0.19041.0\Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> D:\a\1\s\artifacts\bin\Controls.Xaml\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Xaml.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Controls.Maps -> D:\a\1\s\artifacts\bin\Controls.Maps\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Maps.dll
  Controls.TestCases.HostApp -> D:\a\1\s\artifacts\bin\Controls.TestCases.HostApp\Debug\net10.0-windows10.0.19041.0\win-x64\Controls.TestCases.HostApp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:06:04.49
  Determining projects to restore...
  Restored D:\a\1\s\src\Controls\tests\CustomAttributes\Controls.CustomAttributes.csproj (in 881 ms).
  Restored D:\a\1\s\src\TestUtils\src\VisualTestUtils\VisualTestUtils.csproj (in 3 ms).
  Restored D:\a\1\s\src\TestUtils\src\VisualTestUtils.MagickNet\VisualTestUtils.MagickNet.csproj (in 6.76 sec).
  Restored D:\a\1\s\src\Controls\tests\TestCases.WinUI.Tests\Controls.TestCases.WinUI.Tests.csproj (in 9.53 sec).
  Restored D:\a\1\s\src\TestUtils\src\UITest.Core\UITest.Core.csproj (in 2 ms).
  Restored D:\a\1\s\src\TestUtils\src\UITest.Appium\UITest.Appium.csproj (in 2 ms).
  Restored D:\a\1\s\src\TestUtils\src\UITest.NUnit\UITest.NUnit.csproj (in 2.17 sec).
  Restored D:\a\1\s\src\TestUtils\src\UITest.Analyzers\UITest.Analyzers.csproj (in 5.81 sec).
  7 of 15 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Graphics -> D:\a\1\s\artifacts\bin\Graphics\Debug\net10.0\Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Essentials -> D:\a\1\s\artifacts\bin\Essentials\Debug\net10.0\Microsoft.Maui.Essentials.dll
  Controls.CustomAttributes -> D:\a\1\s\artifacts\bin\Controls.CustomAttributes\Debug\net10.0\Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Core -> D:\a\1\s\artifacts\bin\Core\Debug\net10.0\Microsoft.Maui.dll
  Controls.Core.Design -> D:\a\1\s\artifacts\bin\Controls.Core.Design\Debug\net472\Microsoft.Maui.Controls.DesignTools.dll
  Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Debug\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Debug\net10.0\Microsoft.Maui.Controls.dll
  UITest.Core -> D:\a\1\s\artifacts\bin\UITest.Core\Debug\net10.0\UITest.Core.dll
  UITest.Appium -> D:\a\1\s\artifacts\bin\UITest.Appium\Debug\net10.0\UITest.Appium.dll
  UITest.NUnit -> D:\a\1\s\artifacts\bin\UITest.NUnit\Debug\net10.0\UITest.NUnit.dll
  VisualTestUtils -> D:\a\1\s\artifacts\bin\VisualTestUtils\Debug\netstandard2.0\VisualTestUtils.dll
  VisualTestUtils.MagickNet -> D:\a\1\s\artifacts\bin\VisualTestUtils.MagickNet\Debug\netstandard2.0\VisualTestUtils.MagickNet.dll
  UITest.Analyzers -> D:\a\1\s\artifacts\bin\UITest.Analyzers\Debug\netstandard2.0\UITest.Analyzers.dll
  Controls.TestCases.WinUI.Tests -> D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll
Test run for D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 5/29/2026 2:23:29 PM FixtureSetup for Issue30464(Windows)
>>>>> 5/29/2026 2:23:40 PM ValidatePickerTitleAndItemCharacterSpacing Start
>>>>> 5/29/2026 2:23:43 PM ValidatePickerTitleAndItemCharacterSpacing Stop
>>>>> 5/29/2026 2:23:43 PM Log types: 
  Failed ValidatePickerTitleAndItemCharacterSpacing [3 s]
  Error Message:
   VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: ValidatePickerTitleAndItemCharacterSpacing.png (1.34% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow

  Stack Trace:
     at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 309
   at Microsoft.Maui.TestCases.Tests.Issues.Issue30464.ValidatePickerTitleAndItemCharacterSpacing() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30464.cs:line 23
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

NUnit Adapter 4.5.0.0: Test execution complete
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.11]   Discovering: Controls.TestCases.WinUI.Tests
[xUnit.net 00:00:00.31]   Discovered:  Controls.TestCases.WinUI.Tests
Results File: D:\a\1\s\CustomAgentLogsTmp\UITests\TestResults\Issue30464.trx

Total tests: 1
     Failed: 1
Test Run Failed.
 Total time: 34.3116 Seconds
>>> TRX_RESULT_FILE: D:\a\1\s\CustomAgentLogsTmp\UITests\TestResults\Issue30464.trx

🟢 With fix — 🖥️ Issue30464: PASS ✅ · 471s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Graphics -> D:\a\1\s\artifacts\bin\Graphics\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Essentials -> D:\a\1\s\artifacts\bin\Essentials\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Essentials.dll
  Graphics.Win2D -> D:\a\1\s\artifacts\bin\Graphics.Win2D\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Graphics.Win2D.WinUI.Desktop.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Core -> D:\a\1\s\artifacts\bin\Core\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.dll
  Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Debug\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Maps -> D:\a\1\s\artifacts\bin\Maps\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Microsoft.AspNetCore.Components.WebView.Maui -> D:\a\1\s\artifacts\bin\Microsoft.AspNetCore.Components.WebView.Maui\Debug\net10.0-windows10.0.19041.0\Microsoft.AspNetCore.Components.WebView.Maui.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Controls.Foldable -> D:\a\1\s\artifacts\bin\Controls.Foldable\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Foldable.dll
  Controls.Xaml -> D:\a\1\s\artifacts\bin\Controls.Xaml\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Xaml.dll
  Controls.Maps -> D:\a\1\s\artifacts\bin\Controls.Maps\Debug\net10.0-windows10.0.19041.0\Microsoft.Maui.Controls.Maps.dll
  Controls.TestCases.HostApp -> D:\a\1\s\artifacts\bin\Controls.TestCases.HostApp\Debug\net10.0-windows10.0.19041.0\win-x64\Controls.TestCases.HostApp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:05:50.23
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Graphics -> D:\a\1\s\artifacts\bin\Graphics\Debug\net10.0\Microsoft.Maui.Graphics.dll
  Controls.CustomAttributes -> D:\a\1\s\artifacts\bin\Controls.CustomAttributes\Debug\net10.0\Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Essentials -> D:\a\1\s\artifacts\bin\Essentials\Debug\net10.0\Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Core -> D:\a\1\s\artifacts\bin\Core\Debug\net10.0\Microsoft.Maui.dll
  Controls.Core.Design -> D:\a\1\s\artifacts\bin\Controls.Core.Design\Debug\net472\Microsoft.Maui.Controls.DesignTools.dll
  Controls.BindingSourceGen -> D:\a\1\s\artifacts\bin\Controls.BindingSourceGen\Debug\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14232338
  Controls.Core -> D:\a\1\s\artifacts\bin\Controls.Core\Debug\net10.0\Microsoft.Maui.Controls.dll
  UITest.Core -> D:\a\1\s\artifacts\bin\UITest.Core\Debug\net10.0\UITest.Core.dll
  UITest.Appium -> D:\a\1\s\artifacts\bin\UITest.Appium\Debug\net10.0\UITest.Appium.dll
  UITest.NUnit -> D:\a\1\s\artifacts\bin\UITest.NUnit\Debug\net10.0\UITest.NUnit.dll
  VisualTestUtils -> D:\a\1\s\artifacts\bin\VisualTestUtils\Debug\netstandard2.0\VisualTestUtils.dll
  VisualTestUtils.MagickNet -> D:\a\1\s\artifacts\bin\VisualTestUtils.MagickNet\Debug\netstandard2.0\VisualTestUtils.MagickNet.dll
  UITest.Analyzers -> D:\a\1\s\artifacts\bin\UITest.Analyzers\Debug\netstandard2.0\UITest.Analyzers.dll
  Controls.TestCases.WinUI.Tests -> D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll
Test run for D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in D:\a\1\s\artifacts\bin\Controls.TestCases.WinUI.Tests\Debug\net10.0\Controls.TestCases.WinUI.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 5/29/2026 2:31:22 PM FixtureSetup for Issue30464(Windows)
>>>>> 5/29/2026 2:31:33 PM ValidatePickerTitleAndItemCharacterSpacing Start
>>>>> 5/29/2026 2:31:35 PM ValidatePickerTitleAndItemCharacterSpacing Stop
  Passed ValidatePickerTitleAndItemCharacterSpacing [1 s]
NUnit Adapter 4.5.0.0: Test execution complete
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.14]   Discovering: Controls.TestCases.WinUI.Tests
[xUnit.net 00:00:00.40]   Discovered:  Controls.TestCases.WinUI.Tests
Results File: D:\a\1\s\CustomAgentLogsTmp\UITests\TestResults\Issue30464.trx

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 28.3362 Seconds
>>> TRX_RESULT_FILE: D:\a\1\s\CustomAgentLogsTmp\UITests\TestResults\Issue30464.trx

📁 Fix files reverted (5 files)
  • eng/pipelines/ci-copilot.yml
  • src/Core/src/Handlers/Picker/PickerHandler.Windows.cs
  • src/Core/src/Platform/Windows/PickerExtensions.cs
  • src/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xaml
  • src/Core/src/Platform/Windows/Styles/Resources.xaml

New files (not reverted):

  • src/Core/src/Platform/Windows/CharacterSpacingConverter.cs

🧪 UI Tests — Picker,ViewBaseTests

Detected UI test categories: Picker,ViewBaseTests

Deep UI tests — 161 passed, 0 failed across 2 categories on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
Picker 46/47 ✓
ViewBaseTests 115/115 ✓
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

🔍 Pre-Flight — Context & Validation

Issue: #30464 - [Windows] Picker CharacterSpacing property not applied to Title and PickerItems text
PR: #30612 - [Windows] Fix for Picker CharacterSpacing Not Being Applied to Title and Dropdown Items
Platforms Affected: Windows
Files Changed: 5 implementation, 4 test

Key Findings

  • Linked issue [Windows] Picker CharacterSpacing property not applied to Title and PickerItems text #30464 is verified on Windows for MAUI 9.0.81/9.0.0: Picker CharacterSpacing has no visual effect on title or picker item text.
  • PR implementation changes Windows Picker/ComboBox behavior plus Windows style resources; added UI test coverage for Issue30464 with Windows/Android snapshots.
  • Gate was already completed before this phase: tests fail without fix and pass with PR fix; this phase did not re-run gate verification.
  • Impacted UI test category: Picker.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: medium
Errors: 1 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • src/Core/src/Handlers/Picker/PickerHandler.Windows.cs:128 and :177 only reapply selected item TextBlock spacing when VirtualView.CharacterSpacing > 0; other Windows text controls apply CharacterSpacing.ToEm() without a positive-only guard, so negative values can be lost after selection/dropdown changes.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #30612 Add CharacterSpacingConverter/resource, update ComboBoxHeader binding, set ComboBox.CharacterSpacing, imperatively apply spacing to selected item TextBlock and realized dropdown ComboBoxItem containers. PASSED (Gate) PickerHandler.Windows.cs, PickerExtensions.cs, CharacterSpacingConverter.cs, MauiComboBoxStyle.xaml, Resources.xaml Original PR; code review flagged positive-only reapply guard.

🔬 Code Review — Deep Analysis

Code Review PR #30612

Independent Assessment

What this changes: Windows Picker maps MAUI CharacterSpacing into the native ComboBox header template, the closed selected item TextBlock, and realized dropdown item containers.
Inferred motivation: Picker title/selected text/dropdown items on Windows were ignoring CharacterSpacing, unlike other text controls.

Reconciliation with PR Narrative

Author claims: The PR fixes Windows Picker title and dropdown item character spacing by adding a converter, updating the header template/resource dictionary, applying spacing to selected item visuals, and adding a UI screenshot test for #30464.
Agreement/disagreement: This matches the code and the linked issue. CI is green. There is already an unresolved inline comment on MauiComboBoxStyle.xaml:9 about the header binding source, so I am not duplicating that as a new finding.

Findings

Error Negative CharacterSpacing is not reapplied after selection/dropdown changes

src/Core/src/Handlers/Picker/PickerHandler.Windows.cs:128 and :177 only reapply the selected item TextBlock spacing when VirtualView.CharacterSpacing > 0. TextElement.CharacterSpacingProperty has no non-negative validator, and other Windows controls (TextBlockExtensions, ControlExtensions, TimePickerExtensions, TextBoxExtensions) apply CharacterSpacing.ToEm() without a positive-only guard. As a result, negative spacing can be applied by the mapper initially, but after a selection change or dropdown close the internal TextBlock can be reused without the configured negative spacing. Apply the current spacing unconditionally, or use a condition that preserves negative values.

Devil's Advocate

The reported issue and the new screenshot test cover positive spacing, so most user scenarios likely improve. However, the new guard is only on added code paths and conflicts with the existing API behavior/patterns for valid non-positive values; CI would not catch this edge case because the new test uses only 14.

Verdict: NEEDS_CHANGES

Confidence: medium
Summary: The approach is generally aligned with the Windows Picker bug, and CI is passing. Before merge, the reapply logic should avoid regressing valid negative CharacterSpacing values; additionally, the existing unresolved header binding comment should be addressed.


🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 maui-expert-reviewer MauiPickerComboBox subclass owns template/selection/dropdown lifecycle reapply. FAIL 3 files (+ new subclass) Compile failed: WinUI event nullability mismatch (CS8622); higher platform-view complexity.
2 maui-expert-reviewer after candidate 1 failure Explicit ComboBox.ItemTemplate/SelectionBoxItemTemplate generated with TextBlock.CharacterSpacing. FAIL 2 files Compile failed: ComboBox.SelectionBoxItemTemplate is read-only (CS0200), so closed selected item cannot be controlled this way.
3 maui-expert-reviewer after candidates 1-2 failures Centralize PR visual-tree reapply and remove positive-only guard so selected/dropdown text receives the current converted value, including zero/negative values. PASS 2 files Passes Issue30464 Windows UI test and fixes the code-review concern in PR's current fix.
PR PR #30612 Converter/resource + header binding + selected-item/dropdown item visual-tree reapply guarded by CharacterSpacing > 0. PASSED (Gate) 5 implementation files Original PR passes gate but can drop valid non-positive spacing during reapply.

Cross-Pollination / Learn Loop

Round Input Failure Expert Guidance Result
1 Initial code review found positive-only reapply skips negative values. Try a platform-view-owned lifecycle (MauiPickerComboBox) to avoid scattered handler reapply. Candidate 1 failed compile.
2 Candidate 1 CS8622 and subclass complexity. Try template-owned rendering with item and selection templates. Candidate 2 failed compile because selection-box template is read-only.
3 Candidate 2 cannot control closed selected item via template. Preserve PR's proven mechanism but make it state-driven/sign-preserving and centralized. Candidate 3 passed.

Regression Cross-Reference: No regression risks detected in regression-check/content.md.
Exhausted: Yes for meaningfully different subclass/template alternatives; further alternatives would either fork the full WinUI ComboBox template (high blast radius) or be minor variants of candidate 3.
Selected Fix: Candidate #3 - it passes the Windows Issue30464 test and is demonstrably better than PR #30612's current fix because it removes the non-positive CharacterSpacing correctness hole while preserving the working PR behavior.

Per-Candidate Details

  • try-fix-1/content.md
  • try-fix-2/content.md
  • try-fix-3/content.md

📋 Report — Final Recommendation

Comparative Report - PR #30612

Candidate ranking

  1. pr-plus-reviewer - winner

    • Builds on the submitted PR's passing regression behavior and applies the expert reviewer's actionable correction.
    • Removes the positive-only guard so selected-item reapplication preserves zero and negative CharacterSpacing values consistently.
    • This is also aligned with the only passing STEP 5a alternative, try-fix-3, but keeps the smallest possible delta from the submitted PR.
  2. try-fix-3

    • Regression result: PASS.
    • Uses the same sign-preserving insight as pr-plus-reviewer, but goes further by centralizing selected-item and dropdown-container reapplication in PickerExtensions.ApplyCharacterSpacing.
    • It is sound, but it changes more surface area than necessary compared with applying the reviewer feedback directly to the PR.
  3. pr

    • Gate result: PASS.
    • Correctly fixes the reported Windows Picker CharacterSpacing regression for positive spacing and includes regression coverage.
    • Ranked below pr-plus-reviewer and try-fix-3 because it still skips valid zero/negative values in selection/dropdown lifecycle reapply paths.
  4. try-fix-1

    • Regression result: FAIL.
    • Introduced a MAUI-specific Windows MauiPickerComboBox subclass to own loaded/template/selection/dropdown lifecycle reapplication.
    • Failed before UI execution due to event-handler nullability build errors, and the approach adds lifecycle complexity beyond what the fix requires.
  5. try-fix-2

    • Regression result: FAIL.
    • Attempted to use explicit ComboBox.ItemTemplate and SelectionBoxItemTemplate templates to avoid visual-tree searches.
    • Failed to compile because WinUI ComboBox.SelectionBoxItemTemplate is read-only, making the approach non-viable as proposed.

Decision

pr-plus-reviewer is the best candidate. It preserves the PR's passing regression fix, addresses the expert reviewer's concrete correctness finding, and avoids the additional refactoring risk of try-fix-3.

Candidates that failed regression/build validation (try-fix-1, try-fix-2) are ranked below all passing candidates as required.


@kubaflo kubaflo changed the base branch from main to inflight/current May 30, 2026 11:08
@kubaflo kubaflo merged commit ada8ace into dotnet:inflight/current May 30, 2026
32 checks passed
@github-actions github-actions Bot added this to the .NET 10.0 SR8 milestone May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-picker Picker community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/windows s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Windows] Picker CharacterSpacing property not applied to Title and PickerItems text

7 participants