[Windows] Fix for Picker CharacterSpacing Not Being Applied to Title and Dropdown Items#30612
Conversation
There was a problem hiding this comment.
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
CharacterSpacingConverterfor UWP-compatible em conversion. - Updated Windows ComboBox style to bind CharacterSpacing via the new converter.
- Enhanced
UpdateCharacterSpacinginPickerExtensionsto 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.IValueConverterinterface reference is incorrect without ausing UI = Windows.UI.Xaml;alias or fullWindows.UI.Xaml.Data.IValueConverterqualification. 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}}" |
There was a problem hiding this comment.
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.
| CharacterSpacing="{Binding CharacterSpacing, Converter={StaticResource CharacterSpacingConverter}}" | |
| CharacterSpacing="{Binding CharacterSpacing, ElementName=HeaderContentPresenter, Converter={StaticResource CharacterSpacingConverter}}" |
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
jsuarezruiz
left a comment
There was a problem hiding this comment.
@SyedAbdulAzeemSF4852 Could you rebase and fix the conflict?
09c1ae5 to
dab35d4
Compare
@jsuarezruiz , Rebased the branch and resolved merge conflicts. |
84a11a4 to
e9860b7
Compare
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the AI's summary?
|
/review -b feature/refactor-copilot-yml |
MauiBot
left a comment
There was a problem hiding this comment.
🤖 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();
+}
+}
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's suggestions?
e9860b7 to
6b625c0
Compare
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 30612Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 30612" |
@kubaflo , Reviewed MauiBot’s suggestions and addressed all the valid concerns. |
|
/review -b feature/refactor-copilot-yml -p windows |
MauiBot
left a comment
There was a problem hiding this comment.
🤖 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>
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's suggestions?
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/review -b feature/refactor-copilot-yml |
MauiBot
left a comment
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
[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.
🤖 AI Summary
📊 Review Session —
|
| 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.ymlsrc/Core/src/Handlers/Picker/PickerHandler.Windows.cssrc/Core/src/Platform/Windows/PickerExtensions.cssrc/Core/src/Platform/Windows/Styles/MauiComboBoxStyle.xamlsrc/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:128and:177only reapply selected item TextBlock spacing whenVirtualView.CharacterSpacing > 0; other Windows text controls applyCharacterSpacing.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.mdtry-fix-2/content.mdtry-fix-3/content.md
📋 Report — Final Recommendation
Comparative Report - PR #30612
Candidate ranking
-
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
CharacterSpacingvalues 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.
-
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 inPickerExtensions.ApplyCharacterSpacing. - It is sound, but it changes more surface area than necessary compared with applying the reviewer feedback directly to the PR.
-
pr- Gate result: PASS.
- Correctly fixes the reported Windows Picker
CharacterSpacingregression for positive spacing and includes regression coverage. - Ranked below
pr-plus-reviewerandtry-fix-3because it still skips valid zero/negative values in selection/dropdown lifecycle reapply paths.
-
try-fix-1- Regression result: FAIL.
- Introduced a MAUI-specific Windows
MauiPickerComboBoxsubclass 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.
-
try-fix-2- Regression result: FAIL.
- Attempted to use explicit
ComboBox.ItemTemplateandSelectionBoxItemTemplatetemplates to avoid visual-tree searches. - Failed to compile because WinUI
ComboBox.SelectionBoxItemTemplateis 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.
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
Root Cause
Description of Change
Issues Fixed
Fixes #30464
Validated the behaviour in the following platforms
Output
Before.mp4
After.mp4