Skip to content

Add Pin.ShowInfoWindow() and Pin.HideInfoWindow() methods#33985

Merged
jfversluis merged 5 commits intonet11.0from
feature/show-info-window
Mar 2, 2026
Merged

Add Pin.ShowInfoWindow() and Pin.HideInfoWindow() methods#33985
jfversluis merged 5 commits intonet11.0from
feature/show-info-window

Conversation

@jfversluis
Copy link
Copy Markdown
Member

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!

Description

Adds the ability to programmatically show and hide map pin info windows/callouts, addressing a long-standing community request.

Fixes #10601

API Changes

Microsoft.Maui.Maps.IMap (Core):

  • void ShowInfoWindow(IMapPin pin) — Shows the info window for the specified pin
  • void HideInfoWindow(IMapPin pin) — Hides the info window for the specified pin

Microsoft.Maui.Controls.Maps.Pin (Controls):

  • void ShowInfoWindow() — Shows the info window for this pin
  • void HideInfoWindow() — Hides the info window for this pin

Implementation Details

  • Android: Uses Marker.ShowInfoWindow() / Marker.HideInfoWindow() via Google Maps SDK
  • iOS/MacCatalyst: Uses MKMapView.SelectAnnotation() / MKMapView.DeselectAnnotation() via MapKit
  • Windows/Tizen/Standard: Stub implementations (not supported)

Bug Fix: Pin.Parent tracking

Fixed a pre-existing bug where Pin.Parent was never set when pins were added to Map.Pins. The PinsOnCollectionChanged method now properly sets pin.Parent = this for new pins and pin.Parent = null for removed pins. This fix is required for ShowInfoWindow() to work (it needs to traverse to the parent Map to invoke the handler), but also fixes any other code path that relies on Pin.Parent.

Testing

  • 4 unit tests added (ShowInfoWindow, HideInfoWindow, ShowInfoWindowRequiresParent, HideInfoWindowRequiresParent)
  • All 33 map unit tests pass
  • Manually verified on iOS, Android, and MacCatalyst

Part of Maps Epic: #33787

Copilot AI review requested due to automatic review settings February 10, 2026 19:26
@jfversluis jfversluis added this to the .NET 11.0-preview1 milestone Feb 10, 2026
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

Adds a new cross-platform API surface to programmatically show/hide a map pin’s info window (callout), wiring the request through IMap/handler commands and exposing convenience methods on Pin.

Changes:

  • Add IMap.ShowInfoWindow(IMapPin) / IMap.HideInfoWindow(IMapPin) and handler command mappings, with Android + iOS/MacCatalyst implementations.
  • Add Pin.ShowInfoWindow() / Pin.HideInfoWindow() and fix Pin.Parent assignment when pins are added/removed from Map.Pins.
  • Add unit tests and a new sample gallery page demonstrating the feature.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/Core/maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt Adds new Core Maps public APIs for info window show/hide + handler command methods.
