microsoft / microsoft/microsoft-ui-xaml
Crash when PipsPager and FlipView are bound to same ObservableCollection and deleting 2 or more items
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 8.4k
- Forks
- 942
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 105
Description
### Describe the bug
WinUI 3 Gallery app (https://github.com/microsoft/WinUI-Gallery) contains an example of a PipsPager integrated with a FlipView. When you adapt the example to become responsive to changes from the ObservableCollection, it all works fine when you delete 1 item from the collection. When deleting two or more, a crash occurs which cannot be explained from the C# side, nor caught by the debugger
### Why is this important?
Either PipsPager alone or FlipView alone doesn't produce a crash, it crashes only when the two are used on the same ObservableCollection and deleting 2 or more items. It is a crash for no apparent reason.
### Steps to reproduce the bug
Create a new WinUI project called `WinUI3Crash` and perform replacements underneath.
Scenario 1: PipsPager and FlipView with tight databinding, click on Delete 2 button underneath, crash
Scenario 2: Using a SelectedIndex to see how PipsPager perhaps can go beyond boundary, crash
Scenario 3: same as Scenario 2, just only FlipView, no crash
Scenario 4: same as Scenario 2, just only PipsPager, no crash
Scenario 5: Using NumberOfPages, crash
Scenario 6: same as Scenario 5, deferred updating of NumberOfPages, no crash
I'm using Scenario 6 as a workaround for the crash happening in production code, it would be great to know what the cause is, whether it requires a different fix, and whether it requires a fix at your end.
Code replacements:
Update TargetFramework within csproj to `net10.0-windows10.0.19041.0`
Update the following in case they differ:
```
```
Replace MainWindow.xaml
```
Delete 2 items, crash
Using SelectedIndex2, crash
Only FlipView, no crash
Only PipsPager, no crash
Using NumberOfPages5, crash
Update NumberOfPages6 in DispatcherQueue, no crash
```
Replace MainWindow.xaml.cs
```
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace WinUI3Crash
{
///
/// An empty window that can be used on its own or navigated to within a Frame.
///
public sealed partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection Pictures1 = ["1", "2", "3", "4"];
public ObservableCollection Pictures2 = ["1", "2", "3", "4"];
public ObservableCollection Pictures3 = ["1", "2", "3", "4"];
public ObservableCollection Pictures4 = ["1", "2", "3", "4"];
public ObservableCollection Pictures5 = ["1", "2", "3", "4"];
public ObservableCollection Pictures6 = ["1", "2", "3", "4"];
public int SelectedIndex2
{
get;
set
{
if (field != value)
{
field = value;
OnPropertyChanged();
}
}
}
public int SelectedIndex3
{
get;
set
{
if (field != value)
{
field = value;
OnPropertyChanged();
}
}
}
public int SelectedIndex4
{
get;
set
{
if (field != value)
{
field = value;
OnPropertyChanged();
}
}
}
public int NumberOfPages5
{
get;
set
{
if (field != value)
{
field = value;
OnPropertyChanged();
}
}
}
public int NumberOfPages6
{
get;
set
{
if (field != value)
{
field = value;
OnPropertyChanged();
}
}
}
public MainWindow()
{
Pictures5.CollectionChanged += Pictures5_CollectionChanged;
Pictures6.CollectionChanged += Pictures6_CollectionChanged;
NumberOfPages5 = Pictures5.Count;
NumberOfPages6 = Pictures6.Count;
InitializeComponent();
}
private void Pictures5_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NumberOfPages5 = Pictures5.Count;
}
private void Pictures6_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
DispatcherQueue.TryEnqueue(() =>
{
NumberOfPages6 = Pictures6.Count;
});
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
if (sender is not Button button)
return;
if (button.Tag is not string collectionItem)
return;
ObservableCollection collection;
switch (collectionItem)
{
case "1":
collection = Pictures1;
break;
case "2":
collection = Pictures2;
break;
case "3":
collection = Pictures3;
break;
case "4":
collection = Pictures4;
break;
case "5":
collection = Pictures5;
break;
case "6":
collection = Pictures6;
break;
default:
return;
}
if (collection.Count < 2)
{
return;
}
collection.RemoveAt(collection.Count - 1);
collection.RemoveAt(collection.Count - 1);
}
}
}
```
### Actual behavior
Crashes inexplicably, also doesn't crash somewhat inexplicably when deferring NumberOfPages field update.
When using a SelectedIndex field, there is nothing which would update the SelectedIndex beyond the PipsPager boundary.
As a last resort, I did an AI analysis and it came up with:
```
Defer NumberOfPages update to allow FlipView to process the collection change first
This avoids a race condition in the WinUI framework between FlipView and PipsPager
```
Still it wouldn't make sense that there even is a race condition between the two controls.
### Expected behavior
PipsPager/FlipView combination should not become instable when deleting 2 or more items from a shared ObservableCollection
### Screenshots
Screenshot of the crash with full debugging options enabled, still not able to step into anything.
### NuGet package version
Microsoft.WindowsAppSDK 2.3.1
### Windows version
Windows 11 (25H2): Build 26200
### Additional context
_No response_
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the replacement MainWindow.xaml and MainWindow.xaml.cs, build the supplied WinUI 3 repro, and run scenarios 1–6 to compare the crashing and non-crashing bindings. Trace the shared ObservableCollection updates between FlipView and PipsPager; done means the cause is identified and the multiple-item deletion case has a verified fix or a confirmed framework limitation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100