[Windows] Fix for TimePicker rendering a default time when its value is null#32314
Conversation
|
Hey there @@SyedAbdulAzeemSF4852! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32266.cs
Outdated
Show resolved
Hide resolved
|
|
||
| [Test] | ||
| [Category(UITestCategories.TimePicker)] | ||
| public void VerifyTimePickerNotMidnightOnNull() |
There was a problem hiding this comment.
@jsuarezruiz , Since I’ve modified the test case and its method name, I will add the snapshots for Mac and Windows platforms after the next CI run.
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
There was a problem hiding this comment.
Pull Request Overview
This PR fixes an issue where the Windows TimePicker incorrectly defaults to displaying midnight (00:00) when the Time value is null, instead of showing an empty/placeholder state. The fix changes the Windows platform implementation to use the SelectedTime property directly, which properly supports null values.
Key changes:
- Updated Windows platform time assignment from
Time(with null-coalescing toTimeSpan.Zero) toSelectedTime(supports nullable) - Added comprehensive UI tests verifying null initial state and cleared time behavior
- Added platform-specific screenshot snapshots for iOS, macOS, and Android
Reviewed Changes
Copilot reviewed 3 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/Core/src/Platform/Windows/TimePickerExtensions.cs |
Changed from Time property with null-coalescing to SelectedTime property for proper null handling |
src/Controls/tests/TestCases.HostApp/Issues/Issue32266.cs |
Added test page with TimePicker initialized to null and buttons to set/clear time |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32266.cs |
Added UI tests verifying null state and clear behavior |
src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/*.png |
Added iOS screenshot baselines for both test scenarios |
src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/*.png |
Added macOS screenshot baselines for both test scenarios |
src/Controls/tests/TestCases.Android.Tests/snapshots/android/*.png |
Added Android screenshot baselines for both test scenarios |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/rebase |
…macOS TimePicker.
…hots for Windows platform
9321065 to
14c28f3
Compare
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 32314Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 32314" |
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #32314 | Use WinUI nullable SelectedTime instead of coercing null to TimeSpan.Zero, and listen to SelectedTimeChanged; add UI coverage for initial-null and clear-to-null flows |
⏳ PENDING (Gate) | src/Core/src/Handlers/TimePicker/TimePickerHandler.Windows.cs, src/Core/src/Platform/Windows/TimePickerExtensions.cs, src/Controls/tests/TestCases.HostApp/Issues/Issue32266.cs, src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32266.cs, snapshot baselines |
Original PR |
🚦 Gate — Test Verification
Gate Result: ✅ PASSED
Platform: windows
Mode: Full Verification
- Tests FAIL without fix: ✅
- Tests PASS with fix: ✅
Evidence
- Verification ran through the required isolated skill flow for
Issue32266. - The reverted baseline reproduced the bug: both
VerifyTimePickerIsNullOnInitialLoadandVerifyClearedTimeDoesNotShowMidnightfailed. - Restoring the PR changes made both UI tests pass on Windows.
Logs
CustomAgentLogsTmp/PRState/32314/PRAgent/gate/verify-tests-fail/verification-report.mdCustomAgentLogsTmp/PRState/32314/PRAgent/gate/verify-tests-fail/verification-log.txt
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Use RegisterPropertyChangedCallback on TimePicker.SelectedTimeProperty instead of the SelectedTimeChanged CLR event, while mapping null through WinUI SelectedTime |
✅ PASS | 2 files | Works, but adds lower-level DP callback plumbing without improving the core fix |
| 2 | try-fix | Keep TimeChanged, but read nullable sender.SelectedTime instead of non-nullable e.NewTime; still map MAUI Time to WinUI SelectedTime |
✅ PASS | 2 files | Suggests the forward null-preserving mapping is the essential behavior |
| 3 | try-fix | Keep existing handler wiring, but when MAUI Time is null, clear WinUI TimePicker.TimeProperty and set SelectedTime = null |
✅ PASS | 1 file | Smallest passing variant, but it only addresses the forward mapping path and leaves reverse nullable sync semantics implicit |
| 4 | try-fix | Hybrid mapping: set platformView.Time for non-null values, but explicitly set platformView.SelectedTime = null for null; keep TimeChanged and read nullable state from SelectedTime |
✅ PASS | 2 files | Passed tests, but artifact output was incomplete (attempt-1.patch instead of a full attempt-4 directory) |
| 5 | try-fix | Change TimePicker.TimeProperty default from new TimeSpan(0) to null at the Controls source-model layer |
❌ FAIL | 1 file | Did not fix either Windows screenshot test; the bug is not solved at the default-value layer alone |
| 6 | try-fix | Defer SelectedTime = null until after WinUI initialization/template application by using Loaded or DispatcherQueue |
✅ PASS | 1 file | Works, but introduces lifecycle/timing complexity that simpler fixes avoid |
| 7 | try-fix | Use ClearValue(TimePicker.SelectedTimeProperty) for null state and narrowly ignore native null-to-null feedback in the handler |
✅ PASS | 2 files | Distinct DP-clear semantics path; passes but is more stateful and indirect than necessary |
| 8 | try-fix | Sync MAUI Time from WinUI only on a user-commit surrogate and ignore programmatic updates during initialization |
❌ FAIL | 1 file | Failed after iteration; commit-only sync did not fix the Windows placeholder behavior |
| 9 | try-fix | Make the Controls ITimePicker.Time implementation Windows value-source-aware (!IsSet(TimeProperty) => null) and combine that with nullable SelectedTime mapping |
✅ PASS | 3 files | Passes, but it broadens the change into the Controls layer and is more invasive than needed |
| PR | PR #32314 | Map ITimePicker.Time to WinUI nullable SelectedTime and switch handler synchronization from TimeChanged to SelectedTimeChanged |
✅ PASSED (Gate) | 4 source files + snapshot baselines | Original PR; aligned with WinUI nullable API semantics and reviewer guidance |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 1 | Yes | DependencyProperty callback on SelectedTimeProperty |
| claude-sonnet-4.6 | 1 | Yes | Keep TimeChanged, but read sender.SelectedTime |
| gpt-5.3-codex | 1 | Yes | Clear WinUI TimeProperty and set SelectedTime = null while leaving handler wiring in place |
| gemini-3-pro-preview | 1 | Yes | Hybrid null handling: use Time for non-null, SelectedTime = null for null, keep TimeChanged |
| claude-opus-4.6 | 2 | No | NO NEW IDEAS |
| claude-sonnet-4.6 | 2 | No | NO NEW IDEAS |
| gpt-5.3-codex | 2 | Yes | Change the Controls TimeProperty default to null |
| gemini-3-pro-preview | 2 | Yes | Defer null assignment until after native initialization via Loaded or DispatcherQueue |
| claude-opus-4.6 | 3 | No | NO NEW IDEAS |
| claude-sonnet-4.6 | 3 | No | NO NEW IDEAS |
| gpt-5.3-codex | 3 | Yes | Use ClearValue(TimePicker.SelectedTimeProperty) for the null state, then a commit-only sync idea |
| gemini-3-pro-preview | 3 | Yes | Make the mapping value-source-aware so default MAUI time does not propagate as a native value |
Exhausted: Yes
Best-Fix Comparison
| Candidate | Why it passes | Trade-offs |
|---|---|---|
| PR #32314 | Uses WinUI's nullable SelectedTime API in both mapping and event synchronization |
Slightly larger than the smallest passing alternative, but direct and idiomatic |
| Candidate #3 | Minimal forward-path fix | Does not explicitly modernize reverse sync semantics; narrower than the PR |
| Candidate #6 | Handles a possible initialization race | Adds lifecycle/timing complexity |
| Candidate #9 | Distinguishes default MAUI value from an explicitly set midnight | Broadens scope into Controls and adds Windows-specific value-source behavior |
Selected Fix: PR #32314 — It is the best overall fix because it is robust, passes Gate, matches the WinUI nullable API model directly, and avoids the extra lifecycle, DP-state, or Controls-layer complexity introduced by the alternative passing attempts.
📋 Report — Final Recommendation
✅ Final Recommendation: APPROVE
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Linked issue is #32266; PR scope is appropriately narrowed to Windows even though the original issue also mentions macOS |
| Gate | ✅ PASSED | Windows full verification proved the tests fail without the fix and pass with it |
| Try-Fix | ✅ COMPLETE | 9 attempts run, 7 passing; PR fix selected as best overall |
| Report | ✅ COMPLETE |
Summary
PR #32314 fixes the Windows TimePicker null-state bug by switching the native mapping to WinUI's nullable SelectedTime API and syncing native changes through SelectedTimeChanged. The added UI coverage correctly verifies both required scenarios: initial load with Time = null and clearing back to null after setting a value.
I also ran a full try-fix exploration across the required models. Several alternative implementations passed the same tests, but the PR's version is still the best choice because it uses the intended WinUI nullable API directly, stays within the Windows platform layer, and avoids the extra lifecycle hooks, dependency-property bookkeeping, or Controls-layer semantics changes introduced by the alternatives.
Root Cause
The Windows platform implementation treated a nullable MAUI TimePicker.Time as though it always had a value. Specifically, it pushed null through the non-nullable native Time API by coercing it to TimeSpan.Zero, which rendered as midnight. The PR corrects that by using WinUI's nullable SelectedTime property and associated nullable event path.
Fix Quality
The fix is good and review-ready.
- It matches reviewer guidance from the PR discussion to use WinUI's nullable
SelectedTime. - It passed Gate verification on Windows with the exact issue tests.
- It is more robust than the smaller passing variants because it handles both native mapping and native-to-virtual synchronization using the same nullable API surface.
- It avoids broader behavioral changes such as altering the Controls default value or adding Windows-specific
IsSet(TimeProperty)semantics in the shared layer.
…is null (#32314) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - On Windows, TimePicker renders a default time when its value is null, both on first load and after runtime updates, instead of showing an empty state. - On macOS, DatePicker and TimePicker render the default date/time when their values are null, again both initially and dynamically, rather than remaining empty. ### Root Cause Windows : - The TimePicker handler uses the null-coalescing operator with TimeSpan.Zero when the Time property is null. As a result, it defaults to 12:00 AM in 12-hour format and 00:00 in 24-hour format. ### Description of Change - Updated TimePickerExtensions.UpdateTime to clear the native control's time value when the logical time is null, instead of defaulting to midnight. - Documentation : [Time Picker](https://learn.microsoft.com/en-us/windows/apps/design/controls/time-picker#:~:text=When%20SelectedTime%20is%20null%2C%20the%20picker%20is%20%27unset%27%20and%20shows%20the%20field%20names%20instead%20of%20a%20time.) **Regarding Mac Platform :** - The same behavior is observed on the native platform as well. The values are not cleared when the date or time picker is set to null. In such cases, the default date or time is rendered. ### Issues Fixed Fixes #32266 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [x] iOS - [x] Mac ### Output | Before | After | |----------|----------| | <img src="https://github.com/user-attachments/assets/70d2fda1-66e4-4083-8df5-75c064ada067"> | <img src="https://github.com/user-attachments/assets/33f4fc5f-456f-4de8-b72c-99c07dc07eaa"> |
…is null (dotnet#32314) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - On Windows, TimePicker renders a default time when its value is null, both on first load and after runtime updates, instead of showing an empty state. - On macOS, DatePicker and TimePicker render the default date/time when their values are null, again both initially and dynamically, rather than remaining empty. ### Root Cause Windows : - The TimePicker handler uses the null-coalescing operator with TimeSpan.Zero when the Time property is null. As a result, it defaults to 12:00 AM in 12-hour format and 00:00 in 24-hour format. ### Description of Change - Updated TimePickerExtensions.UpdateTime to clear the native control's time value when the logical time is null, instead of defaulting to midnight. - Documentation : [Time Picker](https://learn.microsoft.com/en-us/windows/apps/design/controls/time-picker#:~:text=When%20SelectedTime%20is%20null%2C%20the%20picker%20is%20%27unset%27%20and%20shows%20the%20field%20names%20instead%20of%20a%20time.) **Regarding Mac Platform :** - The same behavior is observed on the native platform as well. The values are not cleared when the date or time picker is set to null. In such cases, the default date or time is rendered. ### Issues Fixed Fixes dotnet#32266 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [x] iOS - [x] Mac ### Output | Before | After | |----------|----------| | <img src="https://github.com/user-attachments/assets/70d2fda1-66e4-4083-8df5-75c064ada067"> | <img src="https://github.com/user-attachments/assets/33f4fc5f-456f-4de8-b72c-99c07dc07eaa"> |
…is null (#32314) <!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Issue Details - On Windows, TimePicker renders a default time when its value is null, both on first load and after runtime updates, instead of showing an empty state. - On macOS, DatePicker and TimePicker render the default date/time when their values are null, again both initially and dynamically, rather than remaining empty. ### Root Cause Windows : - The TimePicker handler uses the null-coalescing operator with TimeSpan.Zero when the Time property is null. As a result, it defaults to 12:00 AM in 12-hour format and 00:00 in 24-hour format. ### Description of Change - Updated TimePickerExtensions.UpdateTime to clear the native control's time value when the logical time is null, instead of defaulting to midnight. - Documentation : [Time Picker](https://learn.microsoft.com/en-us/windows/apps/design/controls/time-picker#:~:text=When%20SelectedTime%20is%20null%2C%20the%20picker%20is%20%27unset%27%20and%20shows%20the%20field%20names%20instead%20of%20a%20time.) **Regarding Mac Platform :** - The same behavior is observed on the native platform as well. The values are not cleared when the date or time picker is set to null. In such cases, the default date or time is rendered. ### Issues Fixed Fixes #32266 ### Validated the behaviour in the following platforms - [x] Windows - [x] Android - [x] iOS - [x] Mac ### Output | Before | After | |----------|----------| | <img src="https://github.com/user-attachments/assets/70d2fda1-66e4-4083-8df5-75c064ada067"> | <img src="https://github.com/user-attachments/assets/33f4fc5f-456f-4de8-b72c-99c07dc07eaa"> |

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
Windows :
Description of Change
Regarding Mac Platform :
Issues Fixed
Fixes #32266
Validated the behaviour in the following platforms
Output