src/Core/maps/src/PublicAPI/net/PublicAPI.Unshipped.txt Adds new Core Maps public APIs for info window show/hide + handler command methods.
src/Core/maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt Adds new Core Maps public APIs for info window show/hide + handler command methods.
src/Core/maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt Adds new Core Maps public APIs for info window show/hide + handler command methods.
src/Core/maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt Adds new Core Maps public APIs for info window show/hide + handler command methods.
src/Core/maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt Adds new Core Maps public APIs for info window show/hide + handler command methods.
src/Core/maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt Adds new Core Maps public APIs for info window show/hide + handler command methods.
src/Core/maps/src/Core/IMap.cs Extends IMap with ShowInfoWindow/HideInfoWindow methods.
src/Core/maps/src/Handlers/Map/MapHandler.cs Adds command mapper entries and command handlers for show/hide info window.
src/Core/maps/src/Handlers/Map/MapHandler.Android.cs Implements show/hide via marker lookup + Marker.ShowInfoWindow() / HideInfoWindow().
src/Core/maps/src/Handlers/Map/MapHandler.iOS.cs Implements show/hide via SelectAnnotation / DeselectAnnotation.
src/Core/maps/src/Handlers/Map/MapHandler.Windows.cs Adds stub instance methods for show/hide on unsupported platform handler.
src/Core/maps/src/Handlers/Map/MapHandler.Tizen.cs Adds stub instance methods for show/hide on unsupported platform handler.
src/Core/maps/src/Handlers/Map/MapHandler.Standard.cs Adds stub instance methods for show/hide on unsupported platform handler.
src/Controls/Maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt Adds new Pin.ShowInfoWindow() / Pin.HideInfoWindow() public APIs.
src/Controls/Maps/src/PublicAPI/net/PublicAPI.Unshipped.txt Adds new Pin.ShowInfoWindow() / Pin.HideInfoWindow() public APIs.
src/Controls/Maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt Adds new Pin.ShowInfoWindow() / Pin.HideInfoWindow() public APIs.
src/Controls/Maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt Adds new Pin.ShowInfoWindow() / Pin.HideInfoWindow() public APIs.
src/Controls/Maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt Adds new Pin.ShowInfoWindow() / Pin.HideInfoWindow() public APIs.
src/Controls/Maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt Adds new Pin.ShowInfoWindow() / Pin.HideInfoWindow() public APIs.
src/Controls/Maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt Adds new Pin.ShowInfoWindow() / Pin.HideInfoWindow() public APIs.
src/Controls/Maps/src/Pin.cs Adds ShowInfoWindow() / HideInfoWindow() convenience methods that invoke map handler commands.
src/Controls/Maps/src/Map.cs Fixes Pin.Parent assignment on pin add/remove (collection changed handler).
src/Controls/Maps/src/HandlerImpl/Map.Impl.cs Implements new IMap interface methods by invoking handler commands.
src/Controls/tests/Core.UnitTests/MapTests.cs Adds unit tests to ensure show/hide calls don’t throw.
src/Controls/samples/Controls.Sample/Pages/Controls/MapsGalleries/MapsGallery.cs Adds navigation entry to the new “Info Window” sample.
src/Controls/samples/Controls.Sample/Pages/Controls/MapsGalleries/InfoWindowGallery.xaml New sample UI for showing/hiding info windows.
src/Controls/samples/Controls.Sample/Pages/Controls/MapsGalleries/InfoWindowGallery.xaml.cs New sample code-behind that calls Pin.ShowInfoWindow() / HideInfoWindow().

Comment on lines +234 to +248
if (e.NewItems is not null)
{
foreach (Pin pin in e.NewItems)
{
pin.Parent = this;
}
}

