Skip to content

Fix #7536: crash when changing source during SelectionChanged - #22102

Merged
MrJul merged 4 commits into
mainfrom
fix/7536-selection-changed-source-update
Sep 1, 2026
Merged

MrJul merged 4 commits into
mainfrom
fix/7536-selection-changed-source-update

Conversation

@grokys

@grokys grokys commented Aug 30, 2026

Copy link
Copy Markdown
Member

What

Fixes #7536: an InvalidOperationException ("Cannot change source while update is in progress.") when a SelectingItemsControl's source is changed from within its own SelectionChanged handler while the selection is being lost.

Cause

In SelectionModel.CommitOperation, when the selection is lost the code bumps the operation's UpdateCount before raising LostSelection so that any selection change made by the LostSelection handler (e.g. AlwaysSelected re-selecting the first item) folds into the current operation. However it never decremented the count again, so the rest of the commit — including the SelectionChanged event — ran with UpdateCount > 0.

As a result:

  • A SelectionChanged handler that changed the control's source (as StructuredLogViewer's UpdateBreadcrumb does) hit the UpdateCount > 0 guard in SetSource and threw.
  • A SelectionChanged handler that changed the selection had its change batched into the outer operation and then silently discarded when the commit finished.

Note the non-lost-selection path already ran SelectionChanged at UpdateCount == 0; this only brings the lost-selection path in line with it.

Fix

Decrement UpdateCount again once the LostSelection handler returns, so the batching only wraps that handler and the subsequent SelectionChanged event runs at UpdateCount == 0.

Tests

  • Changing_ItemsSource_During_SelectionChanged_When_Selection_Lost_Does_Not_Throw — reproduces the original crash.
  • Can_Change_Selection_From_SelectionChanged_When_AlwaysSelected_Reselects — pins the interaction with AlwaysSelected: the re-selection still folds into the operation, and an in-handler selection change is now honoured instead of being swallowed. Both fail before the fix and pass after.

Full SelectingItemsControlTests* (540), SelectionModelTests* (122), ListBoxTests and TreeViewTests suites pass.

🤖 Generated with Claude Code

grokys and others added 3 commits August 30, 2026 12:52
Reproduces the "Cannot change source while update is in progress" crash that
occurs when a SelectingItemsControl's ItemsSource is changed from within its own
SelectionChanged handler while the selection is being lost. When selection is
lost, CommitOperation bumps the operation UpdateCount before raising
LostSelection but never decrements it, so SelectionChanged is raised with
UpdateCount > 0 and SetSource throws.

Claude-Session: https://claude.ai/code/session_01FXP1ejZ9VhUwecY9QCRKVb
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the selection was lost, SelectionModel.CommitOperation incremented the
operation's UpdateCount before raising LostSelection but never decremented it.
As a result the rest of the commit - including the SelectionChanged event - ran
with UpdateCount > 0, so a handler that changed the control's source (as
StructuredLogViewer's UpdateBreadcrumb does) hit the "Cannot change source while
update is in progress." guard in SetSource and threw.

Decrement UpdateCount again once the LostSelection handler has returned so that
the batching only covers the handler itself and the SelectionChanged event is
free to change the source.

Claude-Session: https://claude.ai/code/session_01FXP1ejZ9VhUwecY9QCRKVb
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cted reselects

Covers the interaction between the #7536 fix and AlwaysSelected: clearing the
selection makes AlwaysSelected reselect the first item via LostSelection (which
must still fold into the current operation), and a SelectionChanged handler that
then changes the selection must be honoured rather than swallowed by the batch
update wrapping the LostSelection handler. Fails before the fix (selection stays
on the reselected item), passes after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXP1ejZ9VhUwecY9QCRKVb
@grokys grokys added bug backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch area-controls labels Aug 30, 2026
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.2.999-cibuild0069155-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@grokys
grokys requested a lite review from Copilot August 31, 2026 09:55

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

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.

Pull request overview

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

@MrJul MrJul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think Copilot's comment on the test is valid.

Aside from that, this looks good.

I'm a bit surprised the absence of a matching decrement didn't cause more issues. Since the operation is quickly disposed, I guess the problem was fairly contained.

@grokys

grokys commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@MrJul test updated.

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.2.999-cibuild0069240-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul MrJul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@MrJul
MrJul added this pull request to the merge queue Sep 1, 2026
Merged via the queue into main with commit 1c9e559 Sep 1, 2026
10 checks passed
@MrJul
MrJul deleted the fix/7536-selection-changed-source-update branch September 1, 2026 08:41
MrJul pushed a commit to MrJul/Avalonia that referenced this pull request Sep 2, 2026
…ed (AvaloniaUI#22102)

* Add failing test for AvaloniaUI#7536

Reproduces the "Cannot change source while update is in progress" crash that
occurs when a SelectingItemsControl's ItemsSource is changed from within its own
SelectionChanged handler while the selection is being lost. When selection is
lost, CommitOperation bumps the operation UpdateCount before raising
LostSelection but never decrements it, so SelectionChanged is raised with
UpdateCount > 0 and SetSource throws.

Claude-Session: https://claude.ai/code/session_01FXP1ejZ9VhUwecY9QCRKVb
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix AvaloniaUI#7536: crash when changing source during SelectionChanged

When the selection was lost, SelectionModel.CommitOperation incremented the
operation's UpdateCount before raising LostSelection but never decremented it.
As a result the rest of the commit - including the SelectionChanged event - ran
with UpdateCount > 0, so a handler that changed the control's source (as
StructuredLogViewer's UpdateBreadcrumb does) hit the "Cannot change source while
update is in progress." guard in SetSource and threw.

Decrement UpdateCount again once the LostSelection handler has returned so that
the batching only covers the handler itself and the SelectionChanged event is
free to change the source.

Claude-Session: https://claude.ai/code/session_01FXP1ejZ9VhUwecY9QCRKVb
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add test for changing selection from SelectionChanged when AlwaysSelected reselects

Covers the interaction between the AvaloniaUI#7536 fix and AlwaysSelected: clearing the
selection makes AlwaysSelected reselect the first item via LostSelection (which
must still fold into the current operation), and a SelectionChanged handler that
then changes the selection must be honoured rather than swallowed by the batch
update wrapping the LostSelection handler. Fails before the fix (selection stays
on the reselected item), passes after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXP1ejZ9VhUwecY9QCRKVb

* Assert that `SelectionChanged` was called.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@MrJul MrJul added backported-12.1.x and removed backport-candidate-12.1.x Consider this PR for backporting to 12.1 branch labels Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash during selection change

4 participants