Description:
I'm experiencing a strange behavior in my WinUI 3 application with a ListView displaying a collection of items. The CheckBox in each item template is bound to a bool property called IsComplete. This is a multi-client, server-synchronized application where data changes can occur from different clients and are synced through the server.
Details:
- When I check or uncheck the CheckBox for any item within the WinUI application, it works correctly and updates the IsComplete property as expected.
- When changes are made to items in another client, and these changes are synced to the server and then pulled into this WinUI application, all items reflect the updates correctly in both the bound collection and the UI—except for the first item in the ListView.
- The first item's CheckBox and TextBlock do not visually update to match the new value of IsComplete after a server sync, even though the bound data has been updated correctly (verified via debug step through).
Relevant Code
XAML
<ListView
Grid.Row="1"
Margin="10"
ItemsSource="{x:Bind ViewModel.Items}"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox
Margin="10"
Command="{Binding ViewModel.EditItemCommand, ElementName=ThisPage}"
CommandParameter="{Binding Id}"
Content="{Binding Title}"
IsChecked="{Binding IsComplete}" />
<TextBlock Margin="10" Text="Checked: " />
<TextBlock Text="{Binding IsComplete}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Bound collection
[ObservableProperty]
private ConcurrentObservableCollection<TodoItem> items = [];
Refresh command
[RelayCommand]
public async Task RefreshItemsAsync(CancellationToken cancellationToken = default)
{
try
{
IsRefreshing = true;
// Synchronize data with the remote service (if any).
await service.SynchronizeAsync(cancellationToken);
// Pull all items from the database.
IEnumerable<TodoItem> itemsFromDatabase = await service.TodoItems.OrderBy(item => item.Id).ToListAsync(cancellationToken);
// Replace all the items in the collection.
Items.ReplaceAll(itemsFromDatabase);
//Items.Clear();
//_ = Items.AddRange(itemsFromDatabase);
}
catch (Exception ex)
{
NotificationHandler?.Invoke(this, new NotificationEventArgs(ex.GetType().Name, ex.Message, true));
}
finally
{
IsRefreshing = false;
NotificationHandler?.Invoke(this, new NotificationEventArgs("Items Refreshed", "", false));
}
}
Data model
public class TodoItem : OfflineClientEntity
{
private bool _isComplete = false;
public string Title { get; set; } = string.Empty;
public bool IsComplete { get; set; } = false;
public override string ToString()
=> JsonSerializer.Serialize(this);
}
using System;
using System.ComponentModel.DataAnnotations;
namespace TodoApp.WinUI3.Database;
/// <summary>
/// An abstract class for working with offline entities.
/// </summary>
public abstract class OfflineClientEntity
{
[Key]
public string Id { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public string Version { get; set; }
public bool Deleted { get; set; }
}
Observed Behavior
- Clicking the CheckBox directly in the UI updates IsComplete and correctly reflects the change.
- When another client modifies the first item and syncs the changes, and those changes are pulled into the WinUI application, the bound IsComplete property is correctly updated for the first item (verified with a TextBlock), but the CheckBox visual state does not reflect this change.
- All other items in the list behave as expected, both when directly modified in the UI and when updated from server synchronization.
Itemson the view model? Where doesReplaceAllcome from?