if (e.OldItems is not null)
{
foreach (Pin pin in e.OldItems)
{
pin.Parent = null;
}
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

PinsOnCollectionChanged sets pin.Parent for NewItems and clears it for OldItems, but ObservableCollection.Clear() raises a Reset action with no OldItems. That means map.Pins.Clear() (and other reset scenarios) will leave the removed pins' Parent pointing at the Map, which can cause incorrect behavior and keep the Map alive (leak) if pins are retained elsewhere. Consider handling NotifyCollectionChangedAction.Reset (e.g., by using a custom collection that raises a Remove event with the removed items, similar to GestureElement.GestureRecognizerCollection).

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

Choose a reason for hiding this comment

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

I think it looks like a good point. I've seen some devs reporting issues with memory leaks when it comes to map, so it would be good to make sure we avoid them

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is a valid concern but it's a pre-existing issue in Map.cs, not introduced by this PR. The Reset action not clearing Parent was already the behavior before this change. A separate issue/PR should address this collection management improvement.

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Feb 26, 2026

Independent Code Review

Summary: Adds programmatic ShowInfoWindow() / HideInfoWindow() methods on Pin, with IMap interface methods and handler command mapper entries. Also sets Pin.Parent when pins are added/removed from the Map.

✅ What Looks Good

  • Clean API: pin.ShowInfoWindow() is intuitive
  • Pin correctly checks Parent is Map map before invoking — graceful no-op when orphaned
  • Setting pin.Parent = this on collection change is correct for the parent-child relationship
  • Android uses Marker.ShowInfoWindow() / HideInfoWindow() — correct native API
  • iOS uses SelectAnnotation / DeselectAnnotation — correct native API
  • Good test coverage

🔴 Issues

1. Android GetMarkerForPin does linear scan
for (int i = 0; i < _markers.Count; i++) is O(n). For maps with many pins, this could be slow. Consider using a Dictionary<string, Marker> indexed by marker ID.

2. Pin.Parent assignment on CollectionChanged doesn't handle Reset
The code only processes e.NewItems and e.OldItems. When the collection is cleared (Clear() fires Reset), OldItems is null for NotifyCollectionChangedAction.Reset. This means cleared pins will still have Parent set to the Map, causing a leak and allowing ShowInfoWindow to attempt to invoke on a handler that no longer has the pin.

3. Windows/Tizen/Standard throw NotImplementedException
Calling ShowInfoWindow on a pin on Windows will crash at runtime. These should be no-ops like the iOS handler patterns in other PRs.

4. Double invocation risk
IMap.ShowInfoWindow (explicit interface implementation in Map.Impl.cs) calls Handler?.Invoke(...) and Pin.ShowInfoWindow also calls map.Handler?.Invoke(...). The Pin method is the public API, but someone casting to IMap and calling it directly would get the same behavior. There's a risk of double invocation if both paths are hit.

🟡 Nits

  • The Pin.Parent assignment should arguably have been a separate PR since it fixes a pre-existing issue (pins never had parents set)
  • Android pin.MarkerId is not string pinMarkerId — the not pattern is clean but unusual for this codebase

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 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 -- 33985

Or

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

@jfversluis
Copy link
Copy Markdown
Member Author

Thanks for the review @kubaflo!

Fixed in latest commit:

  • NotImplementedException → no-op — \ShowInfoWindow/\HideInfoWindow\ are now no-ops on Standard, Tizen, and Windows. Calling \pin.ShowInfoWindow()\ on unsupported platforms now silently does nothing instead of crashing.

Responses to other points:

1. Android GetMarkerForPin linear scan — True, it's O(n). However, maps typically have < 100 pins, making this negligible. A \Dictionary<string, Marker>\ would require maintaining the dictionary lifecycle in sync with marker add/remove. The current approach is simple and correct. If performance becomes an issue with large pin counts, it can be optimized later.

2. Pin.Parent Reset action — As I noted in the review thread, this is a pre-existing issue. The \PinsOnCollectionChanged\ method never handled \Reset\ before this PR, and this PR only adds the \NewItems/\OldItems\ handling for \Parent. A separate PR should address \Reset\ handling across all Map collections. Filed issue to track.

3. Double invocation risk — The explicit \IMap.ShowInfoWindow\ implementation in \Map.Impl.cs\ and the \Pin.ShowInfoWindow()\ method both call \Handler.Invoke, but they serve different purposes: \IMap\ is called by the handler infrastructure, \Pin\ is the public API. There's no risk of double invocation because users only call \Pin.ShowInfoWindow()\ — the \IMap\ interface method is for handler plumbing.

4. Pin.Parent in separate PR — Agreed it could have been separate, but it was a required fix for \ShowInfoWindow\ to work (the Pin needs to traverse to its parent Map). Including it here makes the feature complete and self-contained.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Feb 27, 2026

🔍 Round 2 Review — PR #33985 (ShowInfoWindow / HideInfoWindow)

Updated with author response analysis

✅ Fixed Since Round 1

  • Standard/Tizen/Windows: Now use empty method bodies { } instead of throw new NotImplementedException().
  • Pin.Parent assignment: PinsOnCollectionChanged now sets pin.Parent = this on add and pin.Parent = null on remove.
  • Tests: Cover both "no parent" and "with parent" scenarios.

⚠️ Issues & Author Responses

1. Pin.Parent not cleared on CollectionChanged.ResetAcknowledged as pre-existing issue
Author correctly points out this is a pre-existing issue — PinsOnCollectionChanged never handled Reset before this PR. This PR only adds NewItems/OldItems handling for Parent, which is the minimum required for ShowInfoWindow to work. A separate issue has been filed to address Reset handling across all Map collections. Fair to defer. ✅

2. Android GetMarkerForPin is O(n) linear scanAccepted as pragmatic choice
Author notes maps typically have < 100 pins, making this negligible. A Dictionary<string, Marker> would require maintaining lifecycle sync with marker add/remove. The current approach is simple and correct for typical usage. Can be optimized later if needed. ✅

3. Pin.ShowInfoWindow() bypasses IMap interfaceSoftened after author explanation
Author explains: IMap.ShowInfoWindow is for handler infrastructure plumbing, while Pin.ShowInfoWindow() is the public API. Users only call the Pin method. There's no double-invocation risk since the two paths serve different purposes. However, if someone uses a custom handler mapper to intercept ShowInfoWindow, the Pin's direct Handler.Invoke bypasses the IMap implementation. This is an edge case and consistent with how other MAUI controls work. ✅

4. iOS pin.MarkerId is IMKAnnotation type mismatch with AndroidAcknowledged as existing pattern
IMapPin.MarkerId is object? by design — each platform stores its native marker reference. This is a pre-existing pattern, not introduced by this PR. ✅

5. Breaking IMap interface changeRetracted
IMap is an internal handler contract. Consistent across all 8 PRs. ✅

👍 What Looks Good

  • iOS correctly uses SelectAnnotation/DeselectAnnotation with animation.
  • Graceful degradation: Pin.ShowInfoWindow() is a no-op when not attached to a map.
  • Including Pin.Parent assignment in this PR makes the feature self-contained — ShowInfoWindow needs to traverse to the parent Map.

Summary

All concerns were either pre-existing issues (properly deferred to follow-up) or pragmatic design choices. The Pin.Parent inclusion with ShowInfoWindow is well-justified. This PR is ready.

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Feb 27, 2026

📋 Round 3 — PR #33985 (ShowInfoWindow / HideInfoWindow) — Final Recommendation

No outstanding issues. Pre-existing CollectionChanged.Reset handling has been filed separately. All other Round 2 concerns were satisfactorily addressed.

Recommendation: Merge when CI passes. The Build Analysis failure is infrastructure-level.

Merge order note: This PR depends on Pin.Parent being set correctly. If #33831 (clustering) merges first and modifies PinsOnCollectionChanged, verify the Parent assignment code doesn't conflict.

kubaflo
kubaflo previously approved these changes Feb 27, 2026
jfversluis and others added 5 commits March 2, 2026 11:57
Adds the ability to programmatically show and hide info windows on map
pins. This is a frequently requested feature (issue #10601) that enables
developers to control pin callout visibility from code.

Implementation:
- Added ShowInfoWindow(IMapPin) and HideInfoWindow(IMapPin) to IMap interface
- Added ShowInfoWindow() and HideInfoWindow() public methods on Pin class
- Android: Uses Marker.ShowInfoWindow()/HideInfoWindow() via marker lookup
- iOS: Uses MKMapView.SelectAnnotation()/DeselectAnnotation() with animation
- Added stubs for Standard, Windows, and Tizen handlers
- Set Pin.Parent when pins are added/removed from Map.Pins collection
- Updated all PublicAPI.Unshipped.txt files (14 files)
- Added 4 unit tests (all 33 map tests pass)
- Added InfoWindowGallery sample page

Fixes #10601
- Fix namespace mismatch: InfoWindowGallery uses correct namespace
- Fix x:Class in XAML to match corrected namespace
- Fix MapsGallery.cs reference to InfoWindowGallery (remove extra qualification)
- Remove duplicate PublicAPI entries (keep nullable-annotated versions only)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of throwing NotImplementedException, these methods now
silently no-op on Standard, Tizen, and Windows platforms where
info window control is not supported.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jfversluis jfversluis force-pushed the feature/show-info-window branch from 31a6238 to 936b853 Compare March 2, 2026 11:00
@jfversluis jfversluis merged commit 38ac72f into net11.0 Mar 2, 2026
25 of 29 checks passed
@jfversluis jfversluis deleted the feature/show-info-window branch March 2, 2026 13:11
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

🚨 API change(s) detected @davidortinau FYI

@dotnet dotnet deleted a comment from dotnet-policy-service bot Mar 2, 2026
@github-actions github-actions bot locked and limited conversation to collaborators Apr 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants