Refactored the TodoForm to avoid ListViewItem lifecycle crashes by separating data model from UI representation.
- Todos are stored in
List<TodoItem>(not ListViewItem objects) - TodoItem contains:
Id(Guid),Title(string),IsCompleted(bool),CreatedDate(DateTime),CompletedDate(DateTime?) - Data persists through TodoStorage.Save() to JSON file
- Never reuse ListViewItem objects - ListView is completely cleared and rebuilt on every change
- Fresh ListViewItem created from TodoItem data each time
- Prevents lifecycle issues from object pooling, event re-entry, or disposal
- Tag stores only the TodoItem.Id (Guid), not the TodoItem reference
- All public methods operate on UI thread only
isUpdatingUIflag prevents event re-entry during ListView rebuild- ItemChecked events ignored while UI is updating
- try/finally ensures flag is reset even if exceptions occur
- Validates text input (empty/whitespace check)
- Creates new TodoItem with fresh Guid ID
- Appends to data model
- Saves to storage
- Clears input field
- Refreshes UI
- Verifies isUpdatingUI flag and itemId validity
- Finds TodoItem by ID in data model
- Updates IsCompleted and CompletedDate in data model
- Saves to storage
- Refreshes UI
- Validates selection exists
- Removes TodoItem by ID from data model (handles multiple matches safely)
- Saves to storage
- Refreshes UI
- Sets isUpdatingUI flag (blocks ItemChecked events)
- Clears all ListView items
- Rebuilds from data model
- Shows only today's todos (filters by CreatedDate.Date)
- Sorts by creation time (newest first)
- Applies visual styling (SubtleText color for completed items)
- Resets flag in finally block
- TodoStorage.Load() loads from JSON on form construction
- TodoStorage.Save() persists after every change
- Storage handles errors gracefully (returns empty list on failure)
// Stored TodoItem in ListViewItem.Tag - risky
listItem.Tag = item;
// Flag named "isLoading" was confusing
bool isLoading;
// Event handler directly modified TodoItem reference
item.IsCompleted = e.Item.Checked;
// Unclear pattern - ItemChecked event sometimes processed during UI updates// Store only the ID, data lives in List<TodoItem>
listItem.Tag = todo.Id;
// Clear flag name indicates UI update state
bool isUpdatingUI;
// Data model updated first, then UI rebuilt
todo.IsCompleted = e.Item.Checked;
RefreshUI();
// Explicit re-entry protection via flag
if (isUpdatingUI || e.Item?.Tag is not Guid itemId) return;-
Add, Toggle, Delete Operations
- Add multiple todos
- Toggle completion status
- Delete todos
- Verify no crashes or duplicate items
-
Persistence
- Add/modify/delete todos
- Close and reopen application
- Verify todos persist correctly
-
UI Consistency
- Toggle completion and verify styling
- Check that only today's todos appear
- Verify sort order (newest first)
-
Edge Cases
- Rapid add/delete operations
- Empty todo list
- Very long todo text
- Rapid checkbox toggling
✅ No ListViewItem reuse - Fresh objects prevent disposal/lifetime issues
✅ Event re-entry protected - isUpdatingUI flag prevents nested updates
✅ Clear separation of concerns - Data model independent from UI
✅ Safe deletion - RemoveAll() handles ID matching reliably
✅ UI thread safe - All operations occur on UI thread
✅ Exception safe - try/finally ensures flag reset
✅ Thread-aware storage - JSON I/O wrapped in error handling
- TodoForm.cs - Complete refactoring with safety improvements
- TodoItem.cs - No changes (already well-designed)
- TodoStorage.cs - No changes (already handles errors gracefully)