본문 바로가기

Why SelectionChanged Fires Incorrectly in .NET MAUI CollectionView

@veedeeo 2025. 12. 21. 12:16

A Deep Dive Into Virtualized Selection State, UI Recycling, and Event Timing**

Many MAUI developers are confused when CollectionView’s SelectionChanged event fires in moments that seem impossible:

  • You tap an item → event fires twice
  • You scroll quickly → event fires even though you didn’t tap anything
  • Selection jumps between items
  • SelectedItem becomes null unexpectedly
  • SelectionChanged triggers before your UI updates
  • The event fires even when SelectionMode=None

This is one of the most misunderstood behaviors in the MAUI CollectionView ecosystem.

Why?

Because in a virtualized list, Selection is not tied to the UI element you see.
It is tied to MAUI’s internal selection coordinator + recycled visual containers + BindingContext transitions.

This article explains exactly why SelectionChanged misbehaves and how to build a predictable, stable selection system.

 

Summary Card (TLDR)

Why SelectionChanged Fires Incorrectly in .NET MAUI CollectionView

1. SelectionChanged is not strictly user-driven
It fires not only when the user selects an item, but also when:

  • Containers are recycled
  • BindingContexts change
  • ItemsSource updates
  • Visual states re-sync
  • SelectedItem is auto-corrected internally

2. Virtualization causes SelectionChanged to fire multiple times
Recycled templates may trigger selection corrections, producing:

  • Double events
  • Null → value → null transitions
  • SelectionChanged firing without any tap

3. CheckBox and TapGestureRecognizer disrupt the selection pipeline
Interactive child controls consume gestures or change state ahead of MAUI’s selection coordinator, causing:

  • Mismatched selection
  • Missed events
  • Out-of-order timing

4. SelectedItem may become null temporarily
During fast scroll, MAUI detaches the old container before attaching the new one, causing brief null-selection states.

5. SelectionChanged is unreliable for business logic
It should not be used as a transactional trigger.
It is a UI synchronization event, not a guaranteed user-action event.

6. Stable solution: model-driven selection
Instead of relying on SelectionMode + SelectionChanged:

  • Disable SelectionMode
  • Bind IsSelected directly to your model
  • Use CheckBox or custom gestures to control selection state

Core Principle:
SelectionChanged reflects MAUI’s internal UI synchronization, not definitive user intent.
For reliable logic, let your model—not the UI—own the selection state.

 

Why SelectionChanged Fires Incorrectly in .NET MAUI CollectionView


1. SelectionChanged Does Not Mean the User Selected Something

When you see this event:

 
void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    // ...
}
 
 

You naturally assume:

“The user selected or deselected an item.”

But in a virtualized UI like MAUI’s CollectionView, SelectionChanged also fires when:

  • A recycled container receives a new BindingContext
  • The previous selected container scrolls out of view
  • The internal selection index is auto-corrected
  • SelectionMode changes
  • ItemsSource updates
  • The list is re-rendered
  • The UI thread delays the visual update

This is why developers often see:

  • SelectionChanged firing without any tap
  • SelectionChanged firing multiple times for one tap
  • SelectionChanged firing after a scroll, not a tap

The event is not strictly user-driven.
It is virtualization-driven.


2. The Most Common Trap: SelectionChanged Firing Twice

This happens because:

  1. The tap gesture selects the item
  2. Immediately after, MAUI updates the container’s visual state
  3. Virtualization rebinds the template
  4. SelectionChanged fires again due to state correction

This is why the event sequence looks like:

 
SelectionChanged → UI updates → internal correction → SelectionChanged again

If your handler modifies the selection inside the event,
you easily trigger feedback loops.


3. Why SelectedItem Becomes NULL Momentarily

This is the behavior that confuses most developers.

During fast scrolling or item replacement:

  • The selected visual container is recycled
  • MAUI loses the reference to the previously selected item
  • It briefly sets SelectedItem = null
  • Once stabilization finishes, it reassigns SelectedItem

So the sequence becomes:

SelectedItem = OldItem  
SelectedItem = null  
SelectedItem = NewItem
 
 

Which means your SelectionChanged fires twice or three times.

This is expected behavior.


4. Why Selection Behaves Worse With CheckBoxes or TapGestureRecognizer

These UI elements compete with the built-in selection pipeline.

CheckBox

  • Consumes tap
  • Updates its own IsChecked before selection stabilizes
  • Can cause selection toggles without firing actual selection events

TapGestureRecognizer

  • Intercepts gesture before CollectionView does
  • Causes SelectionMode to receive incomplete or incorrect gesture signals
  • May select an item before the BindingContext is ready

When combined with virtualization,
these create unstable selection transitions.


5. The Real Reason Some Items Never Trigger SelectionChanged

When scrolling quickly, a user may tap while the BindingContext is mid-transition.

Timeline:

  1. User taps
  2. BindingContext has not yet finished switching to the correct item
  3. Selection coordinator rejects the selection
  4. Event never fires
  5. UI may look selected, but logic is not executed

This is not a bug—it is a race condition.


6. The Solution: Use a Model-Based Selection System

Instead of relying on SelectionChanged as the core logic,
an expert-level approach is:

 Use SelectionMode=None

 Use CheckBox or toggle UI

 Bind all selected state to your viewmodel

Example model:

 
public class MyItem : ObservableObject
{
    [ObservableProperty]
    private bool isSelected;
}
 
 

XAML:

<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" />
 
 

Now selection no longer depends on:

  • Template lifecycle
  • Gesture routing
  • UI recycling
  • Tap timing

Instead, selection depends solely on your model.

This eliminates 90% of SelectionChanged problems.


7. If You MUST Use SelectionMode

Then follow these rules:

 Rule 1 — Never modify SelectedItem from inside SelectionChanged

This creates loops.

 Rule 2 — Do not combine CheckBox and SelectionMode

They fight.

 Rule 3 — Never mix TapGestureRecognizer with SelectionMode

Gesture competition breaks selection.

 Rule 4 — Expect SelectionChanged to fire multiple times

Design your logic to be idempotent (safe to run repeatedly).

Rule 5 — Avoid SelectionChanged for business logic

Use it for visual state only.


8. The Stable, Recommended Pattern

If you need row selection behavior:

 
SelectionMode="Single"
SelectedItem="{Binding SelectedItem}"
 
 

And in the ViewModel:

 
private object _selectedItem;
public object SelectedItem
{
    get => _selectedItem;
    set
    {
        if (value == _selectedItem) return;
        _selectedItem = value;
        OnItemSelected(value);
    }
}
 
 

This lets you centralize selection logic
instead of reacting to inconsistent UI events.


Final Expert Takeaway

CollectionView selection in MAUI appears simple,
but under virtualization it behaves like this:

  • SelectionChanged ≠ user action
  • SelectedItem can be null → value → null
  • Events fire due to recycling, not tapping
  • Interactive children disrupt the pipeline
  • Gesture routing and timing affect selection reliability

The key is understanding:

SelectionChanged is not a stable business event.
It is a UI synchronization event inside a virtualized layout.

For robust selection logic:

 Let the model store selection

 Keep SelectionMode simple

 Avoid mixing gestures and selection

 Treat SelectionChanged as advisory, not authoritative

veedeeo
@veedeeo :: .net MAUI·Netlify·SEO·웹 최적화 기술 블로그

.net MAUI·Netlify·SEO·웹 최적화 기술 블로그

